code: package com.lody.virtual.client.hook.base;
  
  import android.content.Context;
  
  import com.lody.virtual.client.core.InvocationStubManager;
  import com.lody.virtual.client.core.VirtualCore;
  import com.lody.virtual.client.interfaces.IInjector;
  
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Modifier;
  
  /**
   * @author Lody
   *         <p>
   *         This class is responsible with:
   *         - Instantiating a {@link MethodInvocationStub.HookInvocationHandler} on {@link #getInvocationStub()} ()}
   *         - Install a bunch of {@link MethodProxy}s, either with a @{@link Inject} annotation or manually
   *         calling {@link #addMethodProxy(MethodProxy)} from {@link #onBindMethods()}
   *         - Install the hooked object on the Runtime via {@link #inject()}
   *         <p>
   *         All {@link MethodInvocationProxy}s (plus a couple of other @{@link IInjector}s are installed by
   *         {@link InvocationStubManager}
   * @see Inject
   */
  public abstract class MethodInvocationProxy<T extends MethodInvocationStub> implements IInjector {
  
      protected T mInvocationStub;
  
      public MethodInvocationProxy(T invocationStub) {
          this.mInvocationStub = invocationStub;
          onBindMethods();
          afterHookApply(invocationStub);
  
          LogInvocation loggingAnnotation = getClass().getAnnotation(LogInvocation.class);
          if (loggingAnnotation != null) {
              invocationStub.setInvocationLoggingCondition(loggingAnnotation.value());
          }
      }
  
      protected void onBindMethods() {
  
          if (mInvocationStub == null) {
              return;
          }
          Class<? extends MethodInvocationProxy> clazz = getClass();
          Inject inject = clazz.getAnnotation(Inject.class);
          if (inject != null) {
              Class<?> proxiesClass = inject.value();
              Class<?>[] innerClasses = proxiesClass.getDeclaredClasses();
              for (Class<?> innerClass : innerClasses) {
                  if (!Modifier.isAbstract(innerClass.getModifiers())
                          && MethodProxy.class.isAssignableFrom(innerClass)
                          && innerClass.getAnnotation(SkipInject.class) == null) {
                      addMethodProxy(innerClass);
                  }
              }
  
          }
      }
  
      private void addMethodProxy(Class<?> hookType) {
          try {
              Constructor<?> constructor = hookType.getDeclaredConstructors()[0];
              if (!constructor.isAccessible()) {
                  constructor.setAccessible(true);
              }
              MethodProxy methodProxy;
              if (constructor.getParameterTypes().length == 0) {
                  methodProxy = (MethodProxy) constructor.newInstance();
              } else {
                  methodProxy = (MethodProxy) constructor.newInstance(this);
              }
              mInvocationStub.addMethodProxy(methodProxy);
          } catch (Throwable e) {
              throw new RuntimeException("Unable to instance Hook : " + hookType + " : " + e.getMessage());
          }
      }
  
      public MethodProxy addMethodProxy(MethodProxy methodProxy) {
          return mInvocationStub.addMethodProxy(methodProxy);
      }
  
      protected void afterHookApply(T delegate) {
      }
  
      @Override
      public abstract void inject() throws Throwable;
  
      public Context getContext() {
          return VirtualCore.get().getContext();
      }
  
      public T getInvocationStub() {
          return mInvocationStub;
      }
  }
  