content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you 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.apache.aries.proxy.impl.common;
  
  import static java.lang.String.format;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.DISPATCHER_FIELD;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.DISPATCHER_TYPE;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.LISTENER_FIELD;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.LISTENER_TYPE;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.METHOD_TYPE;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.NO_ARGS;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.OBJECT_TYPE;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.THROWABLE_INAME;
  import static org.apache.aries.proxy.impl.common.AbstractWovenProxyAdapter.WOVEN_PROXY_IFACE_TYPE;
  import static org.objectweb.asm.Opcodes.ACONST_NULL;
  import static org.objectweb.asm.Opcodes.ASM9;
  import static org.objectweb.asm.Opcodes.IFNE;
  
  import java.util.Arrays;
  
  import org.apache.aries.proxy.InvocationListener;
  import org.objectweb.asm.Label;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  import org.objectweb.asm.Type;
  import org.objectweb.asm.commons.GeneratorAdapter;
  import org.objectweb.asm.commons.Method;
  
  /**
   * This class weaves dispatch and listener code into a method, there are two known
   * subclasses {@link WovenProxyConcreteMethodAdapter} is used for weaving instance methods
   * {@link WovenProxyAbstractMethodAdapter} is used to provide a delegating
   * implementation of an interface method.
   * 
   * Roughly (but not exactly because it's easier to write working bytecode
   * if you don't have to exactly recreate the Java!) this is trying to 
   * do the following: <code>
   * 
   *      
      if(dispatcher != null) {
        int returnValue;
        Object token = null;
        boolean inInvoke = false;
        try {
          Object toInvoke = dispatcher.call();
          if(listener != null)
            token = listener.preInvoke(toInvoke, method, args);
          
          inInvoke = true;
          returnValue = ((Template) toInvoke).doStuff(args);
          inInvoke = false;
          
          if(listener != null)
            listener.postInvoke(token, toInvoke, method, args);
          
        } catch (Throwable e){
          // whether the the exception is an error is an application decision
          // if we catch an exception we decide carefully which one to
          // throw onwards
          Throwable exceptionToRethrow = null;
          // if the exception came from a precall or postcall 
          // we will rethrow it
          if (!inInvoke) {
            exceptionToRethrow = e;
          }
          // if the exception didn't come from precall or postcall then it
          // came from invoke
          // we will rethrow this exception if it is not a runtime
          // exception, but we must unwrap InvocationTargetExceptions
          else {
            if (!(e instanceof RuntimeException)) {
              exceptionToRethrow = e;
            }
          }
          try {
            if(listener != null)
              listener.postInvokeExceptionalReturn(token, method, null, e);
          } catch (Throwable f) {
            // we caught an exception from
            // postInvokeExceptionalReturn
            // if we haven't already chosen an exception to rethrow then
            // we will throw this exception
            if (exceptionToRethrow == null) {
              exceptionToRethrow = f;
            }
          }
          // if we made it this far without choosing an exception we
          // should throw e
          if (exceptionToRethrow == null) {
            exceptionToRethrow = e;
          }
          throw exceptionToRethrow;
        }
      }
      
      //...original method body
        </code>
   *  
   *   
   */
  public abstract class AbstractWovenProxyMethodAdapter extends GeneratorAdapter
  {
    /** The type of a RuntimeException */
    private static final Type RUNTIME_EX_TYPE = Type.getType(RuntimeException.class);
    private static final Type THROWABLE_TYPE = Type.getType(Throwable.class);
    
    /** The postInvoke method of an {@link InvocationListener} */
    private static final Method POST_INVOKE_METHOD = getAsmMethodFromClass(InvocationListener.class, "postInvoke", Object.class,
        Object.class, java.lang.reflect.Method.class, Object.class);
    /** The postInvokeExceptionalReturn method of an {@link InvocationListener} */
    private static final Method POST_INVOKE_EXCEPTIONAL_METHOD = getAsmMethodFromClass(InvocationListener.class, 
        "postInvokeExceptionalReturn", Object.class, Object.class,
        java.lang.reflect.Method.class, Throwable.class);
    /** The preInvoke method of an {@link InvocationListener} */
    private static final Method PRE_INVOKE_METHOD = getAsmMethodFromClass(InvocationListener.class, "preInvoke", Object.class,
        java.lang.reflect.Method.class, Object[].class);
    
    
    /** The name of the static field that stores our {@link java.lang.reflect.Method} */
    private final String methodStaticFieldName;
    /** The current method */
    protected final Method currentTransformMethod;
    /** The type of <code>this</code> */
    protected final Type typeBeingWoven;
    /** True if this is a void method */
    private final boolean isVoid;
  
    //ints for local store
    /** The local we use to store the {@link InvocationListener} token */
    private int preInvokeReturnedToken;
    /** The local we use to note whether we are in the original method body or not */
    private int inNormalMethod;
    /** The local we use to store the invocation target to dispatch to */
    private int dispatchTarget;
    /** The local for storing our method's result */
    private int normalResult;
  
    //the Labels we need for jumping around the pre/post/postexception and current method code
    /** This marks the start of the try/catch around the pre/postInvoke*/
    private final Label beginTry = new Label();
    /** This marks the end of the try/catch around the pre/postInvoke*/
    private final Label endTry = new Label();
  
    /** The return type of this method */
    private final Type returnType;
    
    private final Type methodDeclaringType;
    
    private final boolean isMethodDeclaringTypeInterface;
    private boolean isDefaultMethod;
    
    /**
     * Construct a new method adapter
     * @param mv - the method visitor to write to
     * @param access - the access modifiers on this method
     * @param name - the name of this method
     * @param desc - the descriptor of this method
     * @param methodStaticFieldName - the name of the static field that will hold
     *                                the {@link java.lang.reflect.Method} representing
     *                                this method.
     * @param currentTransformMethod - the ASM representation of this method
     * @param proxyType - the type being woven that contains this method
     */
    public AbstractWovenProxyMethodAdapter(MethodVisitor mv, int access, String name, String desc,
        String methodStaticFieldName, Method currentTransformMethod, Type typeBeingWoven,
        Type methodDeclaringType, boolean isMethodDeclaringTypeInterface, boolean isDefaultMethod)
    {
      super(ASM9, mv, access, name, desc);
      this.methodStaticFieldName = methodStaticFieldName;
      this.currentTransformMethod = currentTransformMethod;
      returnType = currentTransformMethod.getReturnType();
      isVoid = returnType.getSort() == Type.VOID;
      this.typeBeingWoven = typeBeingWoven;
      this.methodDeclaringType = methodDeclaringType;
      this.isMethodDeclaringTypeInterface = isMethodDeclaringTypeInterface;
      this.isDefaultMethod = isDefaultMethod;
    }
  
    @Override
    public abstract void visitCode();
  
    @Override
    public abstract void visitMaxs(int stack, int locals);
    
    /**
     * Write out the bytecode instructions necessary to do the dispatch.
     * We know the dispatcher is non-null, and we need a try/catch around the
     * invocation and listener calls.
     */
    protected final void writeDispatcher() {
      // Setup locals we will use in the dispatch
      setupLocals();
      
      //Write the try catch block
      visitTryCatchBlock(beginTry, endTry, endTry, THROWABLE_INAME);
      mark(beginTry);
      
      //Start dispatching, get the target object and store it
      loadThis();
      getField(typeBeingWoven, DISPATCHER_FIELD, DISPATCHER_TYPE);
      invokeInterface(DISPATCHER_TYPE, new Method("call", OBJECT_TYPE, NO_ARGS));
      storeLocal(dispatchTarget);
      
      //Pre-invoke, invoke, post-invoke, return
      writePreInvoke();
      //Start the real method
      push(true);
      storeLocal(inNormalMethod);
      
      //Dispatch the method and store the result (null for void)
      loadLocal(dispatchTarget);
      checkCast(methodDeclaringType);
      loadArgs();
      if(isMethodDeclaringTypeInterface) {
        invokeInterface(methodDeclaringType, currentTransformMethod);
      } else {
        invokeVirtual(methodDeclaringType, currentTransformMethod);
      }
      if(isVoid) {
        visitInsn(ACONST_NULL);
      }
      storeLocal(normalResult);
      
      // finish the real method and post-invoke
      push(false);
      storeLocal(inNormalMethod);
      writePostInvoke();
      
      //Return, with the return value if necessary
      if(!!!isVoid) {
        loadLocal(normalResult);
      }
      returnValue();
      
      //End of our try, start of our catch
      mark(endTry);
      writeMethodCatchHandler();
    }
    
    /**
     * Setup the normalResult, inNormalMethod, preInvokeReturnedToken and
     * dispatch target locals.
     */
    private final void setupLocals() {
      if (isVoid){
        normalResult = newLocal(OBJECT_TYPE);
      } else{
        normalResult = newLocal(returnType);
      }
      
      preInvokeReturnedToken = newLocal(OBJECT_TYPE);
      visitInsn(ACONST_NULL);
      storeLocal(preInvokeReturnedToken);
      
      inNormalMethod = newLocal(Type.BOOLEAN_TYPE);
      push(false);
      storeLocal(inNormalMethod);
      
      dispatchTarget = newLocal(OBJECT_TYPE);
      visitInsn(ACONST_NULL);
      storeLocal(dispatchTarget);
    }
  
    /**
     * Begin trying to invoke the listener, if the listener is
     * null the bytecode will branch to the supplied label, other
     * otherwise it will load the listener onto the stack.
     * @param l The label to branch to
     */
    private final void beginListenerInvocation(Label l) {
      //If there's no listener then skip invocation
      loadThis();
      getField(typeBeingWoven, LISTENER_FIELD, LISTENER_TYPE);
      ifNull(l);
      loadThis();
      getField(typeBeingWoven, LISTENER_FIELD, LISTENER_TYPE);
    }
  
    /**
     * Write out the preInvoke. This copes with the listener being null
     */
    private final void writePreInvoke() {
      //The place to go if the listener is null
      Label nullListener = newLabel();
      beginListenerInvocation(nullListener);
  
      // The listener is on the stack, we need (target, method, args)
      
      loadLocal(dispatchTarget);
      getStatic(typeBeingWoven, methodStaticFieldName, METHOD_TYPE);
      loadArgArray();
      
      //invoke it and store the token returned
      invokeInterface(LISTENER_TYPE, PRE_INVOKE_METHOD);
      storeLocal(preInvokeReturnedToken);
      
      mark(nullListener);
    }
    
    /**
     * Write out the postInvoke. This copes with the listener being null
     */
    private final void writePostInvoke() {
      //The place to go if the listener is null
      Label nullListener = newLabel();
      beginListenerInvocation(nullListener);
      
      // The listener is on the stack, we need (token, target, method, result)
      
      loadLocal(preInvokeReturnedToken);
      loadLocal(dispatchTarget);
      getStatic(typeBeingWoven, methodStaticFieldName, METHOD_TYPE);
      loadLocal(normalResult);
      
      //If the result a primitive then we need to box it
      if (!!!isVoid && returnType.getSort() != Type.OBJECT && returnType.getSort() != Type.ARRAY){
        box(returnType);
      }
      
      //invoke the listener
      invokeInterface(LISTENER_TYPE, POST_INVOKE_METHOD);
      
      mark(nullListener);
    }
    
    /**
     * Write the catch handler for our method level catch, this runs the exceptional
     * post-invoke if there is a listener, and throws the correct exception at the
     * end
     */
    private final void writeMethodCatchHandler() {
      
      //Store the original exception
      int originalException = newLocal(THROWABLE_TYPE);
      storeLocal(originalException);
      
      //Start by initialising exceptionToRethrow
      int exceptionToRethrow = newLocal(THROWABLE_TYPE);
      visitInsn(ACONST_NULL);
      storeLocal(exceptionToRethrow);
      
      //We need another try catch around the postInvokeExceptionalReturn, so here 
      //are some labels and the declaration for it
      Label beforeInvoke = newLabel();
      Label afterInvoke = newLabel();
      visitTryCatchBlock(beforeInvoke, afterInvoke, afterInvoke, THROWABLE_INAME);
      
      //If we aren't in normal flow then set exceptionToRethrow = originalException
      loadLocal(inNormalMethod);
      Label inNormalMethodLabel = newLabel();
      // Jump if not zero (false)
      visitJumpInsn(IFNE, inNormalMethodLabel);
      loadLocal(originalException);
      storeLocal(exceptionToRethrow);
      goTo(beforeInvoke);
      
      mark(inNormalMethodLabel);
      //We are in normal method flow so set exceptionToRethrow = originalException
      //if originalException is not a runtime exception
      loadLocal(originalException);
      instanceOf(RUNTIME_EX_TYPE);
      //If false then store original in toThrow, otherwise go to beforeInvoke
      visitJumpInsn(IFNE, beforeInvoke);
      loadLocal(originalException);
      storeLocal(exceptionToRethrow);
      goTo(beforeInvoke);
      //Setup of variables finished, begin try/catch
         
      //Mark the start of our try
      mark(beforeInvoke);
      //Begin invocation of the listener, jump to throw if null
      Label throwSelectedException = newLabel();
      beginListenerInvocation(throwSelectedException);
      
      //We have a listener, so call it (token, target, method, exception)
      loadLocal(preInvokeReturnedToken);
      loadLocal(dispatchTarget);
      getStatic(typeBeingWoven, methodStaticFieldName, METHOD_TYPE);
      loadLocal(originalException);
      invokeInterface(LISTENER_TYPE, POST_INVOKE_EXCEPTIONAL_METHOD);
      goTo(throwSelectedException);
      
      mark(afterInvoke);
      //catching another exception replaces the original
      storeLocal(originalException);
  
      //Throw exceptionToRethrow if it isn't null, or the original if it is
      Label throwException = newLabel();
      mark(throwSelectedException);
      loadLocal(exceptionToRethrow);
      ifNonNull(throwException);
      loadLocal(originalException);
      storeLocal(exceptionToRethrow);
      
      mark(throwException);
      loadLocal(exceptionToRethrow);
      throwException();
    }
    
    /**
     * This method unwraps woven proxy instances for use in the right-hand side
     * of equals methods
     */
    protected final void unwrapEqualsArgument() {
      
      //Create and initialize a local for our work
      int unwrapLocal = newLocal(OBJECT_TYPE);
      visitInsn(ACONST_NULL);
      storeLocal(unwrapLocal);
      
      Label startUnwrap = newLabel();
      mark(startUnwrap);
      //Load arg and check if it is a WovenProxy instances
      loadArg(0);
      instanceOf(WOVEN_PROXY_IFACE_TYPE);
      Label unwrapFinished = newLabel();
      //Jump if zero (false)
      visitJumpInsn(Opcodes.IFEQ, unwrapFinished);
      //Arg is a wovenProxy, if it is the same as last time then we're done
      loadLocal(unwrapLocal);
      loadArg(0);
      ifCmp(OBJECT_TYPE, EQ, unwrapFinished);
      //Not the same, store current arg in unwrapLocal for next loop
      loadArg(0);
      storeLocal(unwrapLocal);
      
      //So arg is a WovenProxy, but not the same as last time, cast it and store 
      //the result of unwrap.call in the arg
      loadArg(0);
      checkCast(WOVEN_PROXY_IFACE_TYPE);
      //Now unwrap
      invokeInterface(WOVEN_PROXY_IFACE_TYPE, new Method("org_apache_aries_proxy_weaving_WovenProxy_unwrap",
          DISPATCHER_TYPE, NO_ARGS));
      
      //Now we may have a Callable to invoke
      int callable = newLocal(DISPATCHER_TYPE);
      storeLocal(callable);
      loadLocal(callable);
      ifNull(unwrapFinished);
      loadLocal(callable);
      invokeInterface(DISPATCHER_TYPE, new Method("call",
          OBJECT_TYPE, NO_ARGS));
      //Store the result and we're done (for this iteration)
      storeArg(0);
      goTo(startUnwrap);
      
      mark(unwrapFinished);
    }
  
    /**
     * A utility method for getting an ASM method from a Class
     * @param clazz the class to search
     * @param name The method name
     * @param argTypes The method args
     * @return
     */
    private static final Method getAsmMethodFromClass(Class<?> clazz, String name, Class<?>... argTypes)
    {
      //get the java.lang.reflect.Method to get the types
      java.lang.reflect.Method ilMethod = null;
      try {
        ilMethod = clazz.getMethod(name, argTypes);
      } catch (Exception e) {
        //Should be impossible!
        throw new RuntimeException(format("Error finding InvocationListener method %s with argument types %s.", 
                                          name, Arrays.toString(argTypes)), e);
      }
      //get the ASM method
      return new Method(name, Type.getReturnType(ilMethod), Type.getArgumentTypes(ilMethod));
    }
  }
  