content: /*
   * 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<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
  
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    private final Map<Class<?>, Object> stickyEvents;
  
    private final ThreadLocal<PostingThreadState> currentPostingThreadState =
        new ThreadLocal<PostingThreadState>() {
          @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.
     *
     * <p>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<SubscriberMethod> 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<Subscription> 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<Class<?>> 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<Class>).
          Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
          for (Map.Entry<Class<?>, 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<Subscription> 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<Class<?>> 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<Object> 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> T getStickyEvent(Class<T> 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> T removeStickyEvent(Class<T> 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<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
      if (eventTypes != null) {
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
          Class<?> clazz = eventTypes.get(h);
          CopyOnWriteArrayList<Subscription> 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<Class<?>> 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<Subscription> 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<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
      synchronized (eventTypesCache) {
        List<Class<?>> 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<Class<?>> 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). */
    static final class PostingThreadState {
      final List<Object> 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<SubscriberExceptionEvent> exceptionEvents);
    }
  
    @Override
    public String toString() {
      return "EventBus[indexCount=" + indexCount + ", eventInheritance=" + eventInheritance + "]";
    }
  }
  