330 lines
14 KiB
Java
330 lines
14 KiB
Java
/*
|
|
* 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 implements ClassVisitor, 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) {
|
|
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");
|
|
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");
|
|
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)) { // 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");
|
|
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);
|
|
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");
|
|
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);
|
|
// 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;");
|
|
|
|
// 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);
|
|
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, 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);
|
|
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) {
|
|
}
|
|
|
|
}
|
|
|
|
}
|