org.springsource.loaded.ExecutorBuilder Maven / Gradle / Ivy
/*
* 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.lang.reflect.Modifier;
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.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* The executor embodies the new implementation of the type after it has been reloaded.
*
* The executor is the class full of static methods that looks very like the original class.
*
* Methods. For each method in the original type we have a method in the executor, it has the same SourceFile attribute and
* the same local variable and line number details for debugging to work. Note the first variable will have been renamed from 'this'
* to 'thiz' to prevent the eclipse debugger crashing. All annotations from the new version will be copied to the methods on an
* executor.
*
* Fields. Fields are copied into the executor but only so that there is a place to hang the annotations off (so that they
* can be accessed through reflection).
*
* Constructors. Constructors are added to the executor as ___init___ methods, with the invokespecials within them
* transformed, either removed if they are calls to Object.<init> or mutated into ___init___ calls on the supertype instance.
*
* @author Andy Clement
* @since 0.5.0
*/
public class ExecutorBuilder {
private TypeRegistry typeRegistry;
ExecutorBuilder(TypeRegistry typeRegistry) {
this.typeRegistry = typeRegistry;
}
public byte[] createFor(ReloadableType reloadableType, String versionstamp, TypeDescriptor typeDescriptor, byte[] newVersionData) {
if (typeDescriptor == null) {
// must be reloadable or we would not be here - so can pass 'true'
typeDescriptor = typeRegistry.getExtractor().extract(newVersionData, true);
}
ClassReader fileReader = new ClassReader(newVersionData);
ExecutorBuilderVisitor executorVisitor = new ExecutorBuilderVisitor(reloadableType.getSlashedName(), versionstamp,
typeDescriptor);
fileReader.accept(executorVisitor, 0);
return executorVisitor.getBytes();
}
/**
* ClassVisitor that constructs the executor by visiting the original class. The basic goal is to visit the original class and
* 'copy' the methods into the executor, making adjustments as we go.
*/
static class ExecutorBuilderVisitor extends ClassVisitor implements Constants {
private ClassWriter cw = new ClassWriter(0);
private String classname;
private String suffix;
protected TypeDescriptor typeDescriptor;
public ExecutorBuilderVisitor(String classname, String suffix, TypeDescriptor typeDescriptor) {
super(ASM5);
this.classname = classname;
this.suffix = suffix;
this.typeDescriptor = typeDescriptor;
}
public byte[] getBytes() {
return cw.toByteArray();
}
public void visit(int version, int flags, String name, String signature, String superclassName, String[] interfaceNames) {
cw.visit(version, Opcodes.ACC_PUBLIC, Utils.getExecutorName(classname, suffix), null, "java/lang/Object", null);
}
// For type level annotation copying
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
AnnotationVisitor av = cw.visitAnnotation(desc, visible);
return new CopyingAnnotationVisitor(av);
}
// Fields are copied solely to provide a place to hang annotations
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
return cw.visitField(access, name, desc, signature, value);
}
// For each method, copy it into the new class making appropriate adjustments
public MethodVisitor visitMethod(int flags, String name, String descriptor, String signature, String[] exceptions) {
if (!Utils.isInitializer(name)) {
// method
if (!Modifier.isStatic(flags)) {
// For non static methods add the extra initial parameter which is 'this'
descriptor = Utils.insertExtraParameter(classname, descriptor);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
} else {
// If this static method would 'clash' with an instance method that has the extra parameter added then
// we have a couple of options to make them different:
// 1. tweak the name
// 2. tweak the parameters
MethodMember method = typeDescriptor.getByDescriptor(name, descriptor);
if (MethodMember.isClash(method)) {
name = "__" + name;
}
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, name, descriptor, signature, exceptions);
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
}
} else {
// constructor
if (name.charAt(1) != 'c') {
// regular constructor
// want to create the ___init___ handler for this constructor
// With the JDT compiler the inner class constructor gets an extra first parameter that is the type of
// containing class. But with javac the inner class constructor gets an extra first parameter that is of
// a special anonymous type (inner class of the containing class)
// For example: class Foo { class Bar {}}
// JDT: ctor in Bar is (Foo) {}
// JAVAC: ctor in Bar is (Foo$1) {}
descriptor = Utils.insertExtraParameter(classname, descriptor);
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mInitializerName, descriptor, signature, exceptions);
ConstructorCopier cc = new ConstructorCopier(mv, typeDescriptor, suffix, classname);
return cc;
} else {
// static initializer
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC_STATIC, mStaticInitializerName, descriptor, signature, exceptions);
return new MethodCopier(mv, typeDescriptor.isInterface(), descriptor, typeDescriptor, classname, suffix);
}
}
}
public void visitSource(String sourcefile, String debug) {
cw.visitSource(sourcefile, debug);
}
private static class CopyingAnnotationVisitor extends AnnotationVisitor {
private AnnotationVisitor av;
public CopyingAnnotationVisitor(AnnotationVisitor av) {
super(ASM5);
this.av = av;
}
public void visit(String name, Object value) {
av.visit(name, value);
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
AnnotationVisitor localav = av.visitAnnotation(name, desc);
return new CopyingAnnotationVisitor(localav);
}
public AnnotationVisitor visitArray(String name) {
AnnotationVisitor localav = av.visitArray(name);
return new CopyingAnnotationVisitor(localav);
}
public void visitEnd() {
av.visitEnd();
}
public void visitEnum(String name, String desc, String value) {
av.visitEnum(name, desc, value);
}
}
public void visitOuterClass(String arg0, String arg1, String arg2) {
// nothing to do
}
public void visitAttribute(Attribute attr) {
// nothing to do
}
public void visitEnd() {
// nothing to do
}
public void visitInnerClass(String arg0, String arg1, String arg2, int arg3) {
// nothing to do
}
}
}