/* | |
* 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 org.apache.aries.proxy.impl.ProxyUtils.JAVA_CLASS_VERSION; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.Serializable; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Set; | |
import java.util.UUID; | |
import java.util.Map.Entry; | |
import java.util.concurrent.Callable; | |
import org.apache.aries.proxy.InvocationListener; | |
import org.apache.aries.proxy.UnableToProxyException; | |
import org.apache.aries.proxy.impl.NLS; | |
import org.apache.aries.proxy.impl.SystemModuleClassLoader; | |
import org.apache.aries.proxy.impl.gen.Constants; | |
import org.apache.aries.proxy.weaving.WovenProxy; | |
import org.objectweb.asm.ClassReader; | |
import org.objectweb.asm.ClassVisitor; | |
import org.objectweb.asm.FieldVisitor; | |
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.AdviceAdapter; | |
import org.objectweb.asm.commons.GeneratorAdapter; | |
import org.objectweb.asm.commons.Method; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
/** | |
* This abstract superclass is responsible for providing proxy extensions to | |
* classes being written. Classes processed by this adapter will implement | |
* {@link WovenProxy}, and have a static initialiser that populates | |
* {@link java.lang.reflect.Method} fields for use with the | |
* {@link InvocationListener}. Known subclasses are WovenProxyAdapter, | |
* used to weave classes being loaded by the framework, and InterfaceCombiningClassAdapter | |
* which is used to dynamically create objects that implement multiple interfaces | |
*/ | |
public abstract class AbstractWovenProxyAdapter extends ClassVisitor implements Opcodes { | |
private static final Logger LOGGER = LoggerFactory | |
.getLogger(AbstractWovenProxyAdapter.class); | |
/** Access modifier for a public generated method */ | |
private static final int PUBLIC_GENERATED_METHOD_ACCESS = ACC_PUBLIC | ACC_FINAL | |
| ACC_SYNTHETIC; | |
/** The internal name for Throwable */ | |
static final String THROWABLE_INAME = Type.getInternalName(Throwable.class); | |
/** | |
* A static UUID for adding to our method names. | |
* This must not change over time, otherwise uninstalling | |
* and reinstalling the proxy component with a separate | |
* API bundle will cause BIG trouble (NoSuchFieldError) | |
* with subclasses that get woven by the "new" hook | |
*/ | |
private static final String UU_ID = "04df3c80_2877_4f6c_99e2_5a25e11d5535"; | |
/** A constant for No Args methods */ | |
static final Type[] NO_ARGS = new Type[0]; | |
/** The annotation types we should add to generated methods and fields */ | |
private static final String[] annotationTypeDescriptors = new String[] { "Ljavax/persistence/Transient;" }; | |
/** the name of the field used to store the {@link InvocationListener} */ | |
protected static final String LISTENER_FIELD = "org_apache_aries_proxy_InvocationListener_" | |
+ UU_ID; | |
/** the name of the field used to store the dispatcher */ | |
public static final String DISPATCHER_FIELD = "woven_proxy_dispatcher_" + UU_ID; | |
/* Useful ASM types */ | |
/** The ASM type for the {@link InvocationListener} */ | |
static final Type LISTENER_TYPE = Type.getType(InvocationListener.class); | |
/** The ASM type for the dispatcher */ | |
public static final Type DISPATCHER_TYPE = Type.getType(Callable.class); | |
private static final Type CLASS_TYPE = Type.getType(Class.class); | |
private static final Type CLASS_ARRAY_TYPE = Type.getType(Class[].class); | |
private static final Type STRING_TYPE = Type.getType(String.class); | |
public static final Type OBJECT_TYPE = Type.getType(Object.class); | |
static final Type METHOD_TYPE = Type.getType(java.lang.reflect.Method.class); | |
/** The {@link Type} of the {@link WovenProxy} interface */ | |
static final Type WOVEN_PROXY_IFACE_TYPE = Type.getType(WovenProxy.class); | |
private static final Type NPE_TYPE = Type.getType(NullPointerException.class); | |
private static final Type[] DISPATCHER_LISTENER_METHOD_ARGS = new Type[] { | |
DISPATCHER_TYPE, LISTENER_TYPE }; | |
private static final Method ARGS_CONSTRUCTOR = new Method("<init>", Type.VOID_TYPE, | |
DISPATCHER_LISTENER_METHOD_ARGS); | |
private static final Method NO_ARGS_CONSTRUCTOR = new Method("<init>", Type.VOID_TYPE, | |
NO_ARGS); | |
private static final Method NPE_CONSTRUCTOR = new Method("<init>", Type.VOID_TYPE, | |
new Type[] {STRING_TYPE}); | |
// other new methods we will need | |
static final Method getInovcationTargetMethod = new Method( | |
"getInvocationTarget" + UU_ID, OBJECT_TYPE, NO_ARGS); | |
static final Method listenerPreInvokeMethod = new Method("getListener" | |
+ UU_ID, OBJECT_TYPE, new Type[] { OBJECT_TYPE, | |
Type.getType(java.lang.reflect.Method.class), | |
Type.getType(Object[].class) }); | |
/* Instance fields */ | |
/** The type of this class */ | |
protected final Type typeBeingWoven; | |
/** The type of this class's super */ | |
private Type superType; | |
/** The {@link ClassLoader} loading this class */ | |
private final ClassLoader loader; | |
/** | |
* A flag to indicate that we need to weave WovenProxy methods into this class | |
*/ | |
private boolean implementWovenProxy = false; | |
/** | |
* A list of un-woven superclasses between this object and {@link Object}, | |
* only populated for classes which will directly implement {@link WovenProxy}. | |
* This list is then used to override any methods that would otherwise be missed | |
* by the weaving process. | |
*/ | |
protected final List<Class<?>> nonObjectSupers = new ArrayList<Class<?>>(); | |
/** | |
* Methods we have transformed and need to create static fields for. | |
* Stored as field name to {@link TypeMethod} so we know which Class to reflect | |
* them off | |
*/ | |
protected final Map<String, TypeMethod> transformedMethods = new HashMap<String, TypeMethod>(); | |
/** | |
* A set of {@link Method} objects identifying the methods that are in this | |
* class. This is used to prevent us duplicating methods copied from | |
* {@link AbstractWovenProxyAdapter#nonObjectSupers} that are already overridden in | |
* this class. | |
*/ | |
private final Set<Method> knownMethods = new HashSet<Method>(); | |
/** | |
* If our super does not have a no-args constructor then we need to be clever | |
* when writing our own constructor. | |
*/ | |
private boolean superHasNoArgsConstructor = false; | |
/** | |
* If we have a no-args constructor then we can delegate there rather than | |
* to a super no-args | |
*/ | |
private boolean hasNoArgsConstructor = false; | |
/** | |
* If we have a no-args constructor then we can delegate there rather than | |
* to a super no-args | |
*/ | |
protected boolean isSerializable = false; | |
/** | |
* The default static initialization method where we will write the proxy init | |
* code. If there is an existing <clinit> then we will change this and write a | |
* static_init_UUID instead (see the overriden | |
* {@link #visitMethod(int, String, String, String, String[])} | |
* for where this swap happens). See also {@link #writeStaticInitMethod()} for | |
* where the method is actually written. | |
*/ | |
private Method staticInitMethod = new Method("<clinit>", Type.VOID_TYPE, NO_ARGS); | |
/** | |
* The default access flags for the staticInitMethod. If we find an existing | |
* <clinit> then we will write a static_init_UUID method and add the ACC_PRIVATE_FLAG. | |
* See the overriden {@link #visitMethod(int, String, String, String, String[])} | |
* for where this flag is added. See also {@link #writeStaticInitMethod()} for | |
* where the method is actually written. | |
*/ | |
private int staticInitMethodFlags = ACC_SYNTHETIC | ACC_PRIVATE | ACC_STATIC; | |
protected Type currentMethodDeclaringType; | |
protected boolean currentMethodDeclaringTypeIsInterface; | |
public static final boolean IS_AT_LEAST_JAVA_6 = JAVA_CLASS_VERSION >= Opcodes.V1_6; | |
/** | |
* Create a new adapter for the supplied class | |
* | |
* @param writer | |
* The ClassWriter to delegate to | |
* @param className | |
* The name of this class | |
* @param loader | |
* The ClassLoader loading this class | |
*/ | |
public AbstractWovenProxyAdapter(ClassVisitor writer, String className, | |
ClassLoader loader) { | |
super(Opcodes.ASM5, writer); | |
typeBeingWoven = Type.getType("L" + className.replace('.', '/') + ";"); | |
//By default we expect to see methods from a concrete class | |
currentMethodDeclaringType = typeBeingWoven; | |
currentMethodDeclaringTypeIsInterface = false; | |
this.loader = loader; | |
} | |
public final 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 }); | |
// always update to the most recent version of the JVM | |
version = JAVA_CLASS_VERSION; | |
superType = Type.getType("L" + superName + ";"); | |
try { | |
// we only want to implement WovenProxy once in the hierarchy. | |
// It's best to do this as high up as possible so we check the | |
// super. By loading it we may end up weaving it, but that's a good thing! | |
Class<?> superClass = Class.forName(superName.replace('/', '.'), false, | |
loader); | |
isSerializable = Serializable.class.isAssignableFrom(superClass) || | |
Arrays.asList(interfaces).contains(Type.getInternalName(Serializable.class)) || | |
checkInterfacesForSerializability(interfaces); | |
if (!!!WovenProxy.class.isAssignableFrom(superClass)) { | |
// We have found a type we need to add WovenProxy information to | |
implementWovenProxy = true; | |
if(superClass != Object.class) { | |
//If our superclass isn't Object, it means we didn't weave all the way | |
//to the top of the hierarchy. This means we need to override all the | |
//methods defined on our parent so that they can be intercepted! | |
nonObjectSupers.add(superClass); | |
Class<?> nextSuper = superClass.getSuperclass(); | |
while(nextSuper != Object.class) { | |
nonObjectSupers.add(nextSuper); | |
nextSuper = nextSuper.getSuperclass(); | |
} | |
//Don't use reflection - it can be dangerous | |
superHasNoArgsConstructor = superHasNoArgsConstructor(superName, name); | |
} else { | |
superHasNoArgsConstructor = true; | |
} | |
// re-work the interfaces list to include WovenProxy | |
String[] interfacesPlusWovenProxy = new String[interfaces.length + 1]; | |
System.arraycopy(interfaces, 0, interfacesPlusWovenProxy, 0, interfaces.length); | |
interfacesPlusWovenProxy[interfaces.length] = WOVEN_PROXY_IFACE_TYPE.getInternalName(); | |
// Write the class header including WovenProxy. | |
cv.visit(version, access, name, signature, superName, interfacesPlusWovenProxy); | |
} else { | |
// Already has a woven proxy parent, but we still need to write the | |
// header! | |
cv.visit(version, access, name, signature, superName, interfaces); | |
} | |
} catch (ClassNotFoundException e) { | |
// If this happens we're about to hit bigger trouble on verify, so we | |
// should stop weaving and fail. Make sure we don't cause the hook to | |
// throw an error though. | |
UnableToProxyException u = new UnableToProxyException(name, e); | |
throw new RuntimeException(NLS.MESSAGES.getMessage("cannot.load.superclass", superName.replace('/', '.'), typeBeingWoven.getClassName()), u); | |
} | |
} | |
/** | |
* This method allows us to determine whether a superclass has a | |
* non-private no-args constructor without causing it to initialize. | |
* This avoids a potential ClassCircularityError on Mac VMs if the | |
* initialization references the subclass being woven. Odd, but seen | |
* in the wild! | |
*/ | |
private final boolean superHasNoArgsConstructor(String superName, String name) { | |
ConstructorFinder cf = new ConstructorFinder(); | |
try { | |
InputStream is = loader.getResourceAsStream(superName +".class"); | |
if(is == null) | |
throw new IOException(); | |
new ClassReader(is).accept(cf, ClassReader.SKIP_FRAMES + ClassReader.SKIP_DEBUG + ClassReader.SKIP_CODE); | |
} catch (IOException ioe) { | |
UnableToProxyException u = new UnableToProxyException(name, ioe); | |
throw new RuntimeException(NLS.MESSAGES.getMessage("cannot.load.superclass", superName.replace('/', '.'), typeBeingWoven.getClassName()), u); | |
} | |
return cf.hasNoArgsConstructor(); | |
} | |
private boolean checkInterfacesForSerializability(String[] interfaces) throws ClassNotFoundException { | |
for(String iface : interfaces) | |
{ | |
if(Serializable.class.isAssignableFrom(Class.forName( | |
iface.replace('/', '.'), false, loader))) | |
return true; | |
} | |
return false; | |
} | |
/** | |
* This method is called on each method implemented on this object (but not | |
* for superclass methods) Each of these methods is visited in turn and the | |
* code here generates the byte code for the calls to the InovcationListener | |
* around the existing method | |
*/ | |
public final 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 }); | |
Method currentMethod = new Method(name, desc); | |
getKnownMethods().add(currentMethod); | |
MethodVisitor methodVisitorToReturn = null; | |
// Only weave "real" instance methods. Not constructors, initializers or | |
// compiler generated ones. | |
if ((access & (ACC_STATIC | ACC_PRIVATE | ACC_SYNTHETIC | |
| ACC_NATIVE | ACC_BRIDGE)) == 0 && !!!name.equals("<init>") && | |
!!!name.equals("<clinit>")) { | |
// found a method we should weave | |
//Create a field name and store it for later | |
String methodStaticFieldName = "methodField" + getSanitizedUUIDString(); | |
transformedMethods.put(methodStaticFieldName, new TypeMethod( | |
currentMethodDeclaringType, currentMethod)); | |
// Surround the MethodVisitor with our weaver so we can manipulate the code | |
methodVisitorToReturn = getWeavingMethodVisitor(access, name, desc, | |
signature, exceptions, currentMethod, methodStaticFieldName, | |
currentMethodDeclaringType, currentMethodDeclaringTypeIsInterface); | |
} else if (name.equals("<clinit>")){ | |
//there is an existing clinit method, change the fields we use | |
//to write our init code to static_init_UUID instead | |
staticInitMethod = new Method("static_init_" + UU_ID, Type.VOID_TYPE, NO_ARGS); | |
staticInitMethodFlags = staticInitMethodFlags | ACC_FINAL; | |
methodVisitorToReturn = new AdviceAdapter(Opcodes.ASM5, cv.visitMethod(access, name, desc, signature, | |
exceptions), access, name, desc){ | |
@Override | |
protected void onMethodEnter() | |
{ | |
//add into the <clinit> a call to our synthetic static_init_UUID | |
invokeStatic(typeBeingWoven, staticInitMethod); | |
super.onMethodEnter(); | |
} | |
}; | |
} else { | |
if(currentMethod.getArgumentTypes().length == 0 && name.equals("<init>")) | |
hasNoArgsConstructor = true; | |
//This isn't a method we want to weave, so just get the default visitor | |
methodVisitorToReturn = cv.visitMethod(access, name, desc, signature, | |
exceptions); | |
} | |
LOGGER.debug(Constants.LOG_EXIT, "visitMethod", methodVisitorToReturn); | |
return methodVisitorToReturn; | |
} | |
/** | |
* Our class may claim to implement WovenProxy, but doesn't have any | |
* implementations! We should fix this. | |
*/ | |
public void visitEnd() { | |
LOGGER.debug(Constants.LOG_ENTRY, "visitEnd"); | |
for(Class<?> c : nonObjectSupers) { | |
setCurrentMethodDeclaringType(Type.getType(c), false); | |
try { | |
readClass(c, new MethodCopyingClassAdapter(this, loader, c, typeBeingWoven, | |
getKnownMethods(), transformedMethods)); | |
} catch (IOException e) { | |
// This should never happen! <= famous last words (not) | |
throw new RuntimeException(NLS.MESSAGES.getMessage("unexpected.error.processing.class", c.getName(), typeBeingWoven.getClassName()), e); | |
} | |
} | |
// If we need to implement woven proxy in this class then write the methods | |
if (implementWovenProxy) { | |
writeFinalWovenProxyMethods(); | |
} | |
// this method is called when we reach the end of the class | |
// so it is time to make sure the static initialiser method is written | |
writeStaticInitMethod(); | |
// Make sure we add the instance specific WovenProxy method to our class, | |
// and give ourselves a constructor to use | |
writeCreateNewProxyInstanceAndConstructor(); | |
// now delegate to the cv | |
cv.visitEnd(); | |
LOGGER.debug(Constants.LOG_EXIT, "visitEnd"); | |
} | |
public Set<Method> getKnownMethods() { | |
return knownMethods; | |
} | |
/** | |
* Get the {@link MethodVisitor} that will weave a given method | |
* @param access | |
* @param name | |
* @param desc | |
* @param signature | |
* @param exceptions | |
* @param currentMethod | |
* @param methodStaticFieldName | |
* @return | |
*/ | |
protected abstract MethodVisitor getWeavingMethodVisitor(int access, String name, | |
String desc, String signature, String[] exceptions, Method currentMethod, | |
String methodStaticFieldName, Type currentMethodDeclaringType, | |
boolean currentMethodDeclaringTypeIsInterface); | |
/** | |
* Write the methods we need for wovenProxies on the highest supertype | |
*/ | |
private final void writeFinalWovenProxyMethods() { | |
// add private fields for the Callable<Object> dispatcher | |
// and InvocationListener. These aren't static because we can have | |
// multiple instances of the same proxy class. These should not be | |
// serialized, or used in JPA or any other thing we can think of, | |
// so we annotate them as necessary | |
generateField(DISPATCHER_FIELD, Type.getDescriptor(Callable.class)); | |
generateField(LISTENER_FIELD, Type.getDescriptor(InvocationListener.class)); | |
// a general methodAdapter field that we will use to with GeneratorAdapters | |
// to create the methods required to implement WovenProxy | |
GeneratorAdapter methodAdapter; | |
// add a method for unwrapping the dispatcher | |
methodAdapter = getMethodGenerator(PUBLIC_GENERATED_METHOD_ACCESS, new Method( | |
"org_apache_aries_proxy_weaving_WovenProxy_unwrap", DISPATCHER_TYPE, | |
NO_ARGS)); | |
// ///////////////////////////////////////////////////// | |
// Implement the method | |
// load this to get the field | |
methodAdapter.loadThis(); | |
// get the dispatcher field and return | |
methodAdapter.getField(typeBeingWoven, DISPATCHER_FIELD, DISPATCHER_TYPE); | |
methodAdapter.returnValue(); | |
methodAdapter.endMethod(); | |
// ///////////////////////////////////////////////////// | |
// add a method for checking if the dispatcher is set | |
methodAdapter = getMethodGenerator(PUBLIC_GENERATED_METHOD_ACCESS, new Method( | |
"org_apache_aries_proxy_weaving_WovenProxy_isProxyInstance", | |
Type.BOOLEAN_TYPE, NO_ARGS)); | |
// ///////////////////////////////////////////////////// | |
// Implement the method | |
// load this to get the field | |
methodAdapter.loadThis(); | |
// make a label for return true | |
Label returnTrueLabel = methodAdapter.newLabel(); | |
// get the dispatcher field for the stack | |
methodAdapter.getField(typeBeingWoven, DISPATCHER_FIELD, DISPATCHER_TYPE); | |
// check if the dispatcher was non-null and goto return true if it was | |
methodAdapter.ifNonNull(returnTrueLabel); | |
methodAdapter.loadThis(); | |
// get the listener field for the stack | |
methodAdapter.getField(typeBeingWoven, LISTENER_FIELD, LISTENER_TYPE); | |
// check if the listener field was non-null and goto return true if it was | |
methodAdapter.ifNonNull(returnTrueLabel); | |
// return false if we haven't jumped anywhere | |
methodAdapter.push(false); | |
methodAdapter.returnValue(); | |
// mark the returnTrueLable | |
methodAdapter.mark(returnTrueLabel); | |
methodAdapter.push(true); | |
methodAdapter.returnValue(); | |
// end the method | |
methodAdapter.endMethod(); | |
// /////////////////////////////////////////////////////// | |
} | |
/** | |
* We write createNewProxyInstance separately because it isn't final, and is | |
* overridden on each class, we also write a constructor for this method to | |
* use if we don't have one. | |
*/ | |
private final void writeCreateNewProxyInstanceAndConstructor() { | |
GeneratorAdapter methodAdapter = getMethodGenerator(ACC_PUBLIC, new Method( | |
"org_apache_aries_proxy_weaving_WovenProxy_createNewProxyInstance", | |
WOVEN_PROXY_IFACE_TYPE, DISPATCHER_LISTENER_METHOD_ARGS)); | |
// ///////////////////////////////////////////////////// | |
// Implement the method | |
// Create and instantiate a new instance, then return it | |
methodAdapter.newInstance(typeBeingWoven); | |
methodAdapter.dup(); | |
methodAdapter.loadArgs(); | |
methodAdapter.invokeConstructor(typeBeingWoven, new Method("<init>", | |
Type.VOID_TYPE, DISPATCHER_LISTENER_METHOD_ARGS)); | |
methodAdapter.returnValue(); | |
methodAdapter.endMethod(); | |
////////////////////////////////////////////////////////// | |
// Write a protected no-args constructor for this class | |
methodAdapter = getMethodGenerator(ACC_PROTECTED | ACC_SYNTHETIC, ARGS_CONSTRUCTOR); | |
// ///////////////////////////////////////////////////// | |
// Implement the constructor | |
// For the top level supertype we need to invoke a no-args super, on object | |
//if we have to | |
if(implementWovenProxy) { | |
methodAdapter.loadThis(); | |
if (superHasNoArgsConstructor) | |
methodAdapter.invokeConstructor(superType, NO_ARGS_CONSTRUCTOR); | |
else { | |
if(hasNoArgsConstructor) | |
methodAdapter.invokeConstructor(typeBeingWoven, NO_ARGS_CONSTRUCTOR); | |
else | |
throw new RuntimeException(new UnableToProxyException(typeBeingWoven.getClassName(), | |
NLS.MESSAGES.getMessage("type.lacking.no.arg.constructor", typeBeingWoven.getClassName(), superType.getClassName()))); | |
} | |
methodAdapter.loadThis(); | |
methodAdapter.loadArg(0); | |
methodAdapter.putField(typeBeingWoven, DISPATCHER_FIELD, DISPATCHER_TYPE); | |
methodAdapter.loadThis(); | |
methodAdapter.loadArg(1); | |
methodAdapter.putField(typeBeingWoven, LISTENER_FIELD, LISTENER_TYPE); | |
} else { | |
//We just invoke the super with args | |
methodAdapter.loadThis(); | |
methodAdapter.loadArgs(); | |
methodAdapter.invokeConstructor(superType, ARGS_CONSTRUCTOR); | |
} | |
//Throw an NPE if the dispatcher is null, return otherwise | |
methodAdapter.loadArg(0); | |
Label returnValue = methodAdapter.newLabel(); | |
methodAdapter.ifNonNull(returnValue); | |
methodAdapter.newInstance(NPE_TYPE); | |
methodAdapter.dup(); | |
methodAdapter.push("The dispatcher must never be null!"); | |
methodAdapter.invokeConstructor(NPE_TYPE, NPE_CONSTRUCTOR); | |
methodAdapter.throwException(); | |
methodAdapter.mark(returnValue); | |
methodAdapter.returnValue(); | |
methodAdapter.endMethod(); | |
////////////////////////////////////////////////////////// | |
} | |
/** | |
* Create fields and an initialiser for {@link java.lang.reflect.Method} | |
* objects in our class | |
*/ | |
private final void writeStaticInitMethod() { | |
// we create a static field for each method we encounter with a *unique* | |
// random name | |
// since each method needs to be stored individually | |
for (String methodStaticFieldName : transformedMethods.keySet()) { | |
// add a private static field for the method | |
cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL | ACC_SYNTHETIC, | |
methodStaticFieldName, METHOD_TYPE.getDescriptor(), null, null) | |
.visitEnd(); | |
} | |
GeneratorAdapter staticAdapter = new GeneratorAdapter(staticInitMethodFlags, | |
staticInitMethod, null, null, cv); | |
for (Entry<String, TypeMethod> entry : transformedMethods.entrySet()) { | |
// Add some more code to the static initializer | |
TypeMethod m = entry.getValue(); | |
Type[] targetMethodParameters = m.method.getArgumentTypes(); | |
String methodStaticFieldName = entry.getKey(); | |
Label beginPopulate = staticAdapter.newLabel(); | |
Label endPopulate = staticAdapter.newLabel(); | |
Label catchHandler = staticAdapter.newLabel(); | |
staticAdapter.visitTryCatchBlock(beginPopulate, endPopulate, | |
catchHandler, THROWABLE_INAME); | |
staticAdapter.mark(beginPopulate); | |
staticAdapter.push(m.declaringClass); | |
// push the method name string arg onto the stack | |
staticAdapter.push(m.method.getName()); | |
// 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); | |
staticAdapter.push(t); | |
staticAdapter.arrayStore(CLASS_TYPE); | |
index++; | |
} | |
// invoke the getMethod | |
staticAdapter.invokeVirtual(CLASS_TYPE, | |
new Method("getDeclaredMethod", METHOD_TYPE, new Type[] { | |
STRING_TYPE, CLASS_ARRAY_TYPE})); | |
// store the reflected method in the static field | |
staticAdapter.putStatic(typeBeingWoven, methodStaticFieldName, | |
METHOD_TYPE); | |
Label afterCatch = staticAdapter.newLabel(); | |
staticAdapter.mark(endPopulate); | |
staticAdapter.goTo(afterCatch); | |
staticAdapter.mark(catchHandler); | |
// We don't care about the exception, so pop it off | |
staticAdapter.pop(); | |
// store the reflected method in the static field | |
staticAdapter.visitInsn(ACONST_NULL); | |
staticAdapter.putStatic(typeBeingWoven, methodStaticFieldName, | |
METHOD_TYPE); | |
staticAdapter.mark(afterCatch); | |
} | |
staticAdapter.returnValue(); | |
staticAdapter.endMethod(); | |
} | |
/** | |
* Get a new UUID suitable for use in method and field names | |
* | |
* @return | |
*/ | |
public static final String getSanitizedUUIDString() { | |
return UUID.randomUUID().toString().replace('-', '_'); | |
} | |
/** | |
* This method will read the bytes for the supplied {@link Class} using the | |
* supplied ASM {@link ClassVisitor}, the reader will skip DEBUG, FRAMES and CODE. | |
* @param c | |
* @param adapter | |
* @throws IOException | |
*/ | |
public static void readClass(Class<?> c, ClassVisitor adapter) throws IOException { | |
String className = c.getName().replace(".", "/") + ".class"; | |
//Load the class bytes and copy methods across | |
ClassLoader loader = c.getClassLoader(); | |
if (loader == null) { | |
//system class, use SystemModuleClassLoader as fallback | |
loader = new SystemModuleClassLoader(); | |
} | |
ClassReader cReader = new ClassReader(loader.getResourceAsStream(className)); | |
cReader.accept(adapter, ClassReader.SKIP_CODE | | |
ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); | |
} | |
/** | |
* Generate an instance field that should be "invisible" to normal code | |
* | |
* @param fieldName | |
* @param fieldDescriptor | |
*/ | |
private final void generateField(String fieldName, String fieldDescriptor) { | |
FieldVisitor fv = cv.visitField(ACC_PROTECTED | ACC_TRANSIENT | ACC_SYNTHETIC | |
| ACC_FINAL, fieldName, fieldDescriptor, null, null); | |
for (String s : annotationTypeDescriptors) | |
fv.visitAnnotation(s, true).visitEnd(); | |
fv.visitEnd(); | |
} | |
/** | |
* Get a generator for a method, this be annotated with the "invisibility" | |
* annotations (and ensured synthetic) | |
* | |
* @param methodSignature | |
* @return | |
*/ | |
private final GeneratorAdapter getMethodGenerator(int access, Method method) { | |
access = access | ACC_SYNTHETIC; | |
GeneratorAdapter ga = new GeneratorAdapter(access, method, null, null, cv); | |
for (String s : annotationTypeDescriptors) | |
ga.visitAnnotation(s, true).visitEnd(); | |
ga.visitCode(); | |
return ga; | |
} | |
public final void setCurrentMethodDeclaringType(Type type, boolean isInterface) { | |
currentMethodDeclaringType = type; | |
currentMethodDeclaringTypeIsInterface = isInterface; | |
} | |
} |