Package org.springsource.loaded

Source Code of org.springsource.loaded.DispatcherBuilder$DispatcherBuilderVisitor

/*
* Copyright 2010-2012 VMware and contributors
*
* 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.springsource.loaded;

import java.util.ArrayList;
import java.util.List;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.springsource.loaded.Utils.ReturnType;


/**
* Builder that creates the dispatcher. The dispatcher is the implementation of the interface extracted for a type which then
* delegates to the executor. A new dispatcher (and executor) is built for each class reload.
*
* @author Andy Clement
* @since 0.5.0
*/
public class DispatcherBuilder {

  /**
   * Factory method that builds the dispatcher for a specified reloadabletype.
   *
   * @param rtype the reloadable type
   * @param newVersionTypeDescriptor the descriptor of the new version (the executor will be generated according to this)
   * @param versionstamp the suffix that should be appended to the generated dispatcher
   * @return the bytecode for the new dispatcher
   */
  public static byte[] createFor(ReloadableType rtype, IncrementalTypeDescriptor newVersionTypeDescriptor, String versionstamp) {
    ClassReader fileReader = new ClassReader(rtype.interfaceBytes);
    DispatcherBuilderVisitor dispatcherVisitor = new DispatcherBuilderVisitor(rtype, newVersionTypeDescriptor, versionstamp);
    fileReader.accept(dispatcherVisitor, 0);
    return dispatcherVisitor.getBytes();
  }

  /**
   * Whilst visiting the interface, the implementation is created.
   */
  static class DispatcherBuilderVisitor extends ClassVisitor implements Opcodes, Constants {

    private ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);

    private String classname;
    private String executorClassName;
    private String suffix;
    private ReloadableType rtype;
    private IncrementalTypeDescriptor typeDescriptor;

    public DispatcherBuilderVisitor(ReloadableType rtype, IncrementalTypeDescriptor typeDescriptor, String suffix) {
      super(ASM5);
      this.classname = rtype.getSlashedName();
      this.typeDescriptor = typeDescriptor;
      this.suffix = suffix;
      this.rtype = rtype;
      this.executorClassName = Utils.getExecutorName(classname, suffix);
    }

    public byte[] getBytes() {
      return cw.toByteArray();
    }

    public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
      String dispatcherName = Utils.getDispatcherName(classname, suffix);
      cw.visit(version, Opcodes.ACC_PUBLIC, dispatcherName, null, "java/lang/Object",
          new String[] { Utils.getInterfaceName(classname), "org/springsource/loaded/__DynamicallyDispatchable" });
      generateDefaultConstructor();
    }

    private void generateDefaultConstructor() {
      MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
      mv.visitCode();
      mv.visitVarInsn(ALOAD, 0);
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
      mv.visitInsn(RETURN);
      mv.visitMaxs(1, 1);
      mv.visitEnd();
    }

    private void generateClinitDispatcher() {
      MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, mStaticInitializerName, "()V", null, null);
      mv.visitCode();
      mv.visitMethodInsn(INVOKESTATIC, executorClassName, mStaticInitializerName, "()V", false);
      mv.visitInsn(RETURN);
      mv.visitMaxs(1, 1);
      mv.visitEnd();
    }

    public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) {
      return null;
    }

    public void visitAttribute(Attribute arg0) {
    }

    public void visitEnd() {
    }

    public FieldVisitor visitField(int arg0, String arg1, String arg2, String arg3, Object arg4) {
      return null;
    }

    public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
    }

    public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
      if (name.equals(mDynamicDispatchName)) {
        generateDynamicDispatchMethod(name, descriptor, signature, exceptions);
      } else if (!name.equals("<init>")) {
        generateRegularMethod(name, descriptor, signature, exceptions);
      }
      return null;
    }

    /**
     * Generate the body of the dynamic dispatcher method. This method is responsible for calling all the methods that are added
     * to a type after the first time it is defined.
     */
    private void generateDynamicDispatchMethod(String name, String descriptor, String signature, String[] exceptions) {
      final int indexDispatcherInstance = 0;
      final int indexArgs = 1;
      final int indexTarget = 2;
      final int indexNameAndDescriptor = 3;

      // Should be generating the code for each additional method in
      // the executor (new version) that wasn't in the original.
      MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC , name, descriptor, signature, exceptions);
      mv.visitCode();

      // Entries required here for all methods that exist in the new version but didn't exist in the original version
      // There should be no entries for catchers

      int maxStack = 0;
      // Basically generate a long if..else sequence for each method
      List<MethodMember> methods = new ArrayList<MethodMember>(typeDescriptor.getNewOrChangedMethods());

      // these are added because we may be calling through the dynamic dispatcher if calling from an invokeinterface - the invokeinterface
      // will call __execute on the interface, which is then implemented by the real class - but it may be that the
      // actual type implementing the interface already implements that method - if the dispatcher doesn't recognize
      // it then we may go bang

      //      System.out.println("Generating __execute in type " + classname);
      for (MethodMember m : typeDescriptor.getOriginal().getMethods()) {
        methods.add(m);
      }

      for (MethodMember method : methods) {
        if (MethodMember.isCatcher(method) || MethodMember.isSuperDispatcher(method)) { // for reason above, may also need to consider catchers here - what if an interface is changed to add a toString() method, for example
          continue;
          // would the implementation for a catcher call the super catcher?
        }
        //        System.out.println("Generating handler for " + method.name);
        String nameWithDescriptor = new StringBuilder(method.name).append(method.descriptor).toString();

        // 2. Load the input name+descriptor and compare it with this method:
        mv.visitVarInsn(ALOAD, 3);
        mv.visitLdcInsn(nameWithDescriptor);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
        Label label = new Label();
        mv.visitJumpInsn(IFEQ, label); // means if false

        // 3. Generate the code that will call the method on the executor:
        if (!method.isStatic()) {
          mv.visitVarInsn(Opcodes.ALOAD, 2);
          mv.visitTypeInsn(CHECKCAST, classname);
        }
        String callDescriptor = method.isStatic() ? method.descriptor : Utils.insertExtraParameter(classname,
            method.descriptor);

        int pcount = Utils.getParameterCount(method.descriptor);
        if (pcount > maxStack) {
          pcount = maxStack;
        }

        // 4. Unpack parameter array to fit the descriptor for that method
        Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, method.descriptor, 1);

        ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, method.name, callDescriptor, false);
        if (returnType.isVoid()) {
          mv.visitInsn(ACONST_NULL);
        } else if (returnType.isPrimitive()) {
          Utils.insertBoxInsns(mv, returnType.descriptor);
        }
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitLabel(label);
      }
      for (MethodMember ctor : typeDescriptor.getLatestTypeDescriptor().getConstructors()) {
        String nameWithDescriptor = new StringBuilder(ctor.name).append(ctor.descriptor).toString();

        // 2. Load the input name+descriptor and compare it with this method:
        //    if (nameAndDescriptor.equals(xxx)) {
        mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
        mv.visitLdcInsn(nameWithDescriptor);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
        Label label = new Label();
        mv.visitJumpInsn(IFEQ, label); // means if false

        // 3. Generate the code that will call the method on the executor:
        mv.visitVarInsn(Opcodes.ALOAD, 2);
        mv.visitTypeInsn(CHECKCAST, classname);
        String callDescriptor = Utils.insertExtraParameter(classname, ctor.descriptor);

        int pcount = Utils.getParameterCount(ctor.descriptor);
        if (pcount > maxStack) {
          pcount = maxStack;
        }

        // 4. Unpack parameter array to fit the descriptor for that method
        Utils.generateInstructionsToUnpackArrayAccordingToDescriptor(mv, ctor.descriptor, 1);

        //        ReturnType returnType = Utils.getReturnTypeDescriptor(method.descriptor);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, executorClassName, "___init___", callDescriptor, false);
        //        if (returnType.isVoid()) {
        mv.visitInsn(ACONST_NULL);
        //        } else if (returnType.isPrimitive()) {
        //          Utils.insertBoxInsns(mv, returnType.descriptor);
        //        }
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitLabel(label);
      }

      // 5. Throw exception as dynamic dispatcher has been called for something it shouldn't have

      // At this point we failed to find it as a method we can dispatch to our executor, so we want
      // to pass it 'up' to our supertype.  We need to get the dispatcher for our superclass
      // and then call the __execute() on it, assuming that it will be able to handle this request.   

      // alternative 1: use the dispatcher for the superclass

      // Determine the supertype
      String slashedSupertypeName = rtype.getTypeDescriptor().getSupertypeName();

      // getDispatcher will give us the dispatcher for the supertype
      mv.visitFieldInsn(Opcodes.GETSTATIC, slashedSupertypeName, fReloadableTypeFieldName, lReloadableType);
      mv.visitMethodInsn(INVOKEVIRTUAL, tReloadableType, "getDispatcher",
          "()Lorg/springsource/loaded/__DynamicallyDispatchable;", false);

      // alternative 2: find the right dispatcher - i.e. who in the super hierarchy provides that nameAndDescriptor

      // now invoke the dynamic dispatch call on that dispatcher
      mv.visitVarInsn(ALOAD, indexArgs);
      mv.visitVarInsn(ALOAD, indexTarget);
      mv.visitVarInsn(ALOAD, indexNameAndDescriptor);
      mv.visitMethodInsn(INVOKEINTERFACE, tDynamicallyDispatchable, mDynamicDispatchName, mDynamicDispatchDescriptor, false);
      mv.visitInsn(ARETURN);

      //      mv.visitTypeInsn(NEW, "java/lang/IllegalStateException");
      //      mv.visitInsn(DUP);
      //      mv.visitVarInsn(ALOAD, 3);
      //      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V");
      //      mv.visitInsn(ATHROW);
      mv.visitMaxs(maxStack, 6);
      mv.visitEnd();
    }

    /**
     * Called to generate the implementation of a normal method on the interface - a normal method is one that did exist when
     * the type was first defined. Might be a catcher.
     */
    private void generateRegularMethod(String name, String descriptor, String signature, String[] exceptions) {
      // The original descriptor is how it was defined on the original type and how it is defined in the executor class.
      // The original descriptor is this descriptor with the first parameter trimmed off.
      boolean isClinit = name.equals("___clinit___");
      String originalDescriptor = isClinit ? descriptor : Utils.stripFirstParameter(descriptor);
      MethodMember method = null;

      // Detect if the name has been modified for clash avoidance reasons
      if (name.equals("___init___")) {
        // it is a ctor
        method = rtype.getConstructor(originalDescriptor);
      } else {
        if (isClinit) {
          generateClinitDispatcher();
          return;
        } else {
          // TODO need a better solution that these __
          if (name.startsWith("__") && !name.equals("__$swapInit")) { // __$swapInit is the groovy reset method
            // clash avoidance name
            method = rtype.getMethod(name.substring(2), originalDescriptor);
          } else {
            method = rtype.getMethod(name, originalDescriptor);
          }
        }
      }
      boolean isStatic = method.isStatic();

      MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name, descriptor, signature, exceptions);
      mv.visitCode();
      // The input descriptor will include the extra initial parameter (the instance, or null for static methods)
      ReturnType returnTypeDescriptor = Utils.getReturnTypeDescriptor(descriptor);
      // For a static method the first parameter can be ignored
      int params = Utils.getParameterCount(descriptor);
      String callDescriptor = isStatic ? originalDescriptor : descriptor;
      Utils.createLoadsBasedOnDescriptor(mv, callDescriptor, isStatic ? 2 : 1);
      mv.visitMethodInsn(INVOKESTATIC, executorClassName, name, callDescriptor, false);
      Utils.addCorrectReturnInstruction(mv, returnTypeDescriptor, false);
      mv.visitMaxs(params, params + 1);
      mv.visitEnd();
    }

    public void visitOuterClass(String arg0, String arg1, String arg2) {
    }

    public void visitSource(String arg0, String arg1) {
    }

  }

}
TOP

Related Classes of org.springsource.loaded.DispatcherBuilder$DispatcherBuilderVisitor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.