blob: a2f21c3d001842fc603639c251163794c678bf1d [file] [log] [blame]
/*
* Copyright 2002,2003,2004 The Apache Software Foundation
*
* 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 net.sf.cglib.proxy;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.ide.plugins.cl.PluginClassLoader;
import com.intellij.util.ReflectionUtil;
import net.sf.cglib.core.*;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* Generates dynamic subclasses to enable method interception. This
* class started as a substitute for the standard Dynamic Proxy support
* included with JDK 1.3, but one that allowed the proxies to extend a
* concrete base class, in addition to implementing interfaces. The dynamically
* generated subclasses override the non-final methods of the superclass and
* have hooks which callback to user-defined interceptor
* implementations.
* <p>
* The original and most general callback type is the {@link MethodInterceptor}, which
* in AOP terms enables "around advice"--that is, you can invoke custom code both before
* and after the invocation of the "super" method. In addition you can modify the
* arguments before calling the super method, or not call it at all.
* <p>
* Although <code>MethodInterceptor</code> is generic enough to meet any
* interception need, it is often overkill. For simplicity and performance, additional
* specialized callback types, such as {@link LazyLoader} are also available.
* Often a single callback will be used per enhanced class, but you can control
* which callback is used on a per-method basis with a {@link CallbackFilter}.
* <p>
* The most common uses of this class are embodied in the static helper methods. For
* advanced needs, such as customizing the <code>ClassLoader</code> to use, you should create
* a new instance of <code>Enhancer</code>. Other classes within CGLIB follow a similar pattern.
* <p>
* All enhanced objects implement the {@link Factory} interface, unless {@link #setUseFactory} is
* used to explicitly disable this feature. The <code>Factory</code> interface provides an API
* to change the callbacks of an existing object, as well as a faster and easier way to create
* new instances of the same type.
* <p>
* For an almost drop-in replacement for
* <code>java.lang.reflect.Proxy</code>, see the {@link Proxy} class.
*/
@SuppressWarnings("StaticFieldReferencedViaSubclass")
public class AdvancedEnhancer extends AbstractClassGenerator
{
private static final CallbackFilter ALL_ZERO = new CallbackFilter(){
public int accept(Method method) {
return 0;
}
};
private static final Source SOURCE = new Source(Enhancer.class.getName());
private static final EnhancerKey KEY_FACTORY =
(EnhancerKey)KeyFactory.create(EnhancerKey.class);
private static final String BOUND_FIELD = "CGLIB$BOUND";
private static final String THREAD_CALLBACKS_FIELD = "CGLIB$THREAD_CALLBACKS";
private static final String STATIC_CALLBACKS_FIELD = "CGLIB$STATIC_CALLBACKS";
private static final String SET_THREAD_CALLBACKS_NAME = "CGLIB$SET_THREAD_CALLBACKS";
private static final String SET_STATIC_CALLBACKS_NAME = "CGLIB$SET_STATIC_CALLBACKS";
private static final String CONSTRUCTED_FIELD = "CGLIB$CONSTRUCTED";
private static final Type FACTORY =
TypeUtils.parseType("net.sf.cglib.proxy.Factory");
private static final Type ILLEGAL_STATE_EXCEPTION =
TypeUtils.parseType("IllegalStateException");
private static final Type ILLEGAL_ARGUMENT_EXCEPTION =
TypeUtils.parseType("IllegalArgumentException");
private static final Type THREAD_LOCAL =
TypeUtils.parseType("ThreadLocal");
private static final Type CALLBACK =
TypeUtils.parseType("net.sf.cglib.proxy.Callback");
private static final Type CALLBACK_ARRAY =
Type.getType(Callback[].class);
private static final Signature CSTRUCT_NULL =
TypeUtils.parseConstructor("");
private static final Signature SET_THREAD_CALLBACKS =
new Signature(SET_THREAD_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY });
private static final Signature SET_STATIC_CALLBACKS =
new Signature(SET_STATIC_CALLBACKS_NAME, Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY });
private static final Signature NEW_INSTANCE =
new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ CALLBACK_ARRAY });
private static final Signature MULTIARG_NEW_INSTANCE =
new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{
Constants.TYPE_CLASS_ARRAY,
Constants.TYPE_OBJECT_ARRAY,
CALLBACK_ARRAY,
});
private static final Signature SINGLE_NEW_INSTANCE =
new Signature("newInstance", Constants.TYPE_OBJECT, new Type[]{ CALLBACK });
private static final Signature SET_CALLBACK =
new Signature("setCallback", Type.VOID_TYPE, new Type[]{ Type.INT_TYPE, CALLBACK });
private static final Signature GET_CALLBACK =
new Signature("getCallback", CALLBACK, new Type[]{ Type.INT_TYPE });
private static final Signature SET_CALLBACKS =
new Signature("setCallbacks", Type.VOID_TYPE, new Type[]{ CALLBACK_ARRAY });
private static final Signature GET_CALLBACKS =
new Signature("getCallbacks", CALLBACK_ARRAY, new Type[0]);
private static final Signature THREAD_LOCAL_GET =
TypeUtils.parseSignature("Object get()");
private static final Signature THREAD_LOCAL_SET =
TypeUtils.parseSignature("void set(Object)");
private static final Signature BIND_CALLBACKS =
TypeUtils.parseSignature("void CGLIB$BIND_CALLBACKS(Object)");
/** Internal interface, only public due to ClassLoader issues. */
public interface EnhancerKey {
Object newInstance(String type,
String[] interfaces,
CallbackFilter filter,
Type[] callbackTypes,
boolean useFactory,
boolean interceptDuringConstruction,
Long serialVersionUID);
}
private Class[] interfaces;
private CallbackFilter filter;
private Callback[] callbacks;
private Type[] callbackTypes;
private boolean classOnly;
private Class superclass;
private Class[] argumentTypes;
private Object[] arguments;
private boolean useFactory = true;
private Long serialVersionUID;
private boolean interceptDuringConstruction = true;
/**
* Create a new <code>Enhancer</code>. A new <code>Enhancer</code>
* object should be used for each generated object, and should not
* be shared across threads. To create additional instances of a
* generated class, use the <code>Factory</code> interface.
* @see Factory
*/
public AdvancedEnhancer() {
super(SOURCE);
}
/**
* Set the class which the generated class will extend. As a convenience,
* if the supplied superclass is actually an interface, <code>setInterfaces</code>
* will be called with the appropriate argument instead.
* A non-interface argument must not be declared as final, and must have an
* accessible constructor.
* @param superclass class to extend or interface to implement
* @see #setInterfaces(Class[])
*/
public void setSuperclass(Class superclass) {
if (superclass != null && superclass.isInterface()) {
setInterfaces(new Class[]{ superclass });
} else if (superclass != null && superclass.equals(Object.class)) {
// affects choice of ClassLoader
this.superclass = null;
} else {
this.superclass = superclass;
}
}
/**
* Set the interfaces to implement. The <code>Factory</code> interface will
* always be implemented regardless of what is specified here.
* @param interfaces array of interfaces to implement, or null
* @see Factory
*/
public void setInterfaces(Class[] interfaces) {
this.interfaces = interfaces;
}
/**
* Set the {@link CallbackFilter} used to map the generated class' methods
* to a particular callback index.
* New object instances will always use the same mapping, but may use different
* actual callback objects.
* @param filter the callback filter to use when generating a new class
* @see #setCallbacks
*/
public void setCallbackFilter(CallbackFilter filter) {
this.filter = filter;
}
/**
* Set the single {@link Callback} to use.
* Ignored if you use {@link #createClass}.
* @param callback the callback to use for all methods
* @see #setCallbacks
*/
public void setCallback(final Callback callback) {
setCallbacks(new Callback[]{ callback });
}
/**
* Set the array of callbacks to use.
* Ignored if you use {@link #createClass}.
* You must use a {@link CallbackFilter} to specify the index into this
* array for each method in the proxied class.
* @param callbacks the callback array
* @see #setCallbackFilter
* @see #setCallback
*/
public void setCallbacks(Callback[] callbacks) {
if (callbacks != null && callbacks.length == 0) {
throw new IllegalArgumentException("Array cannot be empty");
}
this.callbacks = callbacks;
}
/**
* Set whether the enhanced object instances should implement
* the {@link Factory} interface.
* This was added for tools that need for proxies to be more
* indistinguishable from their targets. Also, in some cases it may
* be necessary to disable the <code>Factory</code> interface to
* prevent code from changing the underlying callbacks.
* @param useFactory whether to implement <code>Factory</code>; default is <code>true</code>
*/
public void setUseFactory(boolean useFactory) {
this.useFactory = useFactory;
}
/**
* Set whether methods called from within the proxy's constructer
* will be intercepted. The default value is true. Unintercepted methods
* will call the method of the proxy's base class, if it exists.
* @param interceptDuringConstruction whether to intercept methods called from the constructor
*/
public void setInterceptDuringConstruction(boolean interceptDuringConstruction) {
this.interceptDuringConstruction = interceptDuringConstruction;
}
/**
* Set the array of callback types to use.
* This may be used instead of {@link #setCallbacks} when calling
* {@link #createClass}, since it may not be possible to have
* an array of actual callback instances.
* You must use a {@link CallbackFilter} to specify the index into this
* array for each method in the proxied class.
* @param callbackTypes the array of callback types
*/
public void setCallbackTypes(Class[] callbackTypes) {
if (callbackTypes != null && callbackTypes.length == 0) {
throw new IllegalArgumentException("Array cannot be empty");
}
this.callbackTypes = CallbackInfo.determineTypes(callbackTypes);
}
/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the no-arg constructor of the superclass.
* @return a new instance
*/
public Object create() {
classOnly = false;
argumentTypes = null;
return createHelper();
}
/**
* Generate a new class if necessary and uses the specified
* callbacks (if any) to create a new object instance.
* Uses the constructor of the superclass matching the <code>argumentTypes</code>
* parameter, with the given arguments.
* @param argumentTypes constructor signature
* @param arguments compatible wrapped arguments to pass to constructor
* @return a new instance
*/
public Object create(Class[] argumentTypes, Object[] arguments) {
classOnly = false;
if (argumentTypes == null || arguments == null || argumentTypes.length != arguments.length) {
throw new IllegalArgumentException("Arguments must be non-null and of equal length");
}
synchronized (this) {
this.argumentTypes = argumentTypes;
this.arguments = arguments;
try {
return createHelper();
}
finally {
this.arguments = null;
}
}
}
private void validate() {
if (classOnly ^ (callbacks == null)) {
if (classOnly) {
throw new IllegalStateException("createClass does not accept callbacks");
} else {
throw new IllegalStateException("Callbacks are required");
}
}
if (classOnly && (callbackTypes == null)) {
throw new IllegalStateException("Callback types are required");
}
if (callbacks != null && callbackTypes != null) {
if (callbacks.length != callbackTypes.length) {
throw new IllegalStateException("Lengths of callback and callback types array must be the same");
}
Type[] check = CallbackInfo.determineTypes(callbacks);
for (int i = 0; i < check.length; i++) {
if (!check[i].equals(callbackTypes[i])) {
throw new IllegalStateException("Callback " + check[i] + " is not assignable to " + callbackTypes[i]);
}
}
} else if (callbacks != null) {
callbackTypes = CallbackInfo.determineTypes(callbacks);
}
if (filter == null) {
if (callbackTypes.length > 1) {
throw new IllegalStateException("Multiple callback types possible but no filter specified");
}
filter = ALL_ZERO;
}
if (interfaces != null) {
for (Class anInterface : interfaces) {
if (anInterface == null) {
throw new IllegalStateException("Interfaces cannot be null");
}
if (!anInterface.isInterface()) {
throw new IllegalStateException(anInterface + " is not an interface");
}
}
}
}
private Object createHelper() {
validate();
if (superclass != null) {
setNamePrefix(superclass.getName());
} else if (interfaces != null) {
setNamePrefix(interfaces[ReflectUtils.findPackageProtected(interfaces)].getName());
}
return super.create(KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
ReflectUtils.getNames(interfaces),
filter,
callbackTypes,
useFactory,
interceptDuringConstruction,
serialVersionUID));
}
protected ClassLoader getDefaultClassLoader() {
int maxIndex = -1;
ClassLoader bestLoader = null;
if (interfaces != null && interfaces.length > 0) {
for (final Class anInterface : interfaces) {
final ClassLoader loader = anInterface.getClassLoader();
if (loader instanceof PluginClassLoader) {
final int order = PluginManagerCore.getPluginLoadingOrder(((PluginClassLoader)loader).getPluginId());
if (maxIndex < order) {
maxIndex = order;
bestLoader = loader;
}
}
}
}
ClassLoader superLoader = null;
if (superclass != null) {
superLoader = superclass.getClassLoader();
if (superLoader instanceof PluginClassLoader &&
maxIndex < PluginManagerCore.getPluginLoadingOrder(((PluginClassLoader)superLoader).getPluginId())) {
return superLoader;
}
}
if (bestLoader != null) return bestLoader;
return superLoader;
}
private static Signature rename(Signature sig, int index) {
return new Signature("CGLIB$" + sig.getName() + "$" + index,
sig.getDescriptor());
}
private static void getMethods(Class superclass, Class[] interfaces, List<Method> methods, List<Method> interfaceMethods, Set forcePublic)
{
ReflectUtils.addAllMethods(superclass, methods);
List<Method> target = (interfaceMethods != null) ? interfaceMethods : methods;
if (interfaces != null) {
for (Class anInterface : interfaces) {
if (anInterface != Factory.class) {
ReflectUtils.addAllMethods(anInterface, target);
}
}
}
if (interfaceMethods != null) {
if (forcePublic != null) {
forcePublic.addAll(MethodWrapper.createSet(interfaceMethods));
}
methods.addAll(interfaceMethods);
}
CollectionUtils.filter(methods, new DuplicatesPredicate());
CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC | Constants.ACC_FINAL));
CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));
}
public void generateClass(ClassVisitor v) throws Exception {
Class sc = (superclass == null) ? Object.class : superclass;
if (TypeUtils.isFinal(sc.getModifiers())) {
throw new IllegalArgumentException("Cannot subclass final class " + sc);
}
List<Constructor> constructors = new ArrayList<Constructor>(Arrays.asList(sc.getDeclaredConstructors()));
filterConstructors(sc, constructors);
// Order is very important: must add superclass, then
// its superclass chain, then each interface and
// its superinterfaces.
final Set forcePublic = new HashSet();
List<Method> actualMethods = new ArrayList<Method>();
final Map<Method, Method> covariantMethods = new HashMap<Method, Method>();
getMethods(sc, interfaces, actualMethods, new ArrayList<Method>(), forcePublic);
//Changes by Peter Gromov & Gregory Shrago
for(Class aClass = sc; aClass != null; aClass = aClass.getSuperclass()) {
for (final Method method : aClass.getDeclaredMethods()) {
if (actualMethods.contains(method)) {
removeAllCovariantMethods(actualMethods, method, covariantMethods);
}
}
}
ClassEmitter e = new ClassEmitter(v);
e.begin_class(Constants.V1_2,
Constants.ACC_PUBLIC,
getClassName(),
Type.getType(sc),
(useFactory ?
TypeUtils.add(TypeUtils.getTypes(interfaces), FACTORY) :
TypeUtils.getTypes(interfaces)),
Constants.SOURCE_FILE);
List constructorInfo = CollectionUtils.transform(constructors, MethodInfoTransformer.getInstance());
e.declare_field(Constants.ACC_PRIVATE, BOUND_FIELD, Type.BOOLEAN_TYPE, null);
if (!interceptDuringConstruction) {
e.declare_field(Constants.ACC_PRIVATE, CONSTRUCTED_FIELD, Type.BOOLEAN_TYPE, null);
}
e.declare_field(Constants.PRIVATE_FINAL_STATIC, THREAD_CALLBACKS_FIELD, THREAD_LOCAL, null);
e.declare_field(Constants.PRIVATE_FINAL_STATIC, STATIC_CALLBACKS_FIELD, CALLBACK_ARRAY, null);
if (serialVersionUID != null) {
e.declare_field(Constants.PRIVATE_FINAL_STATIC, Constants.SUID_FIELD_NAME, Type.LONG_TYPE, serialVersionUID);
}
for (int i = 0; i < callbackTypes.length; i++) {
e.declare_field(Constants.ACC_PRIVATE, getCallbackField(i), callbackTypes[i], null);
}
final Map<Method, MethodInfo> methodInfoMap = new HashMap<Method, MethodInfo>();
for (Method method : actualMethods) {
if (isJdk8DefaultMethod(method)) {
continue;
}
int modifiers =
Constants.ACC_FINAL | (method.getModifiers() & ~Constants.ACC_ABSTRACT & ~Constants.ACC_NATIVE & ~Constants.ACC_SYNCHRONIZED);
if (forcePublic.contains(MethodWrapper.create(method))) {
modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC;
}
if (covariantMethods.containsKey(method)) {
modifiers = modifiers | Constants.ACC_BRIDGE;
}
methodInfoMap.put(method, ReflectUtils.getMethodInfo(method, modifiers));
}
emitMethods(e, methodInfoMap, covariantMethods);
emitConstructors(e, constructorInfo);
emitSetThreadCallbacks(e);
emitSetStaticCallbacks(e);
emitBindCallbacks(e);
if (useFactory) {
int[] keys = getCallbackKeys();
emitNewInstanceCallbacks(e);
emitNewInstanceCallback(e);
emitNewInstanceMultiarg(e, constructorInfo);
emitGetCallback(e, keys);
emitSetCallback(e, keys);
emitGetCallbacks(e);
emitSetCallbacks(e);
}
e.end_class();
}
private static boolean isJdk8DefaultMethod(Method method) {
return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) ==
Modifier.PUBLIC) && method.getDeclaringClass().isInterface();
}
private static void removeAllCovariantMethods(final List<Method> actualMethods, final Method method, final Map<Method, Method> covariantMethods) {
if ((method.getModifiers() & Constants.ACC_SYNTHETIC) != 0) {
return;
}
for (Iterator<Method> it = actualMethods.iterator(); it.hasNext();) {
Method actualMethod = it.next();
if (actualMethod.equals(method)) {
continue;
}
if (!actualMethod.getName().equals(method.getName()) ||
!Arrays.equals(actualMethod.getParameterTypes(), method.getParameterTypes())) {
continue;
}
if (ReflectionUtil.isAssignable(actualMethod.getReturnType(), method.getReturnType())) {
if ((actualMethod.getModifiers() & Constants.ACC_ABSTRACT) != 0 || (actualMethod.getModifiers() & Constants.ACC_SYNTHETIC) != 0) {
covariantMethods.put(actualMethod, method); //generate bridge
}
else {
it.remove();
}
}
}
}
/**
* Filter the list of constructors from the superclass. The
* constructors which remain will be included in the generated
* class. The default implementation is to filter out all private
* constructors, but subclasses may extend Enhancer to override this
* behavior.
* @param sc the superclass
* @param constructors the list of all declared constructors from the superclass
* @throws IllegalArgumentException if there are no non-private constructors
*/
protected void filterConstructors(Class sc, List<Constructor> constructors) {
CollectionUtils.filter(constructors, new VisibilityPredicate(sc, true));
if (constructors.size() == 0) {
throw new IllegalArgumentException("No visible constructors in " + sc);
}
}
protected Object firstInstance(Class type) throws Exception {
if (classOnly) {
return type;
} else {
return createUsingReflection(type);
}
}
protected Object nextInstance(Object instance) {
Class protoclass = (instance instanceof Class) ? (Class) instance : instance.getClass();
if (classOnly) {
return protoclass;
} else if (instance instanceof Factory) {
if (argumentTypes != null) {
return ((Factory)instance).newInstance(argumentTypes, arguments, callbacks);
} else {
return ((Factory)instance).newInstance(callbacks);
}
} else {
return createUsingReflection(protoclass);
}
}
private static void setThreadCallbacks(Class type, Callback[] callbacks) {
setCallbacksHelper(type, callbacks, SET_THREAD_CALLBACKS_NAME);
}
private static void setCallbacksHelper(Class type, Callback[] callbacks, String methodName) {
// TODO: optimize
try {
Method setter = getCallbacksSetter(type, methodName);
setter.invoke(null, (Object)callbacks);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(type + " is not an enhanced class");
} catch (IllegalAccessException e) {
throw new CodeGenerationException(e);
} catch (InvocationTargetException e) {
throw new CodeGenerationException(e);
}
}
private static Method getCallbacksSetter(Class type, String methodName) throws NoSuchMethodException {
return type.getDeclaredMethod(methodName, Callback[].class);
}
private Object createUsingReflection(Class type) {
setThreadCallbacks(type, callbacks);
try{
if (argumentTypes != null) {
return ReflectUtils.newInstance(type, argumentTypes, arguments);
} else {
return ReflectUtils.newInstance(type);
}
}finally{
// clear thread callbacks to allow them to be gc'd
setThreadCallbacks(type, null);
}
}
private void emitConstructors(ClassEmitter ce, List constructors) {
boolean seenNull = false;
for (final Object constructor1 : constructors) {
MethodInfo constructor = (MethodInfo)constructor1;
CodeEmitter e = EmitUtils.begin_method(ce, constructor, Constants.ACC_PUBLIC);
e.load_this();
e.dup();
e.load_args();
Signature sig = constructor.getSignature();
seenNull = seenNull || sig.getDescriptor().equals("()V");
e.super_invoke_constructor(sig);
e.invoke_static_this(BIND_CALLBACKS);
if (!interceptDuringConstruction) {
e.load_this();
e.push(1);
e.putfield(CONSTRUCTED_FIELD);
}
e.return_value();
e.end_method();
}
if (!classOnly && !seenNull && arguments == null) {
throw new IllegalArgumentException("Superclass has no null constructors but no arguments were given");
}
}
private int[] getCallbackKeys() {
int[] keys = new int[callbackTypes.length];
for (int i = 0; i < callbackTypes.length; i++) {
keys[i] = i;
}
return keys;
}
private static void emitGetCallback(ClassEmitter ce, int[] keys) {
final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACK, null);
e.load_this();
e.invoke_static_this(BIND_CALLBACKS);
e.load_this();
e.load_arg(0);
e.process_switch(keys, new ProcessSwitchCallback() {
public void processCase(int key, Label end) {
e.getfield(getCallbackField(key));
e.goTo(end);
}
public void processDefault() {
e.pop(); // stack height
e.aconst_null();
}
});
e.return_value();
e.end_method();
}
private void emitSetCallback(ClassEmitter ce, int[] keys) {
final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACK, null);
e.load_this();
e.load_arg(1);
e.load_arg(0);
e.process_switch(keys, new ProcessSwitchCallback() {
public void processCase(int key, Label end) {
e.checkcast(callbackTypes[key]);
e.putfield(getCallbackField(key));
e.goTo(end);
}
public void processDefault() {
final Type type = Type.getType(AssertionError.class);
e.new_instance(type);
e.dup();
e.invoke_constructor(type);
e.athrow();
}
});
e.return_value();
e.end_method();
}
private void emitSetCallbacks(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SET_CALLBACKS, null);
e.load_this();
e.load_arg(0);
for (int i = 0; i < callbackTypes.length; i++) {
e.dup2();
e.aaload(i);
e.checkcast(callbackTypes[i]);
e.putfield(getCallbackField(i));
}
e.return_value();
e.end_method();
}
private void emitGetCallbacks(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, GET_CALLBACKS, null);
e.load_this();
e.invoke_static_this(BIND_CALLBACKS);
e.load_this();
e.push(callbackTypes.length);
e.newarray(CALLBACK);
for (int i = 0; i < callbackTypes.length; i++) {
e.dup();
e.push(i);
e.load_this();
e.getfield(getCallbackField(i));
e.aastore();
}
e.return_value();
e.end_method();
}
private static void emitNewInstanceCallbacks(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, NEW_INSTANCE, null);
e.load_arg(0);
e.invoke_static_this(SET_THREAD_CALLBACKS);
emitCommonNewInstance(e);
}
private static void emitCommonNewInstance(CodeEmitter e) {
e.new_instance_this();
e.dup();
e.invoke_constructor_this();
e.aconst_null();
e.invoke_static_this(SET_THREAD_CALLBACKS);
e.return_value();
e.end_method();
}
private void emitNewInstanceCallback(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, SINGLE_NEW_INSTANCE, null);
switch (callbackTypes.length) {
case 0:
// TODO: make sure Callback is null
break;
case 1:
// for now just make a new array; TODO: optimize
e.push(1);
e.newarray(CALLBACK);
e.dup();
e.push(0);
e.load_arg(0);
e.aastore();
e.invoke_static_this(SET_THREAD_CALLBACKS);
break;
default:
e.throw_exception(ILLEGAL_STATE_EXCEPTION, "More than one callback object required");
}
emitCommonNewInstance(e);
}
private static void emitNewInstanceMultiarg(ClassEmitter ce, List constructors) {
final CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC, MULTIARG_NEW_INSTANCE, null);
e.load_arg(2);
e.invoke_static_this(SET_THREAD_CALLBACKS);
e.new_instance_this();
e.dup();
e.load_arg(0);
EmitUtils.constructor_switch(e, constructors, new ObjectSwitchCallback() {
public void processCase(Object key, Label end) {
MethodInfo constructor = (MethodInfo)key;
Type types[] = constructor.getSignature().getArgumentTypes();
for (int i = 0; i < types.length; i++) {
e.load_arg(1);
e.push(i);
e.aaload();
e.unbox(types[i]);
}
e.invoke_constructor_this(constructor.getSignature());
e.goTo(end);
}
public void processDefault() {
e.throw_exception(ILLEGAL_ARGUMENT_EXCEPTION, "Constructor not found");
}
});
e.aconst_null();
e.invoke_static_this(SET_THREAD_CALLBACKS);
e.return_value();
e.end_method();
}
private void emitMethods(final ClassEmitter ce, Map<Method, MethodInfo> methodMap, final Map<Method, Method> covariantMethods) {
CallbackGenerator[] generators = CallbackInfo.getGenerators(callbackTypes);
Map<MethodInfo, MethodInfo> covariantInfoMap = new HashMap<MethodInfo, MethodInfo>();
for (Method method : methodMap.keySet()) {
final Method delegate = covariantMethods.get(method);
if (delegate != null) {
covariantInfoMap.put(methodMap.get(method), ReflectUtils.getMethodInfo(delegate, delegate.getModifiers()));
}
}
BridgeMethodGenerator bridgeMethodGenerator = new BridgeMethodGenerator(covariantInfoMap);
Map<CallbackGenerator,List<MethodInfo>> groups = new HashMap<CallbackGenerator, List<MethodInfo>>();
final Map<MethodInfo,Integer> indexes = new HashMap<MethodInfo, Integer>();
final Map<MethodInfo,Integer> originalModifiers = new HashMap<MethodInfo, Integer>();
final Map positions = CollectionUtils.getIndexMap(new ArrayList<MethodInfo>(methodMap.values()));
for (Method actualMethod : methodMap.keySet()) {
MethodInfo method = methodMap.get(actualMethod);
int index = filter.accept(actualMethod);
if (index >= callbackTypes.length) {
throw new IllegalArgumentException("Callback filter returned an index that is too large: " + index);
}
originalModifiers.put(method, (actualMethod != null) ? actualMethod.getModifiers() : method.getModifiers());
indexes.put(method, index);
final CallbackGenerator generator = covariantMethods.containsKey(actualMethod)? bridgeMethodGenerator : generators[index];
List<MethodInfo> group = groups.get(generator);
if (group == null) {
groups.put(generator, group = new ArrayList<MethodInfo>(methodMap.size()));
}
group.add(method);
}
CodeEmitter se = ce.getStaticHook();
se.new_instance(THREAD_LOCAL);
se.dup();
se.invoke_constructor(THREAD_LOCAL, CSTRUCT_NULL);
se.putfield(THREAD_CALLBACKS_FIELD);
CallbackGenerator.Context context = new CallbackGenerator.Context() {
public ClassLoader getClassLoader() {
return AdvancedEnhancer.this.getClassLoader();
}
public int getOriginalModifiers(MethodInfo method) {
return originalModifiers.get(method);
}
public int getIndex(MethodInfo method) {
return indexes.get(method);
}
public void emitCallback(CodeEmitter e, int index) {
emitCurrentCallback(e, index);
}
public Signature getImplSignature(MethodInfo method) {
return rename(method.getSignature(), (Integer)positions.get(method));
}
@Override
public void emitInvoke(CodeEmitter codeEmitter, MethodInfo methodInfo) {
codeEmitter.super_invoke(methodInfo.getSignature());
}
public CodeEmitter beginMethod(ClassEmitter ce, MethodInfo method) {
CodeEmitter e = EmitUtils.begin_method(ce, method);
if (!interceptDuringConstruction &&
!TypeUtils.isAbstract(method.getModifiers())) {
Label constructed = e.make_label();
e.load_this();
e.getfield(CONSTRUCTED_FIELD);
e.if_jump(e.NE, constructed);
e.load_this();
e.load_args();
e.super_invoke();
e.return_value();
e.mark(constructed);
}
return e;
}
};
Set<CallbackGenerator> seenGen = new HashSet<CallbackGenerator>();
for (int i = 0; i < callbackTypes.length + 1; i++) {
CallbackGenerator gen = i == callbackTypes.length? bridgeMethodGenerator : generators[i];
if (!seenGen.contains(gen)) {
seenGen.add(gen);
final List<MethodInfo> fmethods = groups.get(gen);
if (fmethods != null) {
try {
gen.generate(ce, context, fmethods);
gen.generateStatic(se, context, fmethods);
} catch (RuntimeException x) {
throw x;
} catch (Exception x) {
throw new CodeGenerationException(x);
}
}
}
}
se.return_value();
se.end_method();
}
private static void emitSetThreadCallbacks(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC,
SET_THREAD_CALLBACKS,
null);
e.getfield(THREAD_CALLBACKS_FIELD);
e.load_arg(0);
e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_SET);
e.return_value();
e.end_method();
}
private static void emitSetStaticCallbacks(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.ACC_PUBLIC | Constants.ACC_STATIC,
SET_STATIC_CALLBACKS,
null);
e.load_arg(0);
e.putfield(STATIC_CALLBACKS_FIELD);
e.return_value();
e.end_method();
}
private static void emitCurrentCallback(CodeEmitter e, int index) {
e.load_this();
e.getfield(getCallbackField(index));
e.dup();
Label end = e.make_label();
e.ifnonnull(end);
e.pop(); // stack height
e.load_this();
e.invoke_static_this(BIND_CALLBACKS);
e.load_this();
e.getfield(getCallbackField(index));
e.mark(end);
}
private void emitBindCallbacks(ClassEmitter ce) {
CodeEmitter e = ce.begin_method(Constants.PRIVATE_FINAL_STATIC,
BIND_CALLBACKS,
null);
Local me = e.make_local();
e.load_arg(0);
e.checkcast_this();
e.store_local(me);
Label end = e.make_label();
e.load_local(me);
e.getfield(BOUND_FIELD);
e.if_jump(e.NE, end);
e.load_local(me);
e.push(1);
e.putfield(BOUND_FIELD);
e.getfield(THREAD_CALLBACKS_FIELD);
e.invoke_virtual(THREAD_LOCAL, THREAD_LOCAL_GET);
e.dup();
Label found_callback = e.make_label();
e.ifnonnull(found_callback);
e.pop();
e.getfield(STATIC_CALLBACKS_FIELD);
e.dup();
e.ifnonnull(found_callback);
e.pop();
e.goTo(end);
e.mark(found_callback);
e.checkcast(CALLBACK_ARRAY);
e.load_local(me);
e.swap();
for (int i = callbackTypes.length - 1; i >= 0; i--) {
if (i != 0) {
e.dup2();
}
e.aaload(i);
e.checkcast(callbackTypes[i]);
e.putfield(getCallbackField(i));
}
e.mark(end);
e.return_value();
e.end_method();
}
private static String getCallbackField(int index) {
return "CGLIB$CALLBACK_" + index;
}
}