Repository: greenrobot/EventBus Branch: master Commit: 0194926b3bcf Files: 134 Total size: 333.0 KB Directory structure: gitextract_vwo4kya5/ ├── .github/ │ └── workflows/ │ └── gradle.yml ├── .gitignore ├── COMPARISON.md ├── CONTRIBUTING.md ├── EventBus/ │ ├── build.gradle │ └── src/ │ └── org/ │ └── greenrobot/ │ └── eventbus/ │ ├── AsyncPoster.java │ ├── BackgroundPoster.java │ ├── EventBus.java │ ├── EventBusBuilder.java │ ├── EventBusException.java │ ├── Logger.java │ ├── MainThreadSupport.java │ ├── NoSubscriberEvent.java │ ├── PendingPost.java │ ├── PendingPostQueue.java │ ├── Poster.java │ ├── Subscribe.java │ ├── SubscriberExceptionEvent.java │ ├── SubscriberMethod.java │ ├── SubscriberMethodFinder.java │ ├── Subscription.java │ ├── ThreadMode.java │ ├── android/ │ │ ├── AndroidComponents.java │ │ └── AndroidDependenciesDetector.java │ ├── meta/ │ │ ├── AbstractSubscriberInfo.java │ │ ├── SimpleSubscriberInfo.java │ │ ├── SubscriberInfo.java │ │ ├── SubscriberInfoIndex.java │ │ └── SubscriberMethodInfo.java │ └── util/ │ ├── AsyncExecutor.java │ ├── ExceptionToResourceMapping.java │ ├── HasExecutionScope.java │ └── ThrowableFailureEvent.java ├── EventBusAnnotationProcessor/ │ ├── build.gradle │ ├── res/ │ │ └── META-INF/ │ │ └── services/ │ │ └── javax.annotation.processing.Processor │ └── src/ │ └── org/ │ └── greenrobot/ │ └── eventbus/ │ └── annotationprocessor/ │ └── EventBusAnnotationProcessor.java ├── EventBusPerformance/ │ ├── .gitignore │ ├── AndroidManifest.xml │ ├── build.gradle │ ├── proguard-project.txt │ ├── project.properties │ ├── res/ │ │ ├── layout/ │ │ │ ├── activity_runtests.xml │ │ │ └── activity_setuptests.xml │ │ └── values/ │ │ └── strings.xml │ └── src/ │ └── org/ │ └── greenrobot/ │ └── eventbusperf/ │ ├── Test.java │ ├── TestEvent.java │ ├── TestFinishedEvent.java │ ├── TestParams.java │ ├── TestRunner.java │ ├── TestRunnerActivity.java │ ├── TestSetupActivity.java │ └── testsubject/ │ ├── PerfTestEventBus.java │ ├── PerfTestOtto.java │ └── SubscribeClassEventBusDefault.java ├── EventBusTest/ │ ├── AndroidManifest.xml │ ├── build.gradle │ ├── libs/ │ │ └── EventBusTestSubscriberInJar-3.0.0.jar │ └── src/ │ └── org/ │ └── greenrobot/ │ └── eventbus/ │ ├── AbstractAndroidEventBusTest.java │ ├── AndroidComponentsAvailabilityTest.java │ ├── ClassMapPerfTest.java │ ├── EventBusAndroidActivityTest.java │ ├── EventBusAndroidCancelEventDeliveryTest.java │ ├── EventBusAndroidMultithreadedTest.java │ ├── EventBusAndroidOrderTest.java │ ├── EventBusBackgroundThreadTest.java │ ├── EventBusMainThreadRacingTest.java │ ├── EventBusMainThreadTest.java │ ├── EventBusMethodModifiersTest.java │ ├── TestBackgroundPoster.java │ └── indexed/ │ ├── EventBusAndroidOrderTestWithIndex.java │ ├── EventBusBackgroundThreadTestWithIndex.java │ ├── EventBusBasicTestWithIndex.java │ ├── EventBusCancelEventDeliveryTestWithIndex.java │ ├── EventBusFallbackToReflectionTestWithIndex.java │ ├── EventBusGenericsTestWithIndex.java │ ├── EventBusInheritanceDisabledTestWithIndex.java │ ├── EventBusInheritanceTestWithIndex.java │ ├── EventBusMainThreadRacingTestWithIndex.java │ ├── EventBusMainThreadTestWithIndex.java │ ├── EventBusMethodModifiersTestWithIndex.java │ ├── EventBusMultithreadedTestWithIndex.java │ ├── EventBusNoSubscriberEventTestWithIndex.java │ ├── EventBusOrderedSubscriptionsTestWithIndex.java │ ├── EventBusRegistrationRacingTestWithIndex.java │ ├── EventBusStickyEventTestWithIndex.java │ ├── EventBusSubscriberExceptionTestWithIndex.java │ ├── EventBusSubscriberInJarTestWithIndex.java │ └── Indexed.java ├── EventBusTestJava/ │ ├── build.gradle │ ├── libs/ │ │ └── EventBusTestSubscriberInJar-3.0.0.jar │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── greenrobot/ │ └── eventbus/ │ ├── AbstractEventBusTest.java │ ├── EventBusBasicTest.java │ ├── EventBusBuilderTest.java │ ├── EventBusCancelEventDeliveryTest.java │ ├── EventBusFallbackToReflectionTest.java │ ├── EventBusGenericsTest.java │ ├── EventBusIndexTest.java │ ├── EventBusInheritanceDisabledSubclassNoMethod.java │ ├── EventBusInheritanceDisabledSubclassTest.java │ ├── EventBusInheritanceDisabledTest.java │ ├── EventBusInheritanceSubclassNoMethodTest.java │ ├── EventBusInheritanceSubclassTest.java │ ├── EventBusInheritanceTest.java │ ├── EventBusMultithreadedTest.java │ ├── EventBusNoSubscriberEventTest.java │ ├── EventBusOrderedSubscriptionsTest.java │ ├── EventBusRegistrationRacingTest.java │ ├── EventBusStickyEventTest.java │ ├── EventBusSubscriberExceptionTest.java │ ├── EventBusSubscriberInJarTest.java │ ├── EventBusSubscriberLegalTest.java │ └── IntTestEvent.java ├── EventBusTestSubscriberInJar/ │ ├── build.gradle │ └── src/ │ └── org/ │ └── greenrobot/ │ └── eventbus/ │ └── SubscriberInJar.java ├── LICENSE ├── README.md ├── build.gradle ├── eventbus-android/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── org/ │ └── greenrobot/ │ └── eventbus/ │ ├── HandlerPoster.java │ └── android/ │ ├── AndroidComponentsImpl.java │ ├── AndroidLogger.java │ └── DefaultAndroidMainThreadSupport.java ├── gradle/ │ ├── publish.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── javadoc-style/ │ └── stylesheet.css └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/gradle.yml ================================================ # This workflow will build a Java project with Gradle # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle name: Java CI with Gradle on: push: pull_request: jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up JDK 11 uses: actions/setup-java@v3 with: java-version: '11' distribution: 'temurin' cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Gradle Info run: ./gradlew -version - name: Build with Gradle run: ./gradlew build ================================================ FILE: .gitignore ================================================ # Built application files *.apk *.ap_ # Files for the Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ release/ # Gradle files .gradle/ build/ gradle.properties # Local configuration file (sdk path, etc) local.properties # IntelliJ project files *.iml .idea/ # Eclipse project files .settings/ # Misc .DS_Store ================================================ FILE: COMPARISON.md ================================================ EventBus Comparison =================== Comparison with Square's Otto ----------------------------- Otto is another event bus library for Android; actually it's a fork of Guava's EventBus. greenrobot's EventBus and Otto share some basic semantics (register, post, unregister, ...), but there are differences which the following table summarizes:
EventBus Otto
Declare event handling methods Annotations (since 3.0, can be precompiled for best performance) Annotations
Event inheritance Yes Yes
Subscriber inheritance Yes No
Cache most recent events Yes, sticky events No
Event producers (e.g. for coding cached events) No Yes
Event delivery in posting thread Yes (Default) Yes
Event delivery in main thread Yes No
Event delivery in background thread Yes No
Asynchronous event delivery Yes No
_**Note:** the following information is outdated, preprocessed annotations are much faster than EventBus 2.x, on which the following table is based._ Besides features, performance is another differentiator. To compare performance, we created an Android application, which is also part of this repository (EventBusPerformance). You can also run the app on your phone to benchmark different scenarios. TODO: Update for EventBus 3 with and without index. Benchmark results indicate that EventBus is significantly faster in almost every scenario:
EventBus Otto
Posting 1000 events, Android 2.3 emulator ~70% faster
Posting 1000 events, S3 Android 4.0 ~110% faster
Register 1000 subscribers, Android 2.3 emulator ~10% faster
Register 1000 subscribers, S3 Android 4.0 ~70% faster
Register subscribers cold start, Android 2.3 emulator ~350% faster
Register subscribers cold start, S3 Android 4.0 About the same
================================================ FILE: CONTRIBUTING.md ================================================ Before you create an Issue... ============================= There are better Places for Support ----------------------------------- We want your question to be answered, so it is important that you ask at the right place. Be aware that an issue tracker is not the best place to ask for support. An issue tracker is used to track issues (bugs or feature requests). Instead, please use [stackoverflow.com](https://stackoverflow.com/questions/tagged/greenrobot-eventbus?sort=frequent) and use the tag [greenrobot-eventbus](http://stackoverflow.com/tags/greenrobot-eventbus/info) for your question. If you want professional support, check http://greenrobot.org/contact-support/. Examples for support questions that are more likely to be answered on StackOverflow: * Asking how something works * Asking how to use EventBus in a specific scenario * Your app crashes/misbehaves and you are not sure why The perfect Issue Report ------------------------ A couple of simple steps can save time for everyone. Check before reporting: * It's not a support inquiry * You have read the docs * You searched the web and stackoverflow * You searched existing issues to avoid duplicates Reporting bugs: * Please investigate if is the bug is really caused by the library. Isolate the issue: what's the minimal code to reproduce the bug? * Bonus steps to gain extra karma points: once you isolated and identified the issue, you can prepare an push request. Submit an unit test causing the bug, and ideally a fix for the bug. Requesting features: * Ask yourself: is the feature useful for a majority users? One of our major goals is to keep the API simple and concise. We do not want to cover all possible use cases, but those that make 80% of users happy. A Note on Pull Requests ======================= Pull requests (and issues) may queue up up a bit. Usually, pull requests and issues are checked when new releases are planned. For bigger pull requests, it's a good idea to check with the maintainer upfront about the idea and the implementation outline. Thanks for reading! =================== It's your contributions and feedback that makes maintaining this library fun. ================================================ FILE: EventBus/build.gradle ================================================ apply plugin: 'java' group = rootProject.group version = rootProject.version java.sourceCompatibility = JavaVersion.VERSION_1_8 java.targetCompatibility = JavaVersion.VERSION_1_8 sourceSets { main { java { srcDir 'src' // exclude 'de/greenrobot/event/util/**' } } } javadoc { failOnError = false title = "EventBus ${version} API" options.bottom = 'Available under the Apache License, Version 2.0 - Copyright © 2012-2020 greenrobot.org. All Rights Reserved.' } task javadocJar(type: Jar, dependsOn: javadoc) { archiveClassifier.set("javadoc") from 'build/docs/javadoc' } task sourcesJar(type: Jar) { archiveClassifier.set("sources") from sourceSets.main.allSource } apply from: rootProject.file("gradle/publish.gradle") // Set project-specific properties afterEvaluate { publishing.publications { mavenJava(MavenPublication) { artifactId = "eventbus-java" from components.java artifact javadocJar artifact sourcesJar pom { name = "EventBus" description = "EventBus is a publish/subscribe event bus." packaging = "jar" } } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/AsyncPoster.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * Posts events in background. * * @author Markus */ class AsyncPoster implements Runnable, Poster { private final PendingPostQueue queue; private final EventBus eventBus; AsyncPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); queue.enqueue(pendingPost); eventBus.getExecutorService().execute(this); } @Override public void run() { PendingPost pendingPost = queue.poll(); if(pendingPost == null) { throw new IllegalStateException("No pending post available"); } eventBus.invokeSubscriber(pendingPost); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/BackgroundPoster.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import java.util.logging.Level; /** * Posts events in background. * * @author Markus */ final class BackgroundPoster implements Runnable, Poster { private final PendingPostQueue queue; private final EventBus eventBus; private volatile boolean executorRunning; BackgroundPoster(EventBus eventBus) { this.eventBus = eventBus; queue = new PendingPostQueue(); } public void enqueue(Subscription subscription, Object event) { PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event); synchronized (this) { queue.enqueue(pendingPost); if (!executorRunning) { executorRunning = true; eventBus.getExecutorService().execute(this); } } } @Override public void run() { try { try { while (true) { PendingPost pendingPost = queue.poll(1000); if (pendingPost == null) { synchronized (this) { // Check again, this time in synchronized pendingPost = queue.poll(); if (pendingPost == null) { executorRunning = false; return; } } } eventBus.invokeSubscriber(pendingPost); } } catch (InterruptedException e) { eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interrupted", e); } } finally { executorRunning = false; } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/EventBus.java ================================================ /* * Copyright (C) 2012-2020 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import org.greenrobot.eventbus.android.AndroidDependenciesDetector; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.logging.Level; /** * EventBus is a central publish/subscribe event system for Java and Android. * Events are posted ({@link #post(Object)}) to the bus, which delivers it to subscribers that have a matching handler * method for the event type. * To receive events, subscribers must register themselves to the bus using {@link #register(Object)}. * Once registered, subscribers receive events until {@link #unregister(Object)} is called. * Event handling methods must be annotated by {@link Subscribe}, must be public, return nothing (void), * and have exactly one parameter (the event). * * @author Markus Junginger, greenrobot */ public class EventBus { /** Log tag, apps may override it. */ public static String TAG = "EventBus"; static volatile EventBus defaultInstance; private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder(); private static final Map, List>> eventTypesCache = new HashMap<>(); private final Map, CopyOnWriteArrayList> subscriptionsByEventType; private final Map>> typesBySubscriber; private final Map, Object> stickyEvents; private final ThreadLocal currentPostingThreadState = new ThreadLocal() { @Override protected PostingThreadState initialValue() { return new PostingThreadState(); } }; // @Nullable private final MainThreadSupport mainThreadSupport; // @Nullable private final Poster mainThreadPoster; private final BackgroundPoster backgroundPoster; private final AsyncPoster asyncPoster; private final SubscriberMethodFinder subscriberMethodFinder; private final ExecutorService executorService; private final boolean throwSubscriberException; private final boolean logSubscriberExceptions; private final boolean logNoSubscriberMessages; private final boolean sendSubscriberExceptionEvent; private final boolean sendNoSubscriberEvent; private final boolean eventInheritance; private final int indexCount; private final Logger logger; /** Convenience singleton for apps using a process-wide EventBus instance. */ public static EventBus getDefault() { EventBus instance = defaultInstance; if (instance == null) { synchronized (EventBus.class) { instance = EventBus.defaultInstance; if (instance == null) { instance = EventBus.defaultInstance = new EventBus(); } } } return instance; } public static EventBusBuilder builder() { return new EventBusBuilder(); } /** For unit test primarily. */ public static void clearCaches() { SubscriberMethodFinder.clearCaches(); eventTypesCache.clear(); } /** * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a * central bus, consider {@link #getDefault()}. */ public EventBus() { this(DEFAULT_BUILDER); } EventBus(EventBusBuilder builder) { logger = builder.getLogger(); subscriptionsByEventType = new HashMap<>(); typesBySubscriber = new HashMap<>(); stickyEvents = new ConcurrentHashMap<>(); mainThreadSupport = builder.getMainThreadSupport(); mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null; backgroundPoster = new BackgroundPoster(this); asyncPoster = new AsyncPoster(this); indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0; subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes, builder.strictMethodVerification, builder.ignoreGeneratedIndex); logSubscriberExceptions = builder.logSubscriberExceptions; logNoSubscriberMessages = builder.logNoSubscriberMessages; sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent; sendNoSubscriberEvent = builder.sendNoSubscriberEvent; throwSubscriberException = builder.throwSubscriberException; eventInheritance = builder.eventInheritance; executorService = builder.executorService; } /** * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they * are no longer interested in receiving events. *

* Subscribers have event handling methods that must be annotated by {@link Subscribe}. * The {@link Subscribe} annotation also allows configuration like {@link * ThreadMode} and priority. */ public void register(Object subscriber) { if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) { // Crash if the user (developer) has not imported the Android compatibility library. throw new RuntimeException("It looks like you are using EventBus on Android, " + "make sure to add the \"eventbus\" Android library to your dependencies."); } Class subscriberClass = subscriber.getClass(); List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass); synchronized (this) { for (SubscriberMethod subscriberMethod : subscriberMethods) { subscribe(subscriber, subscriberMethod); } } } // Must be called in synchronized block private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) { Class eventType = subscriberMethod.eventType; Subscription newSubscription = new Subscription(subscriber, subscriberMethod); CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); subscriptionsByEventType.put(eventType, subscriptions); } else { if (subscriptions.contains(newSubscription)) { throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event " + eventType); } } int size = subscriptions.size(); for (int i = 0; i <= size; i++) { if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) { subscriptions.add(i, newSubscription); break; } } List> subscribedEvents = typesBySubscriber.get(subscriber); if (subscribedEvents == null) { subscribedEvents = new ArrayList<>(); typesBySubscriber.put(subscriber, subscribedEvents); } subscribedEvents.add(eventType); if (subscriberMethod.sticky) { if (eventInheritance) { // Existing sticky events of all subclasses of eventType have to be considered. // Note: Iterating over all events may be inefficient with lots of sticky events, // thus data structure should be changed to allow a more efficient lookup // (e.g. an additional map storing sub classes of super classes: Class -> List). Set, Object>> entries = stickyEvents.entrySet(); for (Map.Entry, Object> entry : entries) { Class candidateEventType = entry.getKey(); if (eventType.isAssignableFrom(candidateEventType)) { Object stickyEvent = entry.getValue(); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } else { Object stickyEvent = stickyEvents.get(eventType); checkPostStickyEventToSubscription(newSubscription, stickyEvent); } } } private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) { if (stickyEvent != null) { // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state) // --> Strange corner case, which we don't take care of here. postToSubscription(newSubscription, stickyEvent, isMainThread()); } } /** * Checks if the current thread is running in the main thread. * If there is no main thread support (e.g. non-Android), "true" is always returned. In that case MAIN thread * subscribers are always called in posting thread, and BACKGROUND subscribers are always called from a background * poster. */ private boolean isMainThread() { return mainThreadSupport == null || mainThreadSupport.isMainThread(); } public synchronized boolean isRegistered(Object subscriber) { return typesBySubscriber.containsKey(subscriber); } /** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */ private void unsubscribeByEventType(Object subscriber, Class eventType) { List subscriptions = subscriptionsByEventType.get(eventType); if (subscriptions != null) { int size = subscriptions.size(); for (int i = 0; i < size; i++) { Subscription subscription = subscriptions.get(i); if (subscription.subscriber == subscriber) { subscription.active = false; subscriptions.remove(i); i--; size--; } } } } /** Unregisters the given subscriber from all event classes. */ public synchronized void unregister(Object subscriber) { List> subscribedTypes = typesBySubscriber.get(subscriber); if (subscribedTypes != null) { for (Class eventType : subscribedTypes) { unsubscribeByEventType(subscriber, eventType); } typesBySubscriber.remove(subscriber); } else { logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass()); } } /** Posts the given event to the event bus. */ public void post(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); List eventQueue = postingState.eventQueue; eventQueue.add(event); if (!postingState.isPosting) { postingState.isMainThread = isMainThread(); postingState.isPosting = true; if (postingState.canceled) { throw new EventBusException("Internal error. Abort state was not reset"); } try { while (!eventQueue.isEmpty()) { postSingleEvent(eventQueue.remove(0), postingState); } } finally { postingState.isPosting = false; postingState.isMainThread = false; } } } /** * Called from a subscriber's event handling method, further event delivery will be canceled. Subsequent * subscribers * won't receive the event. Events are usually canceled by higher priority subscribers (see * {@link Subscribe#priority()}). Canceling is restricted to event handling methods running in posting thread * {@link ThreadMode#POSTING}. */ public void cancelEventDelivery(Object event) { PostingThreadState postingState = currentPostingThreadState.get(); if (!postingState.isPosting) { throw new EventBusException( "This method may only be called from inside event handling methods on the posting thread"); } else if (event == null) { throw new EventBusException("Event may not be null"); } else if (postingState.event != event) { throw new EventBusException("Only the currently handled event may be aborted"); } else if (postingState.subscription.subscriberMethod.threadMode != ThreadMode.POSTING) { throw new EventBusException(" event handlers may only abort the incoming event"); } postingState.canceled = true; } /** * Posts the given event to the event bus and holds on to the event (because it is sticky). The most recent sticky * event of an event's type is kept in memory for future access by subscribers using {@link Subscribe#sticky()}. */ public void postSticky(Object event) { synchronized (stickyEvents) { stickyEvents.put(event.getClass(), event); } // Should be posted after it is putted, in case the subscriber wants to remove immediately post(event); } /** * Gets the most recent sticky event for the given type. * * @see #postSticky(Object) */ public T getStickyEvent(Class eventType) { synchronized (stickyEvents) { return eventType.cast(stickyEvents.get(eventType)); } } /** * Remove and gets the recent sticky event for the given event type. * * @see #postSticky(Object) */ public T removeStickyEvent(Class eventType) { synchronized (stickyEvents) { return eventType.cast(stickyEvents.remove(eventType)); } } /** * Removes the sticky event if it equals to the given event. * * @return true if the events matched and the sticky event was removed. */ public boolean removeStickyEvent(Object event) { synchronized (stickyEvents) { Class eventType = event.getClass(); Object existingEvent = stickyEvents.get(eventType); if (event.equals(existingEvent)) { stickyEvents.remove(eventType); return true; } else { return false; } } } /** * Removes all sticky events. */ public void removeAllStickyEvents() { synchronized (stickyEvents) { stickyEvents.clear(); } } public boolean hasSubscriberForEvent(Class eventClass) { List> eventTypes = lookupAllEventTypes(eventClass); if (eventTypes != null) { int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class clazz = eventTypes.get(h); CopyOnWriteArrayList subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(clazz); } if (subscriptions != null && !subscriptions.isEmpty()) { return true; } } } return false; } private void postSingleEvent(Object event, PostingThreadState postingState) throws Error { Class eventClass = event.getClass(); boolean subscriptionFound = false; if (eventInheritance) { List> eventTypes = lookupAllEventTypes(eventClass); int countTypes = eventTypes.size(); for (int h = 0; h < countTypes; h++) { Class clazz = eventTypes.get(h); subscriptionFound |= postSingleEventForEventType(event, postingState, clazz); } } else { subscriptionFound = postSingleEventForEventType(event, postingState, eventClass); } if (!subscriptionFound) { if (logNoSubscriberMessages) { logger.log(Level.FINE, "No subscribers registered for event " + eventClass); } if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) { post(new NoSubscriberEvent(this, event)); } } } private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class eventClass) { CopyOnWriteArrayList subscriptions; synchronized (this) { subscriptions = subscriptionsByEventType.get(eventClass); } if (subscriptions != null && !subscriptions.isEmpty()) { for (Subscription subscription : subscriptions) { postingState.event = event; postingState.subscription = subscription; boolean aborted; try { postToSubscription(subscription, event, postingState.isMainThread); aborted = postingState.canceled; } finally { postingState.event = null; postingState.subscription = null; postingState.canceled = false; } if (aborted) { break; } } return true; } return false; } private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) { switch (subscription.subscriberMethod.threadMode) { case POSTING: invokeSubscriber(subscription, event); break; case MAIN: if (isMainThread) { invokeSubscriber(subscription, event); } else { mainThreadPoster.enqueue(subscription, event); } break; case MAIN_ORDERED: if (mainThreadPoster != null) { mainThreadPoster.enqueue(subscription, event); } else { // temporary: technically not correct as poster not decoupled from subscriber invokeSubscriber(subscription, event); } break; case BACKGROUND: if (isMainThread) { backgroundPoster.enqueue(subscription, event); } else { invokeSubscriber(subscription, event); } break; case ASYNC: asyncPoster.enqueue(subscription, event); break; default: throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode); } } /** Looks up all Class objects including super classes and interfaces. Should also work for interfaces. */ private static List> lookupAllEventTypes(Class eventClass) { synchronized (eventTypesCache) { List> eventTypes = eventTypesCache.get(eventClass); if (eventTypes == null) { eventTypes = new ArrayList<>(); Class clazz = eventClass; while (clazz != null) { eventTypes.add(clazz); addInterfaces(eventTypes, clazz.getInterfaces()); clazz = clazz.getSuperclass(); } eventTypesCache.put(eventClass, eventTypes); } return eventTypes; } } /** Recurses through super interfaces. */ static void addInterfaces(List> eventTypes, Class[] interfaces) { for (Class interfaceClass : interfaces) { if (!eventTypes.contains(interfaceClass)) { eventTypes.add(interfaceClass); addInterfaces(eventTypes, interfaceClass.getInterfaces()); } } } /** * Invokes the subscriber if the subscriptions is still active. Skipping subscriptions prevents race conditions * between {@link #unregister(Object)} and event delivery. Otherwise the event might be delivered after the * subscriber unregistered. This is particularly important for main thread delivery and registrations bound to the * live cycle of an Activity or Fragment. */ void invokeSubscriber(PendingPost pendingPost) { Object event = pendingPost.event; Subscription subscription = pendingPost.subscription; PendingPost.releasePendingPost(pendingPost); if (subscription.active) { invokeSubscriber(subscription, event); } } void invokeSubscriber(Subscription subscription, Object event) { try { subscription.subscriberMethod.method.invoke(subscription.subscriber, event); } catch (InvocationTargetException e) { handleSubscriberException(subscription, event, e.getCause()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } } private void handleSubscriberException(Subscription subscription, Object event, Throwable cause) { if (event instanceof SubscriberExceptionEvent) { if (logSubscriberExceptions) { // Don't send another SubscriberExceptionEvent to avoid infinite event recursion, just log logger.log(Level.SEVERE, "SubscriberExceptionEvent subscriber " + subscription.subscriber.getClass() + " threw an exception", cause); SubscriberExceptionEvent exEvent = (SubscriberExceptionEvent) event; logger.log(Level.SEVERE, "Initial event " + exEvent.causingEvent + " caused exception in " + exEvent.causingSubscriber, exEvent.throwable); } } else { if (throwSubscriberException) { throw new EventBusException("Invoking subscriber failed", cause); } if (logSubscriberExceptions) { logger.log(Level.SEVERE, "Could not dispatch event: " + event.getClass() + " to subscribing class " + subscription.subscriber.getClass(), cause); } if (sendSubscriberExceptionEvent) { SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event, subscription.subscriber); post(exEvent); } } } /** For ThreadLocal, much faster to set (and get multiple values). */ final static class PostingThreadState { final List eventQueue = new ArrayList<>(); boolean isPosting; boolean isMainThread; Subscription subscription; Object event; boolean canceled; } ExecutorService getExecutorService() { return executorService; } /** * For internal use only. */ public Logger getLogger() { return logger; } // Just an idea: we could provide a callback to post() to be notified, an alternative would be events, of course... /* public */interface PostCallback { void onPostCompleted(List exceptionEvents); } @Override public String toString() { return "EventBus[indexCount=" + indexCount + ", eventInheritance=" + eventInheritance + "]"; } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/EventBusBuilder.java ================================================ /* * Copyright (C) 2012-2020 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import org.greenrobot.eventbus.android.AndroidComponents; import org.greenrobot.eventbus.meta.SubscriberInfoIndex; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Creates EventBus instances with custom parameters and also allows to install a custom default EventBus instance. * Create a new builder using {@link EventBus#builder()}. */ @SuppressWarnings("unused") public class EventBusBuilder { private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool(); boolean logSubscriberExceptions = true; boolean logNoSubscriberMessages = true; boolean sendSubscriberExceptionEvent = true; boolean sendNoSubscriberEvent = true; boolean throwSubscriberException; boolean eventInheritance = true; boolean ignoreGeneratedIndex; boolean strictMethodVerification; ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE; List> skipMethodVerificationForClasses; List subscriberInfoIndexes; Logger logger; MainThreadSupport mainThreadSupport; EventBusBuilder() { } /** Default: true */ public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) { this.logSubscriberExceptions = logSubscriberExceptions; return this; } /** Default: true */ public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) { this.logNoSubscriberMessages = logNoSubscriberMessages; return this; } /** Default: true */ public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) { this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent; return this; } /** Default: true */ public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) { this.sendNoSubscriberEvent = sendNoSubscriberEvent; return this; } /** * Fails if an subscriber throws an exception (default: false). *

* Tip: Use this with BuildConfig.DEBUG to let the app crash in DEBUG mode (only). This way, you won't miss * exceptions during development. */ public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) { this.throwSubscriberException = throwSubscriberException; return this; } /** * By default, EventBus considers the event class hierarchy (subscribers to super classes will be notified). * Switching this feature off will improve posting of events. For simple event classes extending Object directly, * we measured a speed up of 20% for event posting. For more complex event hierarchies, the speed up should be * greater than 20%. *

* However, keep in mind that event posting usually consumes just a small proportion of CPU time inside an app, * unless it is posting at high rates, e.g. hundreds/thousands of events per second. */ public EventBusBuilder eventInheritance(boolean eventInheritance) { this.eventInheritance = eventInheritance; return this; } /** * Provide a custom thread pool to EventBus used for async and background event delivery. This is an advanced * setting to that can break things: ensure the given ExecutorService won't get stuck to avoid undefined behavior. */ public EventBusBuilder executorService(ExecutorService executorService) { this.executorService = executorService; return this; } /** * Method name verification is done for methods starting with onEvent to avoid typos; using this method you can * exclude subscriber classes from this check. Also disables checks for method modifiers (public, not static nor * abstract). */ public EventBusBuilder skipMethodVerificationFor(Class clazz) { if (skipMethodVerificationForClasses == null) { skipMethodVerificationForClasses = new ArrayList<>(); } skipMethodVerificationForClasses.add(clazz); return this; } /** Forces the use of reflection even if there's a generated index (default: false). */ public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) { this.ignoreGeneratedIndex = ignoreGeneratedIndex; return this; } /** Enables strict method verification (default: false). */ public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) { this.strictMethodVerification = strictMethodVerification; return this; } /** Adds an index generated by EventBus' annotation preprocessor. */ public EventBusBuilder addIndex(SubscriberInfoIndex index) { if (subscriberInfoIndexes == null) { subscriberInfoIndexes = new ArrayList<>(); } subscriberInfoIndexes.add(index); return this; } /** * Set a specific log handler for all EventBus logging. *

* By default, all logging is via {@code android.util.Log} on Android or System.out on JVM. */ public EventBusBuilder logger(Logger logger) { this.logger = logger; return this; } Logger getLogger() { if (logger != null) { return logger; } else { return Logger.Default.get(); } } MainThreadSupport getMainThreadSupport() { if (mainThreadSupport != null) { return mainThreadSupport; } else if (AndroidComponents.areAvailable()) { return AndroidComponents.get().defaultMainThreadSupport; } else { return null; } } /** * Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be * done only once before the first usage of the default EventBus. * * @throws EventBusException if there's already a default EventBus instance in place */ public EventBus installDefaultEventBus() { synchronized (EventBus.class) { if (EventBus.defaultInstance != null) { throw new EventBusException("Default instance already exists." + " It may be only set once before it's used the first time to ensure consistent behavior."); } EventBus.defaultInstance = build(); return EventBus.defaultInstance; } } /** Builds an EventBus based on the current configuration. */ public EventBus build() { return new EventBus(this); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/EventBusException.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * An {@link RuntimeException} thrown in cases something went wrong inside EventBus. * * @author Markus * */ public class EventBusException extends RuntimeException { private static final long serialVersionUID = -2912559384646531479L; public EventBusException(String detailMessage) { super(detailMessage); } public EventBusException(Throwable throwable) { super(throwable); } public EventBusException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/Logger.java ================================================ /* * Copyright (C) 2012-2020 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import org.greenrobot.eventbus.android.AndroidComponents; import java.util.logging.Level; public interface Logger { void log(Level level, String msg); void log(Level level, String msg, Throwable th); class JavaLogger implements Logger { protected final java.util.logging.Logger logger; public JavaLogger(String tag) { logger = java.util.logging.Logger.getLogger(tag); } @Override public void log(Level level, String msg) { // TODO Replace logged method with caller method logger.log(level, msg); } @Override public void log(Level level, String msg, Throwable th) { // TODO Replace logged method with caller method logger.log(level, msg, th); } } class SystemOutLogger implements Logger { @Override public void log(Level level, String msg) { System.out.println("[" + level + "] " + msg); } @Override public void log(Level level, String msg, Throwable th) { System.out.println("[" + level + "] " + msg); th.printStackTrace(System.out); } } class Default { public static Logger get() { if (AndroidComponents.areAvailable()) { return AndroidComponents.get().logger; } return new SystemOutLogger(); } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/MainThreadSupport.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * Interface to the "main" thread, which can be whatever you like. Typically on Android, Android's main thread is used. */ public interface MainThreadSupport { boolean isMainThread(); Poster createPoster(EventBus eventBus); } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/NoSubscriberEvent.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * This Event is posted by EventBus when no subscriber is found for a posted event. * * @author Markus */ public final class NoSubscriberEvent { /** The {@link EventBus} instance to with the original event was posted to. */ public final EventBus eventBus; /** The original event that could not be delivered to any subscriber. */ public final Object originalEvent; public NoSubscriberEvent(EventBus eventBus, Object originalEvent) { this.eventBus = eventBus; this.originalEvent = originalEvent; } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/PendingPost.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import java.util.ArrayList; import java.util.List; final class PendingPost { private final static List pendingPostPool = new ArrayList(); Object event; Subscription subscription; PendingPost next; private PendingPost(Object event, Subscription subscription) { this.event = event; this.subscription = subscription; } static PendingPost obtainPendingPost(Subscription subscription, Object event) { synchronized (pendingPostPool) { int size = pendingPostPool.size(); if (size > 0) { PendingPost pendingPost = pendingPostPool.remove(size - 1); pendingPost.event = event; pendingPost.subscription = subscription; pendingPost.next = null; return pendingPost; } } return new PendingPost(event, subscription); } static void releasePendingPost(PendingPost pendingPost) { pendingPost.event = null; pendingPost.subscription = null; pendingPost.next = null; synchronized (pendingPostPool) { // Don't let the pool grow indefinitely if (pendingPostPool.size() < 10000) { pendingPostPool.add(pendingPost); } } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/PendingPostQueue.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; final class PendingPostQueue { private PendingPost head; private PendingPost tail; synchronized void enqueue(PendingPost pendingPost) { if (pendingPost == null) { throw new NullPointerException("null cannot be enqueued"); } if (tail != null) { tail.next = pendingPost; tail = pendingPost; } else if (head == null) { head = tail = pendingPost; } else { throw new IllegalStateException("Head present, but no tail"); } notifyAll(); } synchronized PendingPost poll() { PendingPost pendingPost = head; if (head != null) { head = head.next; if (head == null) { tail = null; } } return pendingPost; } synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException { if (head == null) { wait(maxMillisToWait); } return poll(); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/Poster.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * Posts events. * * @author William Ferguson */ public interface Poster { /** * Enqueue an event to be posted for a particular subscription. * * @param subscription Subscription which will receive the event. * @param event Event that will be posted to subscribers. */ void enqueue(Subscription subscription, Object event); } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/Subscribe.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Subscribe { ThreadMode threadMode() default ThreadMode.POSTING; /** * If true, delivers the most recent sticky event (posted with * {@link EventBus#postSticky(Object)}) to this subscriber (if event available). */ boolean sticky() default false; /** Subscriber priority to influence the order of event delivery. * Within the same delivery thread ({@link ThreadMode}), higher priority subscribers will receive events before * others with a lower priority. The default priority is 0. Note: the priority does *NOT* affect the order of * delivery among subscribers with different {@link ThreadMode}s! */ int priority() default 0; } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/SubscriberExceptionEvent.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * This Event is posted by EventBus when an exception occurs inside a subscriber's event handling method. * * @author Markus */ public final class SubscriberExceptionEvent { /** The {@link EventBus} instance to with the original event was posted to. */ public final EventBus eventBus; /** The Throwable thrown by a subscriber. */ public final Throwable throwable; /** The original event that could not be delivered to any subscriber. */ public final Object causingEvent; /** The subscriber that threw the Throwable. */ public final Object causingSubscriber; public SubscriberExceptionEvent(EventBus eventBus, Throwable throwable, Object causingEvent, Object causingSubscriber) { this.eventBus = eventBus; this.throwable = throwable; this.causingEvent = causingEvent; this.causingSubscriber = causingSubscriber; } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/SubscriberMethod.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import java.lang.reflect.Method; /** Used internally by EventBus and generated subscriber indexes. */ public class SubscriberMethod { final Method method; final ThreadMode threadMode; final Class eventType; final int priority; final boolean sticky; /** Used for efficient comparison */ String methodString; public SubscriberMethod(Method method, Class eventType, ThreadMode threadMode, int priority, boolean sticky) { this.method = method; this.threadMode = threadMode; this.eventType = eventType; this.priority = priority; this.sticky = sticky; } @Override public boolean equals(Object other) { if (other == this) { return true; } else if (other instanceof SubscriberMethod) { checkMethodString(); SubscriberMethod otherSubscriberMethod = (SubscriberMethod)other; otherSubscriberMethod.checkMethodString(); // Don't use method.equals because of http://code.google.com/p/android/issues/detail?id=7811#c6 return methodString.equals(otherSubscriberMethod.methodString); } else { return false; } } private synchronized void checkMethodString() { if (methodString == null) { // Method.toString has more overhead, just take relevant parts of the method StringBuilder builder = new StringBuilder(64); builder.append(method.getDeclaringClass().getName()); builder.append('#').append(method.getName()); builder.append('(').append(eventType.getName()); methodString = builder.toString(); } } @Override public int hashCode() { return method.hashCode(); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/SubscriberMethodFinder.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; import org.greenrobot.eventbus.meta.SubscriberInfo; import org.greenrobot.eventbus.meta.SubscriberInfoIndex; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; class SubscriberMethodFinder { /* * In newer class files, compilers may add methods. Those are called bridge or synthetic methods. * EventBus must ignore both. There modifiers are not public but defined in the Java class file format: * http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1 */ private static final int BRIDGE = 0x40; private static final int SYNTHETIC = 0x1000; private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC; private static final Map, List> METHOD_CACHE = new ConcurrentHashMap<>(); private List subscriberInfoIndexes; private final boolean strictMethodVerification; private final boolean ignoreGeneratedIndex; private static final int POOL_SIZE = 4; private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE]; SubscriberMethodFinder(List subscriberInfoIndexes, boolean strictMethodVerification, boolean ignoreGeneratedIndex) { this.subscriberInfoIndexes = subscriberInfoIndexes; this.strictMethodVerification = strictMethodVerification; this.ignoreGeneratedIndex = ignoreGeneratedIndex; } List findSubscriberMethods(Class subscriberClass) { List subscriberMethods = METHOD_CACHE.get(subscriberClass); if (subscriberMethods != null) { return subscriberMethods; } if (ignoreGeneratedIndex) { subscriberMethods = findUsingReflection(subscriberClass); } else { subscriberMethods = findUsingInfo(subscriberClass); } if (subscriberMethods.isEmpty()) { throw new EventBusException("Subscriber " + subscriberClass + " and its super classes have no public methods with the @Subscribe annotation"); } else { METHOD_CACHE.put(subscriberClass, subscriberMethods); return subscriberMethods; } } private List findUsingInfo(Class subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { findState.subscriberInfo = getSubscriberInfo(findState); if (findState.subscriberInfo != null) { SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods(); for (SubscriberMethod subscriberMethod : array) { if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) { findState.subscriberMethods.add(subscriberMethod); } } } else { findUsingReflectionInSingleClass(findState); } findState.moveToSuperclass(); } return getMethodsAndRelease(findState); } private List getMethodsAndRelease(FindState findState) { List subscriberMethods = new ArrayList<>(findState.subscriberMethods); findState.recycle(); synchronized (FIND_STATE_POOL) { for (int i = 0; i < POOL_SIZE; i++) { if (FIND_STATE_POOL[i] == null) { FIND_STATE_POOL[i] = findState; break; } } } return subscriberMethods; } private FindState prepareFindState() { synchronized (FIND_STATE_POOL) { for (int i = 0; i < POOL_SIZE; i++) { FindState state = FIND_STATE_POOL[i]; if (state != null) { FIND_STATE_POOL[i] = null; return state; } } } return new FindState(); } private SubscriberInfo getSubscriberInfo(FindState findState) { if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo(); if (findState.clazz == superclassInfo.getSubscriberClass()) { return superclassInfo; } } if (subscriberInfoIndexes != null) { for (SubscriberInfoIndex index : subscriberInfoIndexes) { SubscriberInfo info = index.getSubscriberInfo(findState.clazz); if (info != null) { return info; } } } return null; } private List findUsingReflection(Class subscriberClass) { FindState findState = prepareFindState(); findState.initForSubscriber(subscriberClass); while (findState.clazz != null) { findUsingReflectionInSingleClass(findState); findState.moveToSuperclass(); } return getMethodsAndRelease(findState); } private void findUsingReflectionInSingleClass(FindState findState) { Method[] methods; try { // This is faster than getMethods, especially when subscribers are fat classes like Activities methods = findState.clazz.getDeclaredMethods(); } catch (Throwable th) { // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149 try { methods = findState.clazz.getMethods(); } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad... String msg = "Could not inspect methods of " + findState.clazz.getName(); if (ignoreGeneratedIndex) { msg += ". Please consider using EventBus annotation processor to avoid reflection."; } else { msg += ". Please make this class visible to EventBus annotation processor to avoid reflection."; } throw new EventBusException(msg, error); } findState.skipSuperClasses = true; } for (Method method : methods) { int modifiers = method.getModifiers(); if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) { Class[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class); if (subscribeAnnotation != null) { Class eventType = parameterTypes[0]; if (findState.checkAdd(method, eventType)) { ThreadMode threadMode = subscribeAnnotation.threadMode(); findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode, subscribeAnnotation.priority(), subscribeAnnotation.sticky())); } } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException("@Subscribe method " + methodName + "must have exactly 1 parameter but has " + parameterTypes.length); } } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) { String methodName = method.getDeclaringClass().getName() + "." + method.getName(); throw new EventBusException(methodName + " is a illegal @Subscribe method: must be public, non-static, and non-abstract"); } } } static void clearCaches() { METHOD_CACHE.clear(); } static class FindState { final List subscriberMethods = new ArrayList<>(); final Map anyMethodByEventType = new HashMap<>(); final Map subscriberClassByMethodKey = new HashMap<>(); final StringBuilder methodKeyBuilder = new StringBuilder(128); Class subscriberClass; Class clazz; boolean skipSuperClasses; SubscriberInfo subscriberInfo; void initForSubscriber(Class subscriberClass) { this.subscriberClass = clazz = subscriberClass; skipSuperClasses = false; subscriberInfo = null; } void recycle() { subscriberMethods.clear(); anyMethodByEventType.clear(); subscriberClassByMethodKey.clear(); methodKeyBuilder.setLength(0); subscriberClass = null; clazz = null; skipSuperClasses = false; subscriberInfo = null; } boolean checkAdd(Method method, Class eventType) { // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required. // Usually a subscriber doesn't have methods listening to the same event type. Object existing = anyMethodByEventType.put(eventType, method); if (existing == null) { return true; } else { if (existing instanceof Method) { if (!checkAddWithMethodSignature((Method) existing, eventType)) { // Paranoia check throw new IllegalStateException(); } // Put any non-Method object to "consume" the existing Method anyMethodByEventType.put(eventType, this); } return checkAddWithMethodSignature(method, eventType); } } private boolean checkAddWithMethodSignature(Method method, Class eventType) { methodKeyBuilder.setLength(0); methodKeyBuilder.append(method.getName()); methodKeyBuilder.append('>').append(eventType.getName()); String methodKey = methodKeyBuilder.toString(); Class methodClass = method.getDeclaringClass(); Class methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass); if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) { // Only add if not already found in a sub class return true; } else { // Revert the put, old class is further down the class hierarchy subscriberClassByMethodKey.put(methodKey, methodClassOld); return false; } } void moveToSuperclass() { if (skipSuperClasses) { clazz = null; } else { clazz = clazz.getSuperclass(); String clazzName = clazz.getName(); // Skip system classes, this degrades performance. // Also we might avoid some ClassNotFoundException (see FAQ for background). if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) { clazz = null; } } } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/Subscription.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; final class Subscription { final Object subscriber; final SubscriberMethod subscriberMethod; /** * Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery * {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions. */ volatile boolean active; Subscription(Object subscriber, SubscriberMethod subscriberMethod) { this.subscriber = subscriber; this.subscriberMethod = subscriberMethod; active = true; } @Override public boolean equals(Object other) { if (other instanceof Subscription) { Subscription otherSubscription = (Subscription) other; return subscriber == otherSubscription.subscriber && subscriberMethod.equals(otherSubscription.subscriberMethod); } else { return false; } } @Override public int hashCode() { return subscriber.hashCode() + subscriberMethod.methodString.hashCode(); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/ThreadMode.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus; /** * Each subscriber method has a thread mode, which determines in which thread the method is to be called by EventBus. * EventBus takes care of threading independently of the posting thread. * * @see EventBus#register(Object) */ public enum ThreadMode { /** * This is the default. Subscriber will be called directly in the same thread, which is posting the event. Event delivery * implies the least overhead because it avoids thread switching completely. Thus, this is the recommended mode for * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread. */ POSTING, /** * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread. *

* If not on Android, behaves the same as {@link #POSTING}. */ MAIN, /** * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN}, * the event will always be queued for delivery. This ensures that the post call is non-blocking. *

* If not on Android, behaves the same as {@link #POSTING}. */ MAIN_ORDERED, /** * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to * return quickly to avoid blocking the background thread. *

* If not on Android, always uses a background thread. */ BACKGROUND, /** * Subscriber will be called in a separate thread. This is always independent of the posting thread and the * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number * of long-running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications. */ ASYNC } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/android/AndroidComponents.java ================================================ package org.greenrobot.eventbus.android; import org.greenrobot.eventbus.Logger; import org.greenrobot.eventbus.MainThreadSupport; public abstract class AndroidComponents { private static final AndroidComponents implementation; static { implementation = AndroidDependenciesDetector.isAndroidSDKAvailable() ? AndroidDependenciesDetector.instantiateAndroidComponents() : null; } public static boolean areAvailable() { return implementation != null; } public static AndroidComponents get() { return implementation; } public final Logger logger; public final MainThreadSupport defaultMainThreadSupport; public AndroidComponents(Logger logger, MainThreadSupport defaultMainThreadSupport) { this.logger = logger; this.defaultMainThreadSupport = defaultMainThreadSupport; } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/android/AndroidDependenciesDetector.java ================================================ package org.greenrobot.eventbus.android; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @SuppressWarnings("TryWithIdenticalCatches") public class AndroidDependenciesDetector { public static boolean isAndroidSDKAvailable() { try { Class looperClass = Class.forName("android.os.Looper"); Method getMainLooper = looperClass.getDeclaredMethod("getMainLooper"); Object mainLooper = getMainLooper.invoke(null); return mainLooper != null; } catch (ClassNotFoundException ignored) {} catch (NoSuchMethodException ignored) {} catch (IllegalAccessException ignored) {} catch (InvocationTargetException ignored) {} return false; } private static final String ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME = "org.greenrobot.eventbus.android.AndroidComponentsImpl"; public static boolean areAndroidComponentsAvailable() { try { Class.forName(ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME); return true; } catch (ClassNotFoundException ex) { return false; } } public static AndroidComponents instantiateAndroidComponents() { try { Class impl = Class.forName(ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME); return (AndroidComponents) impl.getConstructor().newInstance(); } catch (Throwable ex) { return null; } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/meta/AbstractSubscriberInfo.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.meta; import org.greenrobot.eventbus.EventBusException; import org.greenrobot.eventbus.SubscriberMethod; import org.greenrobot.eventbus.ThreadMode; import java.lang.reflect.Method; /** Base class for generated subscriber meta info classes created by annotation processing. */ public abstract class AbstractSubscriberInfo implements SubscriberInfo { private final Class subscriberClass; private final Class superSubscriberInfoClass; private final boolean shouldCheckSuperclass; protected AbstractSubscriberInfo(Class subscriberClass, Class superSubscriberInfoClass, boolean shouldCheckSuperclass) { this.subscriberClass = subscriberClass; this.superSubscriberInfoClass = superSubscriberInfoClass; this.shouldCheckSuperclass = shouldCheckSuperclass; } @Override public Class getSubscriberClass() { return subscriberClass; } @Override public SubscriberInfo getSuperSubscriberInfo() { if(superSubscriberInfoClass == null) { return null; } try { return superSubscriberInfoClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } @Override public boolean shouldCheckSuperclass() { return shouldCheckSuperclass; } protected SubscriberMethod createSubscriberMethod(String methodName, Class eventType) { return createSubscriberMethod(methodName, eventType, ThreadMode.POSTING, 0, false); } protected SubscriberMethod createSubscriberMethod(String methodName, Class eventType, ThreadMode threadMode) { return createSubscriberMethod(methodName, eventType, threadMode, 0, false); } protected SubscriberMethod createSubscriberMethod(String methodName, Class eventType, ThreadMode threadMode, int priority, boolean sticky) { try { Method method = subscriberClass.getDeclaredMethod(methodName, eventType); return new SubscriberMethod(method, eventType, threadMode, priority, sticky); } catch (NoSuchMethodException e) { throw new EventBusException("Could not find subscriber method in " + subscriberClass + ". Maybe a missing ProGuard rule?", e); } } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/meta/SimpleSubscriberInfo.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.meta; import org.greenrobot.eventbus.SubscriberMethod; /** * Uses {@link SubscriberMethodInfo} objects to create {@link org.greenrobot.eventbus.SubscriberMethod} objects on demand. */ public class SimpleSubscriberInfo extends AbstractSubscriberInfo { private final SubscriberMethodInfo[] methodInfos; public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) { super(subscriberClass, null, shouldCheckSuperclass); this.methodInfos = methodInfos; } @Override public synchronized SubscriberMethod[] getSubscriberMethods() { int length = methodInfos.length; SubscriberMethod[] methods = new SubscriberMethod[length]; for (int i = 0; i < length; i++) { SubscriberMethodInfo info = methodInfos[i]; methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode, info.priority, info.sticky); } return methods; } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/meta/SubscriberInfo.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.meta; import org.greenrobot.eventbus.SubscriberMethod; /** Base class for generated index classes created by annotation processing. */ public interface SubscriberInfo { Class getSubscriberClass(); SubscriberMethod[] getSubscriberMethods(); SubscriberInfo getSuperSubscriberInfo(); boolean shouldCheckSuperclass(); } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/meta/SubscriberInfoIndex.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.meta; /** * Interface for generated indexes. */ public interface SubscriberInfoIndex { SubscriberInfo getSubscriberInfo(Class subscriberClass); } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/meta/SubscriberMethodInfo.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.meta; import org.greenrobot.eventbus.ThreadMode; public class SubscriberMethodInfo { final String methodName; final ThreadMode threadMode; final Class eventType; final int priority; final boolean sticky; public SubscriberMethodInfo(String methodName, Class eventType, ThreadMode threadMode, int priority, boolean sticky) { this.methodName = methodName; this.threadMode = threadMode; this.eventType = eventType; this.priority = priority; this.sticky = sticky; } public SubscriberMethodInfo(String methodName, Class eventType) { this(methodName, eventType, ThreadMode.POSTING, 0, false); } public SubscriberMethodInfo(String methodName, Class eventType, ThreadMode threadMode) { this(methodName, eventType, threadMode, 0, false); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/util/AsyncExecutor.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.util; import org.greenrobot.eventbus.EventBus; import java.lang.reflect.Constructor; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.logging.Level; /** * Executes an {@link RunnableEx} using a thread pool. Thrown exceptions are propagated by posting failure events. * By default, uses {@link ThrowableFailureEvent}. *

* Set a custom event type using {@link Builder#failureEventType(Class)}. * The failure event class must have a constructor with one parameter of type {@link Throwable}. * If using ProGuard or R8 make sure the constructor of the failure event class is kept, it is accessed via reflection. * E.g. add a rule like *

 * -keepclassmembers class com.example.CustomThrowableFailureEvent {
 *     <init>(java.lang.Throwable);
 * }
 * 
*/ public class AsyncExecutor { public static class Builder { private Executor threadPool; private Class failureEventType; private EventBus eventBus; private Builder() { } public Builder threadPool(Executor threadPool) { this.threadPool = threadPool; return this; } public Builder failureEventType(Class failureEventType) { this.failureEventType = failureEventType; return this; } public Builder eventBus(EventBus eventBus) { this.eventBus = eventBus; return this; } public AsyncExecutor build() { return buildForScope(null); } public AsyncExecutor buildForScope(Object executionContext) { if (eventBus == null) { eventBus = EventBus.getDefault(); } if (threadPool == null) { threadPool = Executors.newCachedThreadPool(); } if (failureEventType == null) { failureEventType = ThrowableFailureEvent.class; } return new AsyncExecutor(threadPool, eventBus, failureEventType, executionContext); } } /** Like {@link Runnable}, but the run method may throw an exception. */ public interface RunnableEx { void run() throws Exception; } public static Builder builder() { return new Builder(); } public static AsyncExecutor create() { return new Builder().build(); } private final Executor threadPool; private final Constructor failureEventConstructor; private final EventBus eventBus; private final Object scope; private AsyncExecutor(Executor threadPool, EventBus eventBus, Class failureEventType, Object scope) { this.threadPool = threadPool; this.eventBus = eventBus; this.scope = scope; try { failureEventConstructor = failureEventType.getConstructor(Throwable.class); } catch (NoSuchMethodException e) { throw new RuntimeException( "Failure event class must have a constructor with one parameter of type Throwable", e); } } /** Posts an failure event if the given {@link RunnableEx} throws an Exception. */ public void execute(final RunnableEx runnable) { threadPool.execute(() -> { try { runnable.run(); } catch (Exception e) { Object event; try { event = failureEventConstructor.newInstance(e); } catch (Exception e1) { eventBus.getLogger().log(Level.SEVERE, "Original exception:", e); throw new RuntimeException("Could not create failure event", e1); } if (event instanceof HasExecutionScope) { ((HasExecutionScope) event).setExecutionScope(scope); } eventBus.post(event); } }); } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/util/ExceptionToResourceMapping.java ================================================ /* * Copyright (C) 2012-2020 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.util; import org.greenrobot.eventbus.Logger; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.logging.Level; /** * Maps throwables to texts for error dialogs. Use Config to configure the mapping. * * @author Markus */ public class ExceptionToResourceMapping { public final Map, Integer> throwableToMsgIdMap; public ExceptionToResourceMapping() { throwableToMsgIdMap = new HashMap<>(); } /** Looks at the exception and its causes trying to find an ID. */ public Integer mapThrowable(final Throwable throwable) { Throwable throwableToCheck = throwable; int depthToGo = 20; while (true) { Integer resId = mapThrowableFlat(throwableToCheck); if (resId != null) { return resId; } else { throwableToCheck = throwableToCheck.getCause(); depthToGo--; if (depthToGo <= 0 || throwableToCheck == throwable || throwableToCheck == null) { Logger logger = Logger.Default.get(); // No EventBus instance here logger.log(Level.FINE, "No specific message resource ID found for " + throwable); // return config.defaultErrorMsgId; return null; } } } } /** Mapping without checking the cause (done in mapThrowable). */ protected Integer mapThrowableFlat(Throwable throwable) { Class throwableClass = throwable.getClass(); Integer resId = throwableToMsgIdMap.get(throwableClass); if (resId == null) { Class closestClass = null; Set, Integer>> mappings = throwableToMsgIdMap.entrySet(); for (Entry, Integer> mapping : mappings) { Class candidate = mapping.getKey(); if (candidate.isAssignableFrom(throwableClass)) { if (closestClass == null || closestClass.isAssignableFrom(candidate)) { closestClass = candidate; resId = mapping.getValue(); } } } } return resId; } public ExceptionToResourceMapping addMapping(Class clazz, int msgId) { throwableToMsgIdMap.put(clazz, msgId); return this; } } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/util/HasExecutionScope.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.util; public interface HasExecutionScope { Object getExecutionScope(); void setExecutionScope(Object executionScope); } ================================================ FILE: EventBus/src/org/greenrobot/eventbus/util/ThrowableFailureEvent.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.util; /** * A generic failure event, which can be used by apps to propagate thrown exceptions. * Used as default failure event by {@link AsyncExecutor}. */ public class ThrowableFailureEvent implements HasExecutionScope { protected final Throwable throwable; protected final boolean suppressErrorUi; private Object executionContext; public ThrowableFailureEvent(Throwable throwable) { this.throwable = throwable; suppressErrorUi = false; } /** * @param suppressErrorUi * true indicates to the receiver that no error UI (e.g. dialog) should now displayed. */ public ThrowableFailureEvent(Throwable throwable, boolean suppressErrorUi) { this.throwable = throwable; this.suppressErrorUi = suppressErrorUi; } public Throwable getThrowable() { return throwable; } public boolean isSuppressErrorUi() { return suppressErrorUi; } public Object getExecutionScope() { return executionContext; } public void setExecutionScope(Object executionContext) { this.executionContext = executionContext; } } ================================================ FILE: EventBusAnnotationProcessor/build.gradle ================================================ apply plugin: 'java' group = rootProject.group version = rootProject.version java.sourceCompatibility = JavaVersion.VERSION_1_8 java.targetCompatibility = JavaVersion.VERSION_1_8 dependencies { implementation project(':eventbus-java') implementation 'de.greenrobot:java-common:2.3.1' // Generates the required META-INF descriptor to make the processor incremental. def incap = '0.2' compileOnly "net.ltgt.gradle.incap:incap:$incap" annotationProcessor "net.ltgt.gradle.incap:incap-processor:$incap" } sourceSets { main { java { srcDir 'src' } resources { srcDir 'res' } } } javadoc { title = "EventBus Annotation Processor ${version} API" options.bottom = 'Available under the Apache License, Version 2.0 - Copyright © 2015-2020 greenrobot.org. All Rights Reserved.' } task javadocJar(type: Jar, dependsOn: javadoc) { archiveClassifier.set("javadoc") from 'build/docs/javadoc' } task sourcesJar(type: Jar) { archiveClassifier.set("sources") from sourceSets.main.allSource } apply from: rootProject.file("gradle/publish.gradle") // Set project-specific properties afterEvaluate { publishing.publications { mavenJava(MavenPublication) { artifactId = "eventbus-annotation-processor" from components.java artifact javadocJar artifact sourcesJar pom { name = "EventBus Annotation Processor" description = "Precompiler for EventBus Annotations." packaging = "jar" } } } } ================================================ FILE: EventBusAnnotationProcessor/res/META-INF/services/javax.annotation.processing.Processor ================================================ org.greenrobot.eventbus.annotationprocessor.EventBusAnnotationProcessor ================================================ FILE: EventBusAnnotationProcessor/src/org/greenrobot/eventbus/annotationprocessor/EventBusAnnotationProcessor.java ================================================ /* * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.greenrobot.eventbus.annotationprocessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; import java.io.BufferedWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; import de.greenrobot.common.ListMap; import static net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.AGGREGATING; /** * Is an aggregating processor as it writes a single file, the subscriber index file, * based on found elements with the @Subscriber annotation. */ @SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe") @SupportedOptions(value = {"eventBusIndex", "verbose"}) @IncrementalAnnotationProcessor(AGGREGATING) public class EventBusAnnotationProcessor extends AbstractProcessor { public static final String OPTION_EVENT_BUS_INDEX = "eventBusIndex"; public static final String OPTION_VERBOSE = "verbose"; /** Found subscriber methods for a class (without superclasses). */ private final ListMap methodsByClass = new ListMap<>(); private final Set classesToSkip = new HashSet<>(); private boolean writerRoundDone; private int round; private boolean verbose; @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latest(); } @Override public boolean process(Set annotations, RoundEnvironment env) { Messager messager = processingEnv.getMessager(); try { String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX); if (index == null) { messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX + " passed to annotation processor"); return false; } verbose = Boolean.parseBoolean(processingEnv.getOptions().get(OPTION_VERBOSE)); int lastPeriod = index.lastIndexOf('.'); String indexPackage = lastPeriod != -1 ? index.substring(0, lastPeriod) : null; round++; if (verbose) { messager.printMessage(Diagnostic.Kind.NOTE, "Processing round " + round + ", new annotations: " + !annotations.isEmpty() + ", processingOver: " + env.processingOver()); } if (env.processingOver()) { if (!annotations.isEmpty()) { messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected processing state: annotations still available after processing over"); return false; } } if (annotations.isEmpty()) { return false; } if (writerRoundDone) { messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected processing state: annotations still available after writing."); } collectSubscribers(annotations, env, messager); checkForSubscribersToSkip(messager, indexPackage); if (!methodsByClass.isEmpty()) { createInfoIndexFile(index); } else { messager.printMessage(Diagnostic.Kind.WARNING, "No @Subscribe annotations found"); } writerRoundDone = true; } catch (RuntimeException e) { // IntelliJ does not handle exceptions nicely, so log and print a message e.printStackTrace(); messager.printMessage(Diagnostic.Kind.ERROR, "Unexpected error in EventBusAnnotationProcessor: " + e); } return true; } private void collectSubscribers(Set annotations, RoundEnvironment env, Messager messager) { for (TypeElement annotation : annotations) { Set elements = env.getElementsAnnotatedWith(annotation); for (Element element : elements) { if (element instanceof ExecutableElement) { ExecutableElement method = (ExecutableElement) element; if (checkHasNoErrors(method, messager)) { TypeElement classElement = (TypeElement) method.getEnclosingElement(); methodsByClass.putElement(classElement, method); } } else { messager.printMessage(Diagnostic.Kind.ERROR, "@Subscribe is only valid for methods", element); } } } } private boolean checkHasNoErrors(ExecutableElement element, Messager messager) { if (element.getModifiers().contains(Modifier.STATIC)) { messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must not be static", element); return false; } if (!element.getModifiers().contains(Modifier.PUBLIC)) { messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must be public", element); return false; } List parameters = ((ExecutableElement) element).getParameters(); if (parameters.size() != 1) { messager.printMessage(Diagnostic.Kind.ERROR, "Subscriber method must have exactly 1 parameter", element); return false; } return true; } /** * Subscriber classes should be skipped if their class or any involved event class are not visible to the index. */ private void checkForSubscribersToSkip(Messager messager, String myPackage) { for (TypeElement skipCandidate : methodsByClass.keySet()) { TypeElement subscriberClass = skipCandidate; while (subscriberClass != null) { if (!isVisible(myPackage, subscriberClass)) { boolean added = classesToSkip.add(skipCandidate); if (added) { String msg; if (subscriberClass.equals(skipCandidate)) { msg = "Falling back to reflection because class is not public"; } else { msg = "Falling back to reflection because " + skipCandidate + " has a non-public super class"; } messager.printMessage(Diagnostic.Kind.NOTE, msg, subscriberClass); } break; } List methods = methodsByClass.get(subscriberClass); if (methods != null) { for (ExecutableElement method : methods) { String skipReason = null; VariableElement param = method.getParameters().get(0); TypeMirror typeMirror = getParamTypeMirror(param, messager); if (!(typeMirror instanceof DeclaredType) || !(((DeclaredType) typeMirror).asElement() instanceof TypeElement)) { skipReason = "event type cannot be processed"; } if (skipReason == null) { TypeElement eventTypeElement = (TypeElement) ((DeclaredType) typeMirror).asElement(); if (!isVisible(myPackage, eventTypeElement)) { skipReason = "event type is not public"; } } if (skipReason != null) { boolean added = classesToSkip.add(skipCandidate); if (added) { String msg = "Falling back to reflection because " + skipReason; if (!subscriberClass.equals(skipCandidate)) { msg += " (found in super class for " + skipCandidate + ")"; } messager.printMessage(Diagnostic.Kind.NOTE, msg, param); } break; } } } subscriberClass = getSuperclass(subscriberClass); } } } private TypeMirror getParamTypeMirror(VariableElement param, Messager messager) { TypeMirror typeMirror = param.asType(); // Check for generic type if (typeMirror instanceof TypeVariable) { TypeMirror upperBound = ((TypeVariable) typeMirror).getUpperBound(); if (upperBound instanceof DeclaredType) { if (messager != null) { messager.printMessage(Diagnostic.Kind.NOTE, "Using upper bound type " + upperBound + " for generic parameter", param); } typeMirror = upperBound; } } return typeMirror; } private TypeElement getSuperclass(TypeElement type) { if (type.getSuperclass().getKind() == TypeKind.DECLARED) { TypeElement superclass = (TypeElement) processingEnv.getTypeUtils().asElement(type.getSuperclass()); String name = superclass.getQualifiedName().toString(); if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) { // Skip system classes, this just degrades performance return null; } else { return superclass; } } else { return null; } } private String getClassString(TypeElement typeElement, String myPackage) { PackageElement packageElement = getPackageElement(typeElement); String packageString = packageElement.getQualifiedName().toString(); String className = typeElement.getQualifiedName().toString(); if (packageString != null && !packageString.isEmpty()) { if (packageString.equals(myPackage)) { className = cutPackage(myPackage, className); } else if (packageString.equals("java.lang")) { className = typeElement.getSimpleName().toString(); } } return className; } private String cutPackage(String paket, String className) { if (className.startsWith(paket + '.')) { // Don't use TypeElement.getSimpleName, it doesn't work for us with inner classes return className.substring(paket.length() + 1); } else { // Paranoia throw new IllegalStateException("Mismatching " + paket + " vs. " + className); } } private PackageElement getPackageElement(TypeElement subscriberClass) { Element candidate = subscriberClass.getEnclosingElement(); while (!(candidate instanceof PackageElement)) { candidate = candidate.getEnclosingElement(); } return (PackageElement) candidate; } private void writeCreateSubscriberMethods(BufferedWriter writer, List methods, String callPrefix, String myPackage) throws IOException { for (ExecutableElement method : methods) { List parameters = method.getParameters(); TypeMirror paramType = getParamTypeMirror(parameters.get(0), null); TypeElement paramElement = (TypeElement) processingEnv.getTypeUtils().asElement(paramType); String methodName = method.getSimpleName().toString(); String eventClass = getClassString(paramElement, myPackage) + ".class"; Subscribe subscribe = method.getAnnotation(Subscribe.class); List parts = new ArrayList<>(); parts.add(callPrefix + "(\"" + methodName + "\","); String lineEnd = "),"; if (subscribe.priority() == 0 && !subscribe.sticky()) { if (subscribe.threadMode() == ThreadMode.POSTING) { parts.add(eventClass + lineEnd); } else { parts.add(eventClass + ","); parts.add("ThreadMode." + subscribe.threadMode().name() + lineEnd); } } else { parts.add(eventClass + ","); parts.add("ThreadMode." + subscribe.threadMode().name() + ","); parts.add(subscribe.priority() + ","); parts.add(subscribe.sticky() + lineEnd); } writeLine(writer, 3, parts.toArray(new String[parts.size()])); if (verbose) { processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Indexed @Subscribe at " + method.getEnclosingElement().getSimpleName() + "." + methodName + "(" + paramElement.getSimpleName() + ")"); } } } private void createInfoIndexFile(String index) { BufferedWriter writer = null; try { JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index); int period = index.lastIndexOf('.'); String myPackage = period > 0 ? index.substring(0, period) : null; String clazz = index.substring(period + 1); writer = new BufferedWriter(sourceFile.openWriter()); if (myPackage != null) { writer.write("package " + myPackage + ";\n\n"); } writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n"); writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n"); writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n"); writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n"); writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n"); writer.write("import java.util.HashMap;\n"); writer.write("import java.util.Map;\n\n"); writer.write("/** This class is generated by EventBus, do not edit. */\n"); writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n"); writer.write(" private static final Map, SubscriberInfo> SUBSCRIBER_INDEX;\n\n"); writer.write(" static {\n"); writer.write(" SUBSCRIBER_INDEX = new HashMap, SubscriberInfo>();\n\n"); writeIndexLines(writer, myPackage); writer.write(" }\n\n"); writer.write(" private static void putIndex(SubscriberInfo info) {\n"); writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n"); writer.write(" }\n\n"); writer.write(" @Override\n"); writer.write(" public SubscriberInfo getSubscriberInfo(Class subscriberClass) {\n"); writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n"); writer.write(" if (info != null) {\n"); writer.write(" return info;\n"); writer.write(" } else {\n"); writer.write(" return null;\n"); writer.write(" }\n"); writer.write(" }\n"); writer.write("}\n"); } catch (IOException e) { throw new RuntimeException("Could not write source for " + index, e); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { //Silent } } } } private void writeIndexLines(BufferedWriter writer, String myPackage) throws IOException { for (TypeElement subscriberTypeElement : methodsByClass.keySet()) { if (classesToSkip.contains(subscriberTypeElement)) { continue; } String subscriberClass = getClassString(subscriberTypeElement, myPackage); if (isVisible(myPackage, subscriberTypeElement)) { writeLine(writer, 2, "putIndex(new SimpleSubscriberInfo(" + subscriberClass + ".class,", "true,", "new SubscriberMethodInfo[] {"); List methods = methodsByClass.get(subscriberTypeElement); writeCreateSubscriberMethods(writer, methods, "new SubscriberMethodInfo", myPackage); writer.write(" }));\n\n"); } else { writer.write(" // Subscriber not visible to index: " + subscriberClass + "\n"); } } } private boolean isVisible(String myPackage, TypeElement typeElement) { Set modifiers = typeElement.getModifiers(); boolean visible; if (modifiers.contains(Modifier.PUBLIC)) { visible = true; } else if (modifiers.contains(Modifier.PRIVATE) || modifiers.contains(Modifier.PROTECTED)) { visible = false; } else { String subscriberPackage = getPackageElement(typeElement).getQualifiedName().toString(); if (myPackage == null) { visible = subscriberPackage.length() == 0; } else { visible = myPackage.equals(subscriberPackage); } } return visible; } private void writeLine(BufferedWriter writer, int indentLevel, String... parts) throws IOException { writeLine(writer, indentLevel, 2, parts); } private void writeLine(BufferedWriter writer, int indentLevel, int indentLevelIncrease, String... parts) throws IOException { writeIndent(writer, indentLevel); int len = indentLevel * 4; for (int i = 0; i < parts.length; i++) { String part = parts[i]; if (i != 0) { if (len + part.length() > 118) { writer.write("\n"); if (indentLevel < 12) { indentLevel += indentLevelIncrease; } writeIndent(writer, indentLevel); len = indentLevel * 4; } else { writer.write(" "); } } writer.write(part); len += part.length(); } writer.write("\n"); } private void writeIndent(BufferedWriter writer, int indentLevel) throws IOException { for (int i = 0; i < indentLevel; i++) { writer.write(" "); } } } ================================================ FILE: EventBusPerformance/.gitignore ================================================ /bin /gen ================================================ FILE: EventBusPerformance/AndroidManifest.xml ================================================ ================================================ FILE: EventBusPerformance/build.gradle ================================================ buildscript { repositories { google() mavenCentral() } dependencies { // Note: IntelliJ IDEA 2021.1 only supports up to version 4.1 classpath 'com.android.tools.build:gradle:4.1.3' } } apply plugin: 'com.android.application' dependencies { implementation project(':eventbus-android') annotationProcessor project(':eventbus-annotation-processor') implementation 'com.squareup:otto:1.3.8' } android { compileSdkVersion _compileSdkVersion sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] res.srcDirs = ['res'] } } defaultConfig { minSdkVersion 7 targetSdkVersion 26 versionCode 1 versionName "2.0.0" javaCompileOptions { annotationProcessorOptions { arguments = [eventBusIndex: 'org.greenrobot.eventbusperf.MyEventBusIndex'] } } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ================================================ FILE: EventBusPerformance/proguard-project.txt ================================================ # To enable ProGuard in your project, edit project.properties # to define the proguard.config property as described in that file. # # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in ${sdk.dir}/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the ProGuard # include property in project.properties. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: EventBusPerformance/project.properties ================================================ # This file is automatically generated by Android Tools. # Do not modify this file -- YOUR CHANGES WILL BE ERASED! # # This file must be checked in Version Control Systems. # # To customize properties used by the Ant build system edit # "ant.properties", and override values to adapt the script to your # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): #proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt # Project target. target=android-17 ================================================ FILE: EventBusPerformance/res/layout/activity_runtests.xml ================================================