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.gen;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationHandler;
  
  import org.apache.aries.proxy.impl.ProxyUtils;
  import org.apache.aries.proxy.impl.SystemModuleClassLoader;
  import org.objectweb.asm.AnnotationVisitor;
  import org.objectweb.asm.Attribute;
  import org.objectweb.asm.ClassReader;
  import org.objectweb.asm.ClassVisitor;
  import org.objectweb.asm.FieldVisitor;
  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;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  public class ProxySubclassAdapter extends ClassVisitor implements Opcodes
  {
  
    private static final Type STRING_TYPE = Type.getType(String.class);
    private static final Type CLASS_TYPE = Type.getType(Class.class);
    private static final Type CLASSLOADER_TYPE = Type.getType(ClassLoader.class);
    private static final Type OBJECT_TYPE = Type.getType(Object.class);
    private static final Type METHOD_TYPE = Type.getType(java.lang.reflect.Method.class);
    private static final Type IH_TYPE = Type.getType(InvocationHandler.class);
    private static final Type[] NO_ARGS = new Type[] {};
  
    private static final String IH_FIELD = "ih";
  
    private static Logger LOGGER = LoggerFactory.getLogger(ProxySubclassAdapter.class);
  
    private String newClassName = null;
    private String superclassBinaryName = null;
    private Class<?> superclassClass = null;
    private ClassLoader loader = null;
    private Type newClassType = null;
    private GeneratorAdapter staticAdapter = null;
    private String currentlyAnalysedClassName = null;
    private Class<?> currentlyAnalysedClass = null;
    private String currentClassFieldName = null;
  
    public ProxySubclassAdapter(ClassVisitor writer, String newClassName, ClassLoader loader)
    {
      // call the superclass constructor
      super(Opcodes.ASM9, writer);
      // the writer is now the cv in the superclass of ClassAdapter
  
      LOGGER.debug(Constants.LOG_ENTRY, "ProxySubclassAdapter", new Object[] { this, writer,
          newClassName });
  
      // set the newClassName field
      this.newClassName = newClassName;
      // set the newClassType descriptor
      newClassType = Type.getType("L" + newClassName + ";");
  
      // set the classloader
      this.loader = loader;
  
      LOGGER.debug(Constants.LOG_EXIT, "ProxySubclassAdapter", this);
    }
  
    /*
     * This method visits the class to generate the new subclass.
     * 
     * The following things happen here: 1. The class is renamed to a dynamic
     * name 2. The existing class name is changed to be the superclass name so
     * that the generated class extends the original class. 3. A private field
     * is added to store an invocation handler 4. A constructor is added that
     * takes an invocation handler as an argument 5. The constructor method
     * instantiates an instance of the superclass 6. The constructor method sets
     * the invocation handler so the invoke method can be called from all the
     * subsequently rewritten methods 7. Add a getInvocationHandler() method 8.
     * store a static Class object of the superclass so we can reflectively find
     * methods later
     */
    public void visit(int version, int access, String name, String signature, String superName,
        String[] interfaces)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "visit", new Object[] { version, access, name,
          signature, superName, interfaces });
  
      // store the superclass binary name
      this.superclassBinaryName = name.replaceAll("/", "\\.");
  
      try {
        this.superclassClass = Class.forName(superclassBinaryName, false, loader);
      } catch (ClassNotFoundException cnfe) {
        throw new TypeNotPresentException(superclassBinaryName, cnfe);
      }
  
  
      // keep the same access and signature as the superclass (unless it's abstract)
      // remove all the superclass interfaces because they will be inherited
      // from the superclass anyway
      if((access & ACC_ABSTRACT) != 0) {
        //If the super was abstract the subclass should not be!
        access &= ~ACC_ABSTRACT;
      }
      cv.visit(ProxyUtils.getWeavingJavaVersion(), access, newClassName, signature, name, null);
  
      // add a private field for the invocation handler
      // this isn't static in case we have multiple instances of the same
      // proxy
      cv.visitField(ACC_PRIVATE, IH_FIELD, Type.getDescriptor(InvocationHandler.class), null, null);
  
      // create a static adapter for generating a static initialiser method in
      // the generated subclass
      staticAdapter = new GeneratorAdapter(ACC_STATIC,
          new Method("<clinit>", Type.VOID_TYPE, NO_ARGS), null, null, cv);
  
      // add a zero args constructor method
      Method m = new Method("<init>", Type.VOID_TYPE, NO_ARGS);
      GeneratorAdapter methodAdapter = new GeneratorAdapter(ACC_PUBLIC, m, null, null, cv);
      // loadthis
      methodAdapter.loadThis();
      // List the constructors in the superclass.
      Constructor<?>[] constructors = superclassClass.getDeclaredConstructors();
      // Check that we've got at least one constructor, and get the 1st one in the list.
      if (constructors.length > 0) {
        // We now need to construct the proxy class as though it is going to invoke the superclasses constructor.
        // We do this because we can no longer call the java.lang.Object() zero arg constructor as the JVM now throws a VerifyError.
        // So what we do is build up the calling of the superclasses constructor using nulls and default values. This means that the 
        // class bytes can be verified by the JVM, and then in the ProxySubclassGenerator, we load the class without invoking the 
        // constructor. 
        Method constructor = Method.getMethod(constructors[0]);      
        Type[] argTypes = constructor.getArgumentTypes();
        if (argTypes.length == 0) {
          methodAdapter.invokeConstructor(Type.getType(superclassClass), new Method("<init>", Type.VOID_TYPE, NO_ARGS));
        } else {
          for (Type type : argTypes) {
            switch (type.getSort())
            {
              case Type.ARRAY:
                // We need to process any array or multidimentional arrays.
                String elementDesc = type.getElementType().getDescriptor();
                String typeDesc = type.getDescriptor();
                
                // Iterate over the number of arrays and load 0 for each one. Keep a count of the number of 
                // arrays as we will need to run different code fo multi dimentional arrays.
                int index = 0;
                while (! elementDesc.equals(typeDesc)) {
                  typeDesc = typeDesc.substring(1);
                  methodAdapter.visitInsn(Opcodes.ICONST_0);
                  index++;
                }
                // If we're just a single array, then call the newArray method, otherwise use the MultiANewArray instruction.
                if (index == 1) {
                  methodAdapter.newArray(type.getElementType());
                } else {
                  methodAdapter.visitMultiANewArrayInsn(type.getDescriptor(), index);
                }
                break;
              case Type.BOOLEAN:
                methodAdapter.push(true);
                break;
              case Type.BYTE:
                methodAdapter.push(Type.VOID_TYPE);
                break;
              case Type.CHAR:
                methodAdapter.push(Type.VOID_TYPE);
                break;
              case Type.DOUBLE:
                methodAdapter.push(0.0);
                break;
              case Type.FLOAT:
                methodAdapter.push(0.0f);
                break;
              case Type.INT:
                methodAdapter.push(0);
                break;
              case Type.LONG:
                methodAdapter.push(0l);
                break;
              case Type.SHORT:
                methodAdapter.push(0);
                break;
              default:
              case Type.OBJECT:
                methodAdapter.visitInsn(Opcodes.ACONST_NULL);
                break;
            }
          }
          
          methodAdapter.invokeConstructor(Type.getType(superclassClass), new Method("<init>", Type.VOID_TYPE, argTypes));
        }
      }
      methodAdapter.returnValue();
      methodAdapter.endMethod();
  
      // add a method for getting the invocation handler
      Method setter = new Method("setInvocationHandler", Type.VOID_TYPE, new Type[] { IH_TYPE });
      m = new Method("getInvocationHandler", IH_TYPE, NO_ARGS);
      methodAdapter = new GeneratorAdapter(ACC_PUBLIC | ACC_FINAL, m, null, null, cv);
      // load this to get the field
      methodAdapter.loadThis();
      // get the ih field and return
      methodAdapter.getField(newClassType, IH_FIELD, IH_TYPE);
      methodAdapter.returnValue();
      methodAdapter.endMethod();
  
      // add a method for setting the invocation handler
      methodAdapter = new GeneratorAdapter(ACC_PUBLIC | ACC_FINAL, setter, null, null, cv);
      // load this to put the field
      methodAdapter.loadThis();
      // load the method arguments (i.e. the invocation handler) to the stack
      methodAdapter.loadArgs();
      // set the ih field using the method argument
      methodAdapter.putField(newClassType, IH_FIELD, IH_TYPE);
      methodAdapter.returnValue();
      methodAdapter.endMethod();
  
      // loop through the class hierarchy to get any needed methods off the
      // supertypes
      // start by finding the methods declared on the class of interest (the
      // superclass of our dynamic subclass)
      java.lang.reflect.Method[] observedMethods = superclassClass.getDeclaredMethods();
      // add the methods to a set of observedMethods
      ProxySubclassMethodHashSet<String> setOfObservedMethods = new ProxySubclassMethodHashSet<String>(
          observedMethods.length);
      setOfObservedMethods.addMethodArray(observedMethods);
      // get the next superclass in the hierarchy
      Class<?> nextSuperClass = superclassClass.getSuperclass();
      while (nextSuperClass != null) {
        // set the fields for the current class
        setCurrentAnalysisClassFields(nextSuperClass);
  
        // add a static field and static initializer code to the generated
        // subclass
        // for each of the superclasses in the hierarchy
        addClassStaticField(currentlyAnalysedClassName);
  
        LOGGER.debug("Class currently being analysed: {} {}", currentlyAnalysedClassName,
            currentlyAnalysedClass);
  
        // now find the methods declared on the current class and add them
        // to a set of foundMethods
        java.lang.reflect.Method[] foundMethods = currentlyAnalysedClass.getDeclaredMethods();
        ProxySubclassMethodHashSet<String> setOfFoundMethods = new ProxySubclassMethodHashSet<String>(
            foundMethods.length);
        setOfFoundMethods.addMethodArray(foundMethods);
        // remove from the set of foundMethods any methods we saw on a
        // subclass
        // because we want to use the lowest level declaration of a method
        setOfFoundMethods.removeAll(setOfObservedMethods);
        try {
          // read the current class and use a
          // ProxySubclassHierarchyAdapter
          // to process only methods on that class that are in the list
          ClassLoader loader = currentlyAnalysedClass.getClassLoader();
          if (loader == null) {
            loader = this.loader;
          }
          InputStream is = loader.getResourceAsStream(currentlyAnalysedClass
                                                      .getName().replaceAll("\\.", "/")
                                                      + ".class");
          if (is == null) {
                //use SystemModuleClassLoader as fallback
                ClassLoader classLoader = new SystemModuleClassLoader();
                is = classLoader.getResourceAsStream(currentlyAnalysedClass
                                                .getName().replaceAll("\\.", "/")
                                                + ".class");
          }
          ClassReader cr = new ClassReader(is);
          ClassVisitor hierarchyAdapter = new ProxySubclassHierarchyAdapter(this, setOfFoundMethods);
          cr.accept(hierarchyAdapter, ClassReader.SKIP_DEBUG);
        } catch (IOException e) {
          throw new TypeNotPresentException(currentlyAnalysedClassName, e);
        }
        // now add the foundMethods to the overall list of observed methods
        setOfObservedMethods.addAll(setOfFoundMethods);
        // get the next class up in the hierarchy and go again
        nextSuperClass = currentlyAnalysedClass.getSuperclass();
      }
  
      // we've finished looking at the superclass hierarchy
      // set the fields for the immediate superclass of our dynamic subclass
      setCurrentAnalysisClassFields(superclassClass);
  
      // add the class static field
      addClassStaticField(currentlyAnalysedClassName);
      // we do the lowest class last because we are already visiting the class
      // when in this adapter code
      // now we are ready to visit all the methods on the lowest class
      // which will happen by the ASM ClassVisitor implemented in this adapter
  
      LOGGER.debug(Constants.LOG_EXIT, "visit");
    }
  
    public void visitSource(String source, String debug)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "visitSource", new Object[] { source, debug });
  
      // set the source to null since the class is generated on the fly and
      // not compiled
      cv.visitSource(null, null);
  
      LOGGER.debug(Constants.LOG_EXIT, "visitSource");
    }
  
    public void visitEnd()
    {
      LOGGER.debug(Constants.LOG_ENTRY, "visitEnd");
  
      // this method is called when we reach the end of the class
      // so it is time to make sure the static initialiser method is closed
      staticAdapter.returnValue();
      staticAdapter.endMethod();
      // now delegate to the cv
      cv.visitEnd();
  
      LOGGER.debug(Constants.LOG_EXIT, "visitEnd");
    }
  
    /*
     * This method is called on each method of the superclass (and all parent
     * classes up to Object) Each of these methods is visited in turn and the
     * code here generates the byte code for the InvocationHandler to call the
     * methods on the superclass.
     */
    public MethodVisitor visitMethod(int access, String name, String desc, String signature,
        String[] exceptions)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "visitMethod", new Object[] { access, name, desc,
          signature, exceptions });
  
      /*
       * Check the method access and handle the method types we don't want to
       * copy: final methods (issue warnings if these are not methods from
       * java.* classes) static methods (initialiser and others) private
       * methods, constructors (for now we don't copy any constructors)
       * everything else we process to proxy. Abstract methods should be made
       * non-abstract so that they can be proxied.
       */
      
      if((access & ACC_ABSTRACT) != 0) {
        //If the method is abstract then it should not be in the concrete subclass!
        access &= ~ACC_ABSTRACT;
      }
      
      LOGGER.debug("Method name: {} with descriptor: {}", name, desc);
  
      MethodVisitor methodVisitorToReturn = null;
  
      if (name.equals("<init>")) {
        // we may need to do something extra with constructors later
        // e.g. include bytecode for calling super with the same args
        // since we currently rely on the super having a zero args
        // constructor
        // we need to issue an error if we don't find one
  
        // for now we return null to ignore them
        methodVisitorToReturn = null;
      } else if (name.equals("<clinit>")) {
        // don't copy static initialisers from the superclass into the new
        // subclass
        methodVisitorToReturn = null;
      } else if ((access & ACC_FINAL) != 0) {
        // since we check for final methods in the ProxySubclassGenerator we
        // should never get here
        methodVisitorToReturn = null;
      } else if ((access & ACC_SYNTHETIC) != 0) {
        // synthetic methods are generated by the compiler for covariance
        // etc
        // we shouldn't copy them or we will have duplicate methods
        methodVisitorToReturn = null;
      } else if ((access & ACC_PRIVATE) != 0) {
        // don't copy private methods from the superclass
        methodVisitorToReturn = null;
      } else if ((access & ACC_STATIC) != 0) {
        // don't copy static methods
        methodVisitorToReturn = null;
      } else if (!(((access & ACC_PUBLIC) != 0) || ((access & ACC_PROTECTED) != 0) || ((access & ACC_PRIVATE) != 0))) {
        // the default (package) modifier value is 0, so by using & with any
        // of the other
        // modifier values and getting a result of zero means that we have
        // default accessibility
  
        // check the package in which the method is declared against the
        // package
        // where the generated subclass will be
        // if they are the same process the method otherwise ignore it
        if (currentlyAnalysedClass.getPackage().equals(superclassClass.getPackage())) {
          processMethod(access, name, desc, signature, exceptions);
          methodVisitorToReturn = null;
        } else {
          methodVisitorToReturn = null;
        }
      } else {
        processMethod(access, name, desc, signature, exceptions);
        // return null because we don't want the original method code from
        // the superclass
        methodVisitorToReturn = null;
      }
  
      LOGGER.debug(Constants.LOG_EXIT, "visitMethod", methodVisitorToReturn);
  
      return methodVisitorToReturn;
  
    }
  
    private void processMethod(int access, String name, String desc, String signature,
        String[] exceptions)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "processMethod", new Object[] { access, name, desc,
          signature, exceptions });
  
      LOGGER.debug("Processing method: {} with descriptor {}", name, desc);
  
      // identify the target method parameters and return type
      Method currentTransformMethod = new Method(name, desc);
      Type[] targetMethodParameters = currentTransformMethod.getArgumentTypes();
      Type returnType = currentTransformMethod.getReturnType();
  
      // we create a static field for each method we encounter with a name
      // like method_parm1_parm2...
      StringBuilder methodStaticFieldNameBuilder = new StringBuilder(name);
      // for each a parameter get the name and add it to the field removing
      // the dots first
      for (Type t : targetMethodParameters) {
        methodStaticFieldNameBuilder.append("_");
        methodStaticFieldNameBuilder.append(t.getClassName().replaceAll("\\[\\]", "Array")
            .replaceAll("\\.", ""));
      }
      String methodStaticFieldName = methodStaticFieldNameBuilder.toString();
  
      // add a private static field for the method
      cv.visitField(ACC_PRIVATE | ACC_STATIC, methodStaticFieldName, METHOD_TYPE.getDescriptor(),
          null, null);
  
      // visit the method using the class writer, delegated through the method
      // visitor and generator
      // modify the method access so that any native methods aren't
      // described as native
      // since they won't be native in proxy form
      // also stop methods being marked synchronized on the proxy as they will
      // be sync
      // on the real object
      int newAccess = access & (~ACC_NATIVE) & (~ACC_SYNCHRONIZED);
      MethodVisitor mv = cv.visitMethod(newAccess, name, desc, signature, exceptions);
      // use a GeneratorAdapter to build the invoke call directly in byte code
      GeneratorAdapter methodAdapter = new GeneratorAdapter(mv, newAccess, name, desc);
  
      /*
       * Stage 1 creates the bytecode for adding the reflected method of the
       * superclass to a static field in the subclass: private static Method
       * methodName_parm1_parm2... = null; static{ methodName_parm1_parm2... =
       * superClass.getDeclaredMethod(methodName,new Class[]{method args}; }
       * 
       * Stage 2 is to call the ih.invoke(this,methodName_parm1_parm2,args) in
       * the new subclass methods Stage 3 is to cast the return value to the
       * correct type
       */
  
      /*
       * Stage 1 use superClass.getMethod(methodName,new Class[]{method args}
       * from the Class object on the stack
       */
  
      // load the static superclass Class onto the stack
      staticAdapter.getStatic(newClassType, currentClassFieldName, CLASS_TYPE);
  
      // push the method name string arg onto the stack
      staticAdapter.push(name);
  
      // create an array of the method parm class[] arg
      staticAdapter.push(targetMethodParameters.length);
      staticAdapter.newArray(CLASS_TYPE);
      int index = 0;
      for (Type t : targetMethodParameters) {
        staticAdapter.dup();
        staticAdapter.push(index);
        switch (t.getSort())
        {
          case Type.BOOLEAN:
            staticAdapter.getStatic(Type.getType(java.lang.Boolean.class), "TYPE", CLASS_TYPE);
            break;
          case Type.BYTE:
            staticAdapter.getStatic(Type.getType(java.lang.Byte.class), "TYPE", CLASS_TYPE);
            break;
          case Type.CHAR:
            staticAdapter.getStatic(Type.getType(java.lang.Character.class), "TYPE", CLASS_TYPE);
            break;
          case Type.DOUBLE:
            staticAdapter.getStatic(Type.getType(java.lang.Double.class), "TYPE", CLASS_TYPE);
            break;
          case Type.FLOAT:
            staticAdapter.getStatic(Type.getType(java.lang.Float.class), "TYPE", CLASS_TYPE);
            break;
          case Type.INT:
            staticAdapter.getStatic(Type.getType(java.lang.Integer.class), "TYPE", CLASS_TYPE);
            break;
          case Type.LONG:
            staticAdapter.getStatic(Type.getType(java.lang.Long.class), "TYPE", CLASS_TYPE);
            break;
          case Type.SHORT:
            staticAdapter.getStatic(Type.getType(java.lang.Short.class), "TYPE", CLASS_TYPE);
            break;
          default:
          case Type.OBJECT:
            staticAdapter.push(t);
            break;
        }
        staticAdapter.arrayStore(CLASS_TYPE);
        index++;
      }
  
      // invoke the getMethod
      staticAdapter.invokeVirtual(CLASS_TYPE, new Method("getDeclaredMethod", METHOD_TYPE,
          new Type[] { STRING_TYPE, Type.getType(java.lang.Class[].class) }));
  
      // store the reflected method in the static field
      staticAdapter.putStatic(newClassType, methodStaticFieldName, METHOD_TYPE);
  
      /*
       * Stage 2 call the ih.invoke(this,supermethod,parms)
       */
  
      // load this to get the ih field
      methodAdapter.loadThis();
      // load the invocation handler from the field (the location of the
      // InvocationHandler.invoke)
      methodAdapter.getField(newClassType, IH_FIELD, IH_TYPE);
      // loadThis (the first arg of the InvocationHandler.invoke)
      methodAdapter.loadThis();
      // load the method to invoke (the second arg of the
      // InvocationHandler.invoke)
      methodAdapter.getStatic(newClassType, methodStaticFieldName, METHOD_TYPE);
      // load all the method arguments onto the stack as an object array (the
      // third arg of the InvocationHandler.invoke)
      methodAdapter.loadArgArray();
      // generate the invoke method
      Method invocationHandlerInvokeMethod = new Method("invoke", OBJECT_TYPE, new Type[] {
          OBJECT_TYPE, METHOD_TYPE, Type.getType(java.lang.Object[].class) });
      // call the invoke method of the invocation handler
      methodAdapter.invokeInterface(IH_TYPE, invocationHandlerInvokeMethod);
  
      /*
       * Stage 3 the returned object is now on the top of the stack We need to
       * check the type and cast as necessary
       */
      switch (returnType.getSort())
      {
        case Type.BOOLEAN:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Boolean.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.BOOLEAN_TYPE);
          break;
        case Type.BYTE:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Byte.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.BYTE_TYPE);
          break;
        case Type.CHAR:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Character.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.CHAR_TYPE);
          break;
        case Type.DOUBLE:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Double.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.DOUBLE_TYPE);
          break;
        case Type.FLOAT:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Float.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.FLOAT_TYPE);
          break;
        case Type.INT:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Integer.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.INT_TYPE);
          break;
        case Type.LONG:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Long.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.LONG_TYPE);
          break;
        case Type.SHORT:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Short.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.SHORT_TYPE);
          break;
        case Type.VOID:
          try {
            methodAdapter.cast(OBJECT_TYPE, Type.getType(Void.class));
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          methodAdapter.unbox(Type.VOID_TYPE);
          break;
        default:
        case Type.OBJECT:
          // in this case check the cast and cast the object to the return
          // type
          methodAdapter.checkCast(returnType);
          try {
            methodAdapter.cast(OBJECT_TYPE, returnType);
          } catch (IllegalArgumentException ex) {
            LOGGER.debug("Ignore cast exception caused by ASM 6.1 and try further", ex);
          }
          break;
      }
      // return the (appropriately cast) result of the invocation from the
      // stack
      methodAdapter.returnValue();
      // end the method
      methodAdapter.endMethod();
  
      LOGGER.debug(Constants.LOG_EXIT, "processMethod");
    }
  
    private void addClassStaticField(String classBinaryName)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "addClassStaticField",
          new Object[] { classBinaryName });
  
      currentClassFieldName = classBinaryName.replaceAll("\\.", "_");
  
      /*
       * use Class.forName on the superclass so we can reflectively find
       * methods later
       * 
       * produces bytecode for retrieving the superclass and storing in a
       * private static field: private static Class superClass = null; static{
       * superClass = Class.forName(superclass, true, TYPE_BEING_PROXIED.class.getClassLoader()); }
       */
  
      // add a private static field for the superclass Class
      cv.visitField(ACC_PRIVATE | ACC_STATIC, currentClassFieldName, CLASS_TYPE.getDescriptor(),
          null, null);
  
      // push the String arg for the Class.forName onto the stack
      staticAdapter.push(classBinaryName);
      //push the boolean arg for the Class.forName onto the stack
      staticAdapter.push(true);
      //get the classloader
      staticAdapter.push(newClassType);
      staticAdapter.invokeVirtual(CLASS_TYPE, new Method("getClassLoader", CLASSLOADER_TYPE, NO_ARGS));
  
      // invoke the Class forName putting the Class on the stack
      staticAdapter.invokeStatic(CLASS_TYPE, new Method("forName", CLASS_TYPE,
          new Type[] { STRING_TYPE, Type.BOOLEAN_TYPE, CLASSLOADER_TYPE }));
  
      // put the Class in the static field
      staticAdapter.putStatic(newClassType, currentClassFieldName, CLASS_TYPE);
  
      LOGGER.debug(Constants.LOG_ENTRY, "addClassStaticField");
    }
  
    private void setCurrentAnalysisClassFields(Class<?> aClass)
    {
      LOGGER.debug(Constants.LOG_ENTRY, "setCurrentAnalysisClassFields",
          new Object[] { aClass });
  
      currentlyAnalysedClassName = aClass.getName();
      currentlyAnalysedClass = aClass;
  
      LOGGER.debug(Constants.LOG_EXIT, "setCurrentAnalysisClassFields");
    }
  
    // we don't want to copy fields from the class into the proxy
    public FieldVisitor visitField(int access, String name, String desc, String signature,
        Object value)
    {
      return null;
    }
  
    // for now we don't do any processing in these methods
    public AnnotationVisitor visitAnnotation(String desc, boolean visible)
    {
      return null;
    }
  
    public void visitAttribute(Attribute attr)
    {
      // no-op
    }
  
    public void visitInnerClass(String name, String outerName, String innerName, int access)
    {
      // no-op
    }
  
    public void visitOuterClass(String owner, String name, String desc)
    {
      // no-op
    }
  }
  