content: /*
   * 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<Class<?>, List<SubscriberMethod>> METHOD_CACHE =
        new ConcurrentHashMap<>();
  
    private List<SubscriberInfoIndex> 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<SubscriberInfoIndex> subscriberInfoIndexes,
        boolean strictMethodVerification,
        boolean ignoreGeneratedIndex) {
      this.subscriberInfoIndexes = subscriberInfoIndexes;
      this.strictMethodVerification = strictMethodVerification;
      this.ignoreGeneratedIndex = ignoreGeneratedIndex;
    }
  
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
      List<SubscriberMethod> 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<SubscriberMethod> 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<SubscriberMethod> getMethodsAndRelease(FindState findState) {
      List<SubscriberMethod> 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<SubscriberMethod> 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<SubscriberMethod> subscriberMethods = new ArrayList<>();
      final Map<Class, Object> anyMethodByEventType = new HashMap<>();
      final Map<String, Class> 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.")
              || clazzName.startsWith("jdkx.")
              || clazzName.startsWith("openjdk.")) {
            clazz = null;
          }
        }
      }
    }
  }
  