| // 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.apache.tapestry5.internal.plastic; |
| |
| import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor; |
| import org.apache.tapestry5.internal.plastic.asm.Opcodes; |
| import org.apache.tapestry5.internal.plastic.asm.Type; |
| import org.apache.tapestry5.internal.plastic.asm.tree.*; |
| import org.apache.tapestry5.plastic.*; |
| |
| import java.io.IOException; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.*; |
| |
| @SuppressWarnings("all") |
| public class PlasticClassImpl extends Lockable implements PlasticClass, InternalPlasticClassTransformation, Opcodes |
| { |
| private static final String NOTHING_TO_VOID = "()V"; |
| |
| static final String CONSTRUCTOR_NAME = "<init>"; |
| |
| private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;"; |
| |
| private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V"; |
| |
| private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format( |
| "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class))); |
| |
| static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils |
| .toInternalName(AbstractMethodInvocation.class.getName()); |
| |
| private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type |
| .getInternalName(PlasticClassHandleShim.class); |
| |
| static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class); |
| |
| private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class); |
| |
| private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME); |
| |
| private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME, |
| INSTANCE_CONTEXT_INTERNAL_NAME); |
| |
| static final Method STATIC_CONTEXT_GET_METHOD = toMethod(StaticContext.class, "get", int.class); |
| |
| static final Method COMPUTED_VALUE_GET_METHOD = toMethod(ComputedValue.class, "get", InstanceContext.class); |
| |
| private static final Method CONSTRUCTOR_CALLBACK_METHOD = toMethod(ConstructorCallback.class, "onConstruct", |
| Object.class, InstanceContext.class); |
| |
| private static String toDesc(String internalName) |
| { |
| return "L" + internalName + ";"; |
| } |
| |
| private static Method toMethod(Class declaringClass, String methodName, Class... parameterTypes) |
| { |
| return PlasticUtils.getMethod(declaringClass, methodName, parameterTypes); |
| } |
| |
| static <T> T safeArrayDeref(T[] array, int index) |
| { |
| if (array == null) |
| return null; |
| |
| return array[index]; |
| } |
| |
| // Now past the inner classes; these are the instance variables of PlasticClassImpl proper: |
| |
| final ClassNode classNode; |
| |
| final PlasticClassPool pool; |
| |
| private final boolean proxy; |
| |
| final String className; |
| |
| private final String superClassName; |
| |
| private final AnnotationAccess annotationAccess; |
| |
| // All the non-introduced (and non-constructor) methods, in sorted order |
| |
| private final List<PlasticMethodImpl> methods; |
| |
| private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<>(); |
| |
| final Set<String> methodNames = new HashSet<>(); |
| |
| private final List<ConstructorCallback> constructorCallbacks = PlasticInternalUtils.newList(); |
| |
| // All non-introduced instance fields |
| |
| private final List<PlasticFieldImpl> fields; |
| |
| /** |
| * Methods that require special attention inside {@link #createInstantiator()} because they |
| * have method advice. |
| */ |
| final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet(); |
| |
| final NameCache nameCache = new NameCache(); |
| |
| // This is generated from fields, as necessary |
| List<PlasticField> unclaimedFields; |
| |
| private final Set<String> fieldNames = PlasticInternalUtils.newSet(); |
| |
| final StaticContext staticContext; |
| |
| final InheritanceData parentInheritanceData, inheritanceData; |
| |
| // MethodNodes in which field transformations should occur; this is most existing and |
| // introduced methods, outside of special access methods. |
| |
| final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet(); |
| |
| // Tracks any methods that the Shim class uses to gain access to fields; used to ensure that |
| // such methods are not optimized away incorrectly. |
| final Set<MethodNode> shimInvokedMethods = PlasticInternalUtils.newSet(); |
| |
| |
| /** |
| * Tracks instrumentations of fields of this class, including private fields which are not published into the |
| * {@link PlasticClassPool}. |
| */ |
| private final FieldInstrumentations fieldInstrumentations; |
| |
| /** |
| * This normal no-arguments constructor, or null. By the end of the transformation |
| * this will be converted into an ordinary method. |
| */ |
| private MethodNode originalConstructor; |
| |
| private final MethodNode newConstructor; |
| |
| final InstructionBuilder constructorBuilder; |
| |
| private String instanceContextFieldName; |
| |
| private Class<?> transformedClass; |
| |
| // Indexes used to identify fields or methods in the shim |
| int nextFieldIndex = 0; |
| |
| int nextMethodIndex = 0; |
| |
| // Set of fields that need to contribute to the shim and gain access to it |
| |
| final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet(); |
| |
| // Set of methods that need to contribute to the shim and gain access to it |
| |
| final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet(); |
| |
| final ClassNode implementationClassNode; |
| |
| private ClassNode interfaceClassNode; |
| |
| /** |
| * @param classNode |
| * @param implementationClassNode |
| * @param pool |
| * @param parentInheritanceData |
| * @param parentStaticContext |
| * @param proxy |
| */ |
| public PlasticClassImpl(ClassNode classNode, ClassNode implementationClassNode, PlasticClassPool pool, InheritanceData parentInheritanceData, |
| StaticContext parentStaticContext, boolean proxy) |
| { |
| this.classNode = classNode; |
| this.pool = pool; |
| this.proxy = proxy; |
| this.implementationClassNode = implementationClassNode; |
| |
| staticContext = parentStaticContext.dupe(); |
| |
| className = PlasticInternalUtils.toClassName(classNode.name); |
| superClassName = PlasticInternalUtils.toClassName(classNode.superName); |
| int lastIndexOfDot = className.lastIndexOf('.'); |
| |
| String packageName = lastIndexOfDot > -1 ? className.substring(0, lastIndexOfDot) : ""; |
| |
| fieldInstrumentations = new FieldInstrumentations(classNode.superName); |
| |
| annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations), |
| pool.createAnnotationAccess(superClassName)); |
| |
| this.parentInheritanceData = parentInheritanceData; |
| |
| inheritanceData = parentInheritanceData.createChild(packageName); |
| |
| for (String interfaceName : classNode.interfaces) |
| { |
| inheritanceData.addInterface(interfaceName); |
| } |
| |
| methods = new ArrayList<>(classNode.methods.size()); |
| |
| String invalidConstructorMessage = invalidConstructorMessage(); |
| |
| for (MethodNode node : classNode.methods) |
| { |
| if (node.name.equals(CONSTRUCTOR_NAME)) |
| { |
| if (node.desc.equals(NOTHING_TO_VOID)) |
| { |
| originalConstructor = node; |
| fieldTransformMethods.add(node); |
| } else |
| { |
| node.instructions.clear(); |
| |
| newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage); |
| } |
| |
| continue; |
| } |
| |
| /* |
| * Static methods are not visible to the main API methods, but they must still be transformed, |
| * in case they directly access fields. In addition, track their names to avoid collisions. |
| */ |
| if (Modifier.isStatic(node.access)) |
| { |
| if (isInheritableMethod(node)) |
| { |
| inheritanceData.addMethod(node.name, node.desc, node.access == 0); |
| } |
| |
| methodNames.add(node.name); |
| |
| fieldTransformMethods.add(node); |
| |
| continue; |
| } |
| |
| if (!Modifier.isAbstract(node.access)) |
| { |
| fieldTransformMethods.add(node); |
| } |
| |
| PlasticMethodImpl pmi = new PlasticMethodImpl(this, node); |
| |
| methods.add(pmi); |
| description2method.put(pmi.getDescription(), pmi); |
| |
| if (isInheritableMethod(node)) |
| { |
| inheritanceData.addMethod(node.name, node.desc, node.access == 0); |
| } |
| |
| methodNames.add(node.name); |
| } |
| |
| methodNames.addAll(parentInheritanceData.methodNames()); |
| |
| Collections.sort(methods); |
| |
| fields = new ArrayList<>(classNode.fields.size()); |
| |
| for (FieldNode node : classNode.fields) |
| { |
| fieldNames.add(node.name); |
| |
| // Ignore static fields. |
| |
| if (Modifier.isStatic(node.access)) |
| continue; |
| |
| // When we instrument the field such that it must be private, we'll get an exception. |
| |
| fields.add(new PlasticFieldImpl(this, node)); |
| } |
| |
| Collections.sort(fields); |
| |
| // TODO: Make the output class's constructor protected, and create a shim class to instantiate it |
| // efficiently (without reflection). |
| newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null); |
| constructorBuilder = newBuilder(newConstructor); |
| |
| // Start by calling the super-class no args constructor |
| |
| if (parentInheritanceData.isTransformed()) |
| { |
| // If the parent is transformed, our first step is always to invoke its constructor. |
| |
| constructorBuilder.loadThis().loadArgument(0).loadArgument(1); |
| constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(), |
| InstanceContext.class.getName()); |
| } else |
| { |
| // Assumes the base class includes a visible constructor that takes no arguments. |
| // TODO: Do a proper check for this case and throw a meaningful exception |
| // if not present. |
| |
| constructorBuilder.loadThis().invokeConstructor(superClassName); |
| } |
| |
| // During the transformation, we'll be adding code to the constructor to pull values |
| // out of the static or instance context and assign them to fields. |
| |
| // Later on, we'll add the RETURN opcode |
| } |
| |
| private String invalidConstructorMessage() |
| { |
| return String.format("Class %s has been transformed and may not be directly instantiated.", className); |
| } |
| |
| @Override |
| public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType) |
| { |
| check(); |
| |
| return annotationAccess.hasAnnotation(annotationType); |
| } |
| |
| @Override |
| public <T extends Annotation> T getAnnotation(Class<T> annotationType) |
| { |
| check(); |
| |
| return annotationAccess.getAnnotation(annotationType); |
| } |
| |
| private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, MethodNode implementationMethodNode) |
| { |
| // visits the method attributes |
| int i, j, n; |
| if (implementationMethodNode.annotationDefault != null) |
| { |
| AnnotationVisitor av = methodNode.visitAnnotationDefault(); |
| AnnotationNode.accept(av, null, implementationMethodNode.annotationDefault); |
| if (av != null) |
| { |
| av.visitEnd(); |
| } |
| } |
| n = implementationMethodNode.visibleAnnotations == null ? 0 : implementationMethodNode.visibleAnnotations.size(); |
| for (i = 0; i < n; ++i) |
| { |
| AnnotationNode an = implementationMethodNode.visibleAnnotations.get(i); |
| an.accept(methodNode.visitAnnotation(an.desc, true)); |
| } |
| n = implementationMethodNode.invisibleAnnotations == null ? 0 : implementationMethodNode.invisibleAnnotations.size(); |
| for (i = 0; i < n; ++i) |
| { |
| AnnotationNode an = implementationMethodNode.invisibleAnnotations.get(i); |
| an.accept(methodNode.visitAnnotation(an.desc, false)); |
| } |
| n = implementationMethodNode.visibleParameterAnnotations == null |
| ? 0 |
| : implementationMethodNode.visibleParameterAnnotations.length; |
| for (i = 0; i < n; ++i) |
| { |
| List<?> l = implementationMethodNode.visibleParameterAnnotations[i]; |
| if (l == null) |
| { |
| continue; |
| } |
| for (j = 0; j < l.size(); ++j) |
| { |
| AnnotationNode an = (AnnotationNode) l.get(j); |
| an.accept(methodNode.visitParameterAnnotation(i, an.desc, true)); |
| } |
| } |
| n = implementationMethodNode.invisibleParameterAnnotations == null |
| ? 0 |
| : implementationMethodNode.invisibleParameterAnnotations.length; |
| for (i = 0; i < n; ++i) |
| { |
| List<?> l = implementationMethodNode.invisibleParameterAnnotations[i]; |
| if (l == null) |
| { |
| continue; |
| } |
| for (j = 0; j < l.size(); ++j) |
| { |
| AnnotationNode an = (AnnotationNode) l.get(j); |
| an.accept(methodNode.visitParameterAnnotation(i, an.desc, false)); |
| } |
| } |
| |
| methodNode.visitEnd(); |
| |
| } |
| |
| private static void removeDuplicatedAnnotations(MethodNode node) |
| { |
| |
| removeDuplicatedAnnotations(node.visibleAnnotations); |
| removeDuplicatedAnnotations(node.invisibleAnnotations); |
| |
| if (node.visibleParameterAnnotations != null) |
| { |
| for (List<AnnotationNode> list : node.visibleParameterAnnotations) |
| { |
| removeDuplicatedAnnotations(list); |
| } |
| } |
| |
| if (node.invisibleParameterAnnotations != null) |
| { |
| for (List<AnnotationNode> list : node.invisibleParameterAnnotations) |
| { |
| removeDuplicatedAnnotations(list); |
| } |
| } |
| |
| } |
| |
| private static void removeDuplicatedAnnotations(ClassNode node) |
| { |
| removeDuplicatedAnnotations(node.visibleAnnotations, true); |
| removeDuplicatedAnnotations(node.invisibleAnnotations, true); |
| } |
| |
| private static void removeDuplicatedAnnotations(List<AnnotationNode> list) { |
| removeDuplicatedAnnotations(list, false); |
| } |
| |
| private static void removeDuplicatedAnnotations(List<AnnotationNode> list, boolean reverse) { |
| |
| if (list != null) |
| { |
| |
| final Set<String> annotations = new HashSet<>(); |
| final List<AnnotationNode> toBeRemoved = new ArrayList<>(); |
| final List<AnnotationNode> toBeIterated; |
| |
| if (reverse) |
| { |
| toBeIterated = new ArrayList<>(list); |
| Collections.reverse(toBeIterated); |
| } |
| else { |
| toBeIterated = list; |
| } |
| |
| for (AnnotationNode annotationNode : toBeIterated) |
| { |
| if (annotations.contains(annotationNode.desc)) |
| { |
| toBeRemoved.add(annotationNode); |
| } |
| else |
| { |
| annotations.add(annotationNode.desc); |
| } |
| } |
| |
| for (AnnotationNode annotationNode : toBeRemoved) |
| { |
| list.remove(annotationNode); |
| } |
| |
| } |
| |
| } |
| |
| private static String getParametersDesc(MethodNode methodNode) { |
| return methodNode.desc.substring(methodNode.desc.indexOf('(') + 1, methodNode.desc.lastIndexOf(')')); |
| } |
| |
| private static MethodNode findExactMatchMethod(MethodNode methodNode, ClassNode source) { |
| |
| MethodNode found = null; |
| |
| final String methodDescription = getParametersDesc(methodNode); |
| |
| for (MethodNode implementationMethodNode : source.methods) |
| { |
| |
| final String implementationMethodDescription = getParametersDesc(implementationMethodNode); |
| if (methodNode.name.equals(implementationMethodNode.name) && |
| // We don't want synthetic methods. |
| ((implementationMethodNode.access & Opcodes.ACC_SYNTHETIC) == 0) |
| && (methodDescription.equals(implementationMethodDescription))) |
| { |
| found = implementationMethodNode; |
| break; |
| } |
| } |
| |
| return found; |
| |
| } |
| |
| private static List<Class> getJavaParameterTypes(MethodNode methodNode) { |
| final ClassLoader classLoader = PlasticInternalUtils.class.getClassLoader(); |
| Type[] parameterTypes = Type.getArgumentTypes(methodNode.desc); |
| List<Class> list = new ArrayList<>(); |
| for (Type type : parameterTypes) |
| { |
| try |
| { |
| list.add(PlasticInternalUtils.toClass(classLoader, type.getClassName())); |
| } |
| catch (ClassNotFoundException e) |
| { |
| throw new RuntimeException(e); // shouldn't happen anyway |
| } |
| } |
| return list; |
| } |
| |
| /** |
| * Returns the first method which matches the given methodNode. |
| * FIXME: this may not find the correct method if the correct one is declared after |
| * another in which all parameters are supertypes of the parameters of methodNode. |
| * To solve this, we would need to dig way deeper than we have time for this. |
| * @param methodNode |
| * @param classNode |
| * @return |
| */ |
| private static MethodNode findGenericMethod(MethodNode methodNode, ClassNode classNode) |
| { |
| |
| MethodNode found = null; |
| |
| List<Class> parameterTypes = getJavaParameterTypes(methodNode); |
| |
| for (MethodNode implementationMethodNode : classNode.methods) |
| { |
| |
| if (methodNode.name.equals(implementationMethodNode.name)) |
| { |
| |
| final List<Class> implementationParameterTypes = getJavaParameterTypes(implementationMethodNode); |
| |
| if (parameterTypes.size() == implementationParameterTypes.size()) |
| { |
| |
| boolean matches = true; |
| for (int i = 0; i < parameterTypes.size(); i++) |
| { |
| final Class implementationParameterType = implementationParameterTypes.get(i); |
| final Class parameterType = parameterTypes.get(i); |
| if (!parameterType.isAssignableFrom(implementationParameterType)) { |
| matches = false; |
| break; |
| } |
| |
| } |
| |
| if (matches && !isBridge(implementationMethodNode)) |
| { |
| found = implementationMethodNode; |
| break; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| return found; |
| |
| } |
| |
| private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, ClassNode source) |
| { |
| if (source != null) |
| { |
| |
| MethodNode candidate = findExactMatchMethod(methodNode, source); |
| |
| final String parametersDesc = getParametersDesc(methodNode); |
| |
| // candidate will be null when the method has generic parameters |
| if (candidate == null && parametersDesc.trim().length() > 0) |
| { |
| candidate = findGenericMethod(methodNode, source); |
| } |
| |
| if (candidate != null) |
| { |
| addMethodAndParameterAnnotationsFromExistingClass(methodNode, candidate); |
| } |
| |
| } |
| |
| } |
| |
| /** |
| * Tells whether a given method is a bridge one or not. |
| * Notice the flag for bridge method is the same as volatile field. Java 6 doesn't have |
| * Modifiers.isBridge(), so we use a workaround. |
| */ |
| private static boolean isBridge(MethodNode methodNode) |
| { |
| return Modifier.isVolatile(methodNode.access); |
| } |
| |
| @Override |
| public PlasticClass proxyInterface(Class interfaceType, PlasticField field) |
| { |
| check(); |
| |
| assert field != null; |
| |
| introduceInterface(interfaceType); |
| |
| for (Method m : getUniqueMethods(interfaceType)) |
| { |
| final MethodDescription description = new MethodDescription(m); |
| if(Modifier.isStatic(description.modifiers)) |
| { |
| continue; |
| } |
| introduceMethod(description).delegateTo(field); |
| } |
| |
| return this; |
| } |
| |
| @Override |
| public ClassInstantiator createInstantiator() |
| { |
| lock(); |
| |
| addClassAnnotations(implementationClassNode); |
| removeDuplicatedAnnotations(classNode); |
| |
| createShimIfNeeded(); |
| |
| interceptFieldAccess(); |
| |
| rewriteAdvisedMethods(); |
| |
| completeConstructor(); |
| |
| transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext); |
| |
| return createInstantiatorFromClass(transformedClass); |
| } |
| |
| private void addClassAnnotations(ClassNode otherClassNode) |
| { |
| // Copy annotations from implementation if available. |
| // Code adapted from ClassNode.accept(), as we just want to copy |
| // the annotations and nothing more. |
| if (otherClassNode != null) |
| { |
| |
| int i, n; |
| n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size(); |
| for (i = 0; i < n; ++i) |
| { |
| AnnotationNode an = otherClassNode.visibleAnnotations.get(i); |
| an.accept(classNode.visitAnnotation(an.desc, true)); |
| } |
| n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size(); |
| for (i = 0; i < n; ++i) |
| { |
| AnnotationNode an = otherClassNode.invisibleAnnotations.get(i); |
| an.accept(classNode.visitAnnotation(an.desc, false)); |
| } |
| |
| } |
| } |
| |
| private ClassInstantiator createInstantiatorFromClass(Class clazz) |
| { |
| try |
| { |
| Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class); |
| |
| return new ClassInstantiatorImpl(clazz, ctor, staticContext); |
| } catch (Exception ex) |
| { |
| throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s", |
| clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex); |
| } |
| } |
| |
| private void completeConstructor() |
| { |
| if (originalConstructor != null) |
| { |
| convertOriginalConstructorToMethod(); |
| } |
| |
| invokeCallbacks(); |
| |
| constructorBuilder.returnResult(); |
| |
| classNode.methods.add(newConstructor); |
| } |
| |
| private void invokeCallbacks() |
| { |
| for (ConstructorCallback callback : constructorCallbacks) |
| { |
| invokeCallback(callback); |
| } |
| } |
| |
| private void invokeCallback(ConstructorCallback callback) |
| { |
| int index = staticContext.store(callback); |
| |
| // First, load the callback |
| |
| constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName()); |
| |
| // Load this and the InstanceContext |
| constructorBuilder.loadThis().loadArgument(1); |
| |
| constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD); |
| } |
| |
| |
| /** |
| * Convert the original constructor into a private method invoked from the |
| * generated constructor. |
| */ |
| private void convertOriginalConstructorToMethod() |
| { |
| String initializerName = makeUnique(methodNames, "initializeInstance"); |
| |
| int originalAccess = originalConstructor.access; |
| |
| originalConstructor.access = ACC_PRIVATE; |
| originalConstructor.name = initializerName; |
| |
| stripOutSuperConstructorCall(originalConstructor); |
| |
| constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName); |
| |
| // And replace it with a constructor that throws an exception |
| |
| MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, |
| null); |
| |
| newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage()); |
| |
| classNode.methods.add(replacementConstructor); |
| } |
| |
| private void stripOutSuperConstructorCall(MethodNode cons) |
| { |
| InsnList ins = cons.instructions; |
| |
| ListIterator li = ins.iterator(); |
| |
| // Look for the ALOAD 0 (i.e., push this on the stack) |
| while (li.hasNext()) |
| { |
| AbstractInsnNode node = (AbstractInsnNode) li.next(); |
| |
| if (node.getOpcode() == ALOAD) |
| { |
| VarInsnNode varNode = (VarInsnNode) node; |
| |
| assert varNode.var == 0; |
| |
| // Remove the ALOAD |
| li.remove(); |
| break; |
| } |
| } |
| |
| // Look for the call to the super-class, an INVOKESPECIAL |
| while (li.hasNext()) |
| { |
| AbstractInsnNode node = (AbstractInsnNode) li.next(); |
| |
| if (node.getOpcode() == INVOKESPECIAL) |
| { |
| MethodInsnNode mnode = (MethodInsnNode) node; |
| |
| assert mnode.owner.equals(classNode.superName); |
| assert mnode.name.equals(CONSTRUCTOR_NAME); |
| assert mnode.desc.equals(cons.desc); |
| |
| li.remove(); |
| return; |
| } |
| } |
| |
| throw new AssertionError("Could not convert constructor to simple method."); |
| } |
| |
| @Override |
| public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType) |
| { |
| check(); |
| |
| List<PlasticField> result = getAllFields(); |
| |
| Iterator<PlasticField> iterator = result.iterator(); |
| |
| while (iterator.hasNext()) |
| { |
| PlasticField plasticField = iterator.next(); |
| |
| if (!plasticField.hasAnnotation(annotationType)) |
| iterator.remove(); |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public List<PlasticField> getAllFields() |
| { |
| check(); |
| |
| return new ArrayList<>(fields); |
| } |
| |
| @Override |
| public List<PlasticField> getUnclaimedFields() |
| { |
| check(); |
| |
| // Initially null, and set back to null by PlasticField.claim(). |
| |
| if (unclaimedFields == null) |
| { |
| unclaimedFields = new ArrayList<>(fields.size()); |
| |
| for (PlasticField f : fields) |
| { |
| if (!f.isClaimed()) |
| unclaimedFields.add(f); |
| } |
| } |
| |
| return unclaimedFields; |
| } |
| |
| @Override |
| public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes, |
| String[] exceptionTypes) |
| { |
| check(); |
| |
| assert PlasticInternalUtils.isNonBlank(typeName); |
| assert PlasticInternalUtils.isNonBlank(suggestedName); |
| |
| String name = makeUnique(methodNames, suggestedName); |
| |
| MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null, |
| exceptionTypes); |
| |
| return introduceMethod(description); |
| } |
| |
| @Override |
| public PlasticField introduceField(String className, String suggestedName) |
| { |
| check(); |
| |
| assert PlasticInternalUtils.isNonBlank(className); |
| assert PlasticInternalUtils.isNonBlank(suggestedName); |
| |
| String name = makeUnique(fieldNames, suggestedName); |
| |
| // No signature and no initial value |
| |
| FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null); |
| |
| classNode.fields.add(fieldNode); |
| |
| fieldNames.add(name); |
| |
| PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode); |
| |
| return newField; |
| } |
| |
| @Override |
| public PlasticField introduceField(Class fieldType, String suggestedName) |
| { |
| assert fieldType != null; |
| |
| return introduceField(nameCache.toTypeName(fieldType), suggestedName); |
| } |
| |
| String makeUnique(Set<String> values, String input) |
| { |
| return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input; |
| } |
| |
| @Override |
| public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType) |
| { |
| check(); |
| |
| List<PlasticMethod> result = getMethods(); |
| |
| result.removeIf(method -> !method.hasAnnotation(annotationType)); |
| |
| return result; |
| } |
| |
| @Override |
| public List<PlasticMethod> getMethods() |
| { |
| check(); |
| |
| return new ArrayList<>(methods); |
| } |
| |
| @Override |
| public PlasticMethod introduceMethod(MethodDescription description) |
| { |
| check(); |
| |
| if (Modifier.isAbstract(description.modifiers)) |
| { |
| description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT); |
| } |
| |
| PlasticMethod result = description2method.get(description); |
| |
| if (result == null) |
| { |
| result = createNewMethod(description); |
| |
| description2method.put(description, result); |
| } |
| |
| methodNames.add(description.methodName); |
| |
| // Note that is it not necessary to add the new MethodNode to |
| // fieldTransformMethods (the default implementations provided by introduceMethod() do not |
| // ever access instance fields) ... unless the caller invokes changeImplementation(). |
| |
| return result; |
| } |
| |
| @Override |
| public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback) |
| { |
| check(); |
| |
| // TODO: optimize this so that a default implementation is not created. |
| |
| return introduceMethod(description).changeImplementation(callback); |
| } |
| |
| @Override |
| public PlasticMethod introduceMethod(Method method) |
| { |
| check(); |
| return introduceMethod(new MethodDescription(method)); |
| } |
| |
| void addMethod(MethodNode methodNode) |
| { |
| classNode.methods.add(methodNode); |
| |
| methodNames.add(methodNode.name); |
| |
| if (isInheritableMethod(methodNode)) |
| { |
| inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0); |
| } |
| } |
| |
| private PlasticMethod createNewMethod(MethodDescription description) |
| { |
| if (Modifier.isStatic(description.modifiers)) |
| throw new IllegalArgumentException(String.format( |
| "Unable to introduce method '%s' into class %s: introduced methods may not be static.", |
| description, className)); |
| |
| String desc = nameCache.toDesc(description); |
| |
| String[] exceptions = new String[description.checkedExceptionTypes.length]; |
| for (int i = 0; i < exceptions.length; i++) |
| { |
| exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]); |
| } |
| |
| MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc, |
| description.genericSignature, exceptions); |
| boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc); |
| |
| if (!isOverride) |
| { |
| addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode); |
| addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode); |
| removeDuplicatedAnnotations(methodNode); |
| } |
| |
| if (isOverride) |
| createOverrideOfBaseClassImpl(description, methodNode); |
| else |
| createNewMethodImpl(description, methodNode); |
| |
| addMethod(methodNode); |
| |
| return new PlasticMethodImpl(this, methodNode); |
| } |
| |
| private boolean isDefaultMethod(Method method) |
| { |
| return method.getDeclaringClass().isInterface() && |
| (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC; |
| } |
| |
| private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode) |
| { |
| newBuilder(methodDescription, methodNode).returnDefaultValue(); |
| } |
| |
| private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode) |
| { |
| InstructionBuilder builder = newBuilder(methodDescription, methodNode); |
| |
| builder.loadThis(); |
| builder.loadArguments(); |
| builder.invokeSpecial(superClassName, methodDescription); |
| builder.returnResult(); |
| } |
| |
| /** |
| * Iterates over all non-introduced methods, including the original constructor. For each |
| * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field, |
| * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim} |
| * for the class has been created, as the shim may create methods that contain references to fields that may be |
| * subject to field access interception. |
| */ |
| private void interceptFieldAccess() |
| { |
| for (MethodNode node : fieldTransformMethods) |
| { |
| // Intercept field access inside the method, tracking which access methods |
| // are actually used by removing them from accessMethods |
| |
| interceptFieldAccess(node); |
| } |
| } |
| |
| /** |
| * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so |
| * a shim class must be created to facilitate read/write access to fields, or invocation of methods. |
| */ |
| private void createShimIfNeeded() |
| { |
| if (shimFields.isEmpty() && shimMethods.isEmpty()) |
| return; |
| |
| PlasticClassHandleShim shim = createShimInstance(); |
| |
| installShim(shim); |
| } |
| |
| public void installShim(PlasticClassHandleShim shim) |
| { |
| for (PlasticFieldImpl f : shimFields) |
| { |
| f.installShim(shim); |
| } |
| |
| for (PlasticMethodImpl m : shimMethods) |
| { |
| m.installShim(shim); |
| } |
| } |
| |
| public PlasticClassHandleShim createShimInstance() |
| { |
| String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID()); |
| |
| ClassNode shimClassNode = new ClassNode(); |
| |
| shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME, |
| null); |
| |
| implementConstructor(shimClassNode); |
| |
| if (!shimFields.isEmpty()) |
| { |
| implementShimGet(shimClassNode); |
| implementShimSet(shimClassNode); |
| } |
| |
| if (!shimMethods.isEmpty()) |
| { |
| implementShimInvoke(shimClassNode); |
| } |
| |
| return instantiateShim(shimClassNode); |
| } |
| |
| private void implementConstructor(ClassNode shimClassNode) |
| { |
| MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null); |
| |
| InstructionBuilder builder = newBuilder(mn); |
| |
| builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult(); |
| |
| shimClassNode.methods.add(mn); |
| |
| } |
| |
| private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode) |
| { |
| try |
| { |
| Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode); |
| |
| return (PlasticClassHandleShim) shimClass.newInstance(); |
| } catch (Exception ex) |
| { |
| throw new RuntimeException( |
| String.format("Unable to instantiate shim class %s for plastic class %s: %s", |
| PlasticInternalUtils.toClassName(shimClassNode.name), className, |
| PlasticInternalUtils.toMessage(ex)), ex); |
| } |
| } |
| |
| private void implementShimGet(ClassNode shimClassNode) |
| { |
| MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null); |
| |
| InstructionBuilder builder = newBuilder(mn); |
| |
| // Arg 0 is the target instance |
| // Arg 1 is the index |
| |
| builder.loadArgument(0).checkcast(className); |
| builder.loadArgument(1); |
| |
| builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() |
| { |
| @Override |
| public void doSwitch(SwitchBlock block) |
| { |
| for (PlasticFieldImpl f : shimFields) |
| { |
| f.extendShimGet(block); |
| } |
| } |
| }); |
| |
| shimClassNode.methods.add(mn); |
| } |
| |
| private void implementShimSet(ClassNode shimClassNode) |
| { |
| MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null); |
| |
| InstructionBuilder builder = newBuilder(mn); |
| |
| // Arg 0 is the target instance |
| // Arg 1 is the index |
| // Arg 2 is the new value |
| |
| builder.loadArgument(0).checkcast(className); |
| builder.loadArgument(2); |
| |
| builder.loadArgument(1); |
| |
| builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback() |
| { |
| @Override |
| public void doSwitch(SwitchBlock block) |
| { |
| for (PlasticFieldImpl f : shimFields) |
| { |
| f.extendShimSet(block); |
| } |
| } |
| }); |
| |
| builder.returnResult(); |
| |
| shimClassNode.methods.add(mn); |
| } |
| |
| private void implementShimInvoke(ClassNode shimClassNode) |
| { |
| MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null, |
| null); |
| |
| InstructionBuilder builder = newBuilder(mn); |
| |
| // Arg 0 is the target instance |
| // Arg 1 is the index |
| // Arg 2 is the object array of parameters |
| |
| builder.loadArgument(0).checkcast(className); |
| |
| builder.loadArgument(1); |
| |
| builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback() |
| { |
| @Override |
| public void doSwitch(SwitchBlock block) |
| { |
| for (PlasticMethodImpl m : shimMethods) |
| { |
| m.extendShimInvoke(block); |
| } |
| } |
| }); |
| |
| shimClassNode.methods.add(mn); |
| } |
| |
| private void rewriteAdvisedMethods() |
| { |
| for (PlasticMethodImpl method : advisedMethods) |
| { |
| method.rewriteMethodForAdvice(); |
| } |
| } |
| |
| private void interceptFieldAccess(MethodNode methodNode) |
| { |
| InsnList insns = methodNode.instructions; |
| |
| ListIterator<AbstractInsnNode> it = insns.iterator(); |
| |
| while (it.hasNext()) |
| { |
| AbstractInsnNode node = it.next(); |
| |
| int opcode = node.getOpcode(); |
| |
| if (opcode != GETFIELD && opcode != PUTFIELD) |
| { |
| continue; |
| } |
| |
| FieldInsnNode fnode = (FieldInsnNode) node; |
| |
| FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD); |
| |
| if (instrumentation == null) |
| { |
| continue; |
| } |
| |
| // Replace the field access node with the appropriate method invocation. |
| |
| insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription, false)); |
| |
| it.remove(); |
| } |
| } |
| |
| private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead) |
| { |
| // First look in the local fieldInstrumentations, which contains private field instrumentations |
| // (as well as non-private ones). |
| |
| String searchStart = node.owner; |
| |
| if (searchStart.equals(classNode.name)) |
| { |
| FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead); |
| |
| if (result != null) |
| { |
| return result; |
| } |
| |
| // Slight optimization: start the search in the super-classes' fields, since we've already |
| // checked this classes fields. |
| |
| searchStart = classNode.superName; |
| } |
| |
| return pool.getFieldInstrumentation(searchStart, node.name, forRead); |
| } |
| |
| String getInstanceContextFieldName() |
| { |
| if (instanceContextFieldName == null) |
| { |
| instanceContextFieldName = makeUnique(fieldNames, "instanceContext"); |
| |
| // TODO: We could use a protected field and only initialize |
| // it once, in the first base class where it is needed, though that raises the possibilities |
| // of name conflicts (a subclass might introduce a field with a conflicting name). |
| |
| FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC, |
| null, null); |
| |
| classNode.fields.add(node); |
| |
| // Extend the constructor to store the context in a field. |
| |
| constructorBuilder.loadThis().loadArgument(1) |
| .putField(className, instanceContextFieldName, InstanceContext.class); |
| } |
| |
| return instanceContextFieldName; |
| } |
| |
| /** |
| * Creates a new private final field and initializes its value (using the StaticContext). |
| */ |
| String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType, |
| Object injectedFieldValue) |
| { |
| String name = makeUnique(fieldNames, suggestedFieldName); |
| |
| FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null); |
| |
| classNode.fields.add(field); |
| |
| initializeFieldFromStaticContext(name, fieldType, injectedFieldValue); |
| |
| return name; |
| } |
| |
| /** |
| * Initializes a field from the static context. The injected value is added to the static |
| * context and the class constructor updated to assign the value from the context (which includes casting and |
| * possibly unboxing). |
| */ |
| void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue) |
| { |
| int index = staticContext.store(injectedFieldValue); |
| |
| // Although it feels nicer to do the loadThis() later and then swap(), that breaks |
| // on primitive longs and doubles, so its just easier to do the loadThis() first |
| // so its at the right place on the stack for the putField(). |
| |
| constructorBuilder.loadThis(); |
| |
| constructorBuilder.loadArgument(0).loadConstant(index); |
| constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD); |
| constructorBuilder.castOrUnbox(fieldType); |
| |
| constructorBuilder.putField(className, fieldName, fieldType); |
| } |
| |
| void pushInstanceContextFieldOntoStack(InstructionBuilder builder) |
| { |
| builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class); |
| } |
| |
| @Override |
| public PlasticClass getPlasticClass() |
| { |
| return this; |
| } |
| |
| @Override |
| public Class<?> getTransformedClass() |
| { |
| if (transformedClass == null) |
| throw new IllegalStateException(String.format( |
| "Transformed class %s is not yet available because the transformation is not yet complete.", |
| className)); |
| |
| return transformedClass; |
| } |
| |
| private boolean isInheritableMethod(MethodNode node) |
| { |
| return !Modifier.isPrivate(node.access); |
| } |
| |
| @Override |
| public String getClassName() |
| { |
| return className; |
| } |
| |
| InstructionBuilderImpl newBuilder(MethodNode mn) |
| { |
| return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn); |
| } |
| |
| InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn) |
| { |
| return new InstructionBuilderImpl(description, mn, nameCache); |
| } |
| |
| public Set<PlasticMethod> introduceInterface(Class interfaceType) |
| { |
| return introduceInterface(interfaceType, null); |
| } |
| |
| private Set<PlasticMethod> introduceInterface(Class interfaceType, PlasticMethod method) |
| { |
| check(); |
| |
| assert interfaceType != null; |
| |
| if (!interfaceType.isInterface()) |
| throw new IllegalArgumentException(String.format( |
| "Class %s is not an interface; only interfaces may be introduced.", interfaceType.getName())); |
| |
| String interfaceName = nameCache.toInternalName(interfaceType); |
| |
| try |
| { |
| interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader()); |
| } catch (IOException e) |
| { |
| throw new RuntimeException(e); |
| } |
| |
| if (!inheritanceData.isInterfaceImplemented(interfaceName)) |
| { |
| classNode.interfaces.add(interfaceName); |
| inheritanceData.addInterface(interfaceName); |
| } |
| |
| addClassAnnotations(interfaceClassNode); |
| |
| Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>(); |
| Set<Method> alreadyIntroducedMethods = new HashSet<>(); |
| |
| Method[] sortedMethods = interfaceType.getMethods(); |
| Arrays.sort(sortedMethods, METHOD_COMPARATOR); |
| for (Method m : sortedMethods) |
| { |
| MethodDescription description = new MethodDescription(m); |
| |
| if (!isMethodImplemented(description) && !Modifier.isStatic(description.modifiers) && !contains(alreadyIntroducedMethods, m)) |
| { |
| PlasticMethod introducedMethod = introduceMethod(m); |
| introducedMethods.add(introducedMethod); |
| if (method != null) { |
| introducedMethod.delegateTo(method); |
| } |
| alreadyIntroducedMethods.add(m); |
| } |
| } |
| |
| interfaceClassNode = null; |
| |
| return introducedMethods; |
| } |
| |
| @Override |
| public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method) |
| { |
| check(); |
| assert method != null; |
| |
| introduceInterface(interfaceType, method); |
| |
| return this; |
| } |
| |
| private boolean contains(Set<Method> alreadyIntroducedMethods, Method m) { |
| boolean contains = false; |
| for (Method method : alreadyIntroducedMethods) |
| { |
| if (METHOD_COMPARATOR.compare(method, m) == 0) |
| { |
| contains = true; |
| break; |
| } |
| } |
| return false; |
| } |
| |
| private Map<MethodSignature, MethodDescription> createMethodSignatureMap(Class interfaceType) { |
| // TAP-2582: preprocessing the method list so we don't add duplicated |
| // methods, something that happens when an interface has superinterfaces |
| // and they define the same method signature. |
| // In addition, we collect all the thrown checked exceptions, just in case. |
| Map<MethodSignature, MethodDescription> map = new HashMap<>(); |
| for (Method m : interfaceType.getMethods()) |
| { |
| final MethodSignature methodSignature = new MethodSignature(m); |
| final MethodDescription newMethodDescription = new MethodDescription(m); |
| if (!map.containsKey(methodSignature)) |
| { |
| map.put(methodSignature, newMethodDescription); |
| } |
| else |
| { |
| if (newMethodDescription.checkedExceptionTypes != null && newMethodDescription.checkedExceptionTypes.length > 0) |
| { |
| final MethodDescription methodDescription = map.get(methodSignature); |
| final Set<String> checkedExceptionTypes = new HashSet<>(); |
| checkedExceptionTypes.addAll(Arrays.asList(methodDescription.checkedExceptionTypes)); |
| checkedExceptionTypes.addAll(Arrays.asList(newMethodDescription.checkedExceptionTypes)); |
| map.put(methodSignature, new MethodDescription( |
| methodDescription, |
| checkedExceptionTypes.toArray(new String[checkedExceptionTypes.size()]))); |
| } |
| } |
| } |
| return map; |
| } |
| |
| final private static class MethodSignature implements Comparable<MethodSignature> { |
| final private Method method; |
| final private String name; |
| final private Class<?>[] parameterTypes; |
| |
| public MethodSignature(Method method) { |
| this.method = method; |
| this.name = method.getName(); |
| this.parameterTypes = method.getParameterTypes(); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + Arrays.hashCode(parameterTypes); |
| result = prime * result + ((name == null) ? 0 : name.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) return true; |
| if (obj == null) return false; |
| if (getClass() != obj.getClass()) return false; |
| |
| MethodSignature other = (MethodSignature) obj; |
| if (!Arrays.equals(parameterTypes, other.parameterTypes)) return false; |
| |
| return name == null ? other.name == null : name.equals(other.name); |
| } |
| |
| @Override |
| public int compareTo(MethodSignature o) { |
| return method.getName().compareTo(o.method.getName()); |
| } |
| } |
| |
| @Override |
| public PlasticClass addToString(final String toStringValue) |
| { |
| check(); |
| |
| if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION)) |
| { |
| introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback() |
| { |
| @Override |
| public void doBuild(InstructionBuilder builder) |
| { |
| builder.loadConstant(toStringValue).returnResult(); |
| } |
| }); |
| } |
| |
| return this; |
| } |
| |
| @Override |
| public boolean isMethodImplemented(MethodDescription description) |
| { |
| return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description)); |
| } |
| |
| @Override |
| public boolean isInterfaceImplemented(Class interfaceType) |
| { |
| assert interfaceType != null; |
| assert interfaceType.isInterface(); |
| |
| String interfaceName = nameCache.toInternalName(interfaceType); |
| |
| return inheritanceData.isInterfaceImplemented(interfaceName); |
| } |
| |
| @Override |
| public String getSuperClassName() |
| { |
| return superClassName; |
| } |
| |
| @Override |
| public PlasticClass onConstruct(ConstructorCallback callback) |
| { |
| check(); |
| |
| assert callback != null; |
| |
| constructorCallbacks.add(callback); |
| |
| return this; |
| } |
| |
| void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method) |
| { |
| FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); |
| |
| fieldInstrumentations.write.put(fieldName, fi); |
| |
| if (!(proxy || privateField)) |
| { |
| pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi); |
| } |
| } |
| |
| void redirectFieldRead(String fieldName, boolean privateField, MethodNode method) |
| { |
| FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc); |
| |
| fieldInstrumentations.read.put(fieldName, fi); |
| |
| if (!(proxy || privateField)) |
| { |
| pool.setFieldReadInstrumentation(classNode.name, fieldName, fi); |
| } |
| } |
| |
| final private MethodComparator METHOD_COMPARATOR = new MethodComparator(); |
| |
| final private class MethodComparator implements Comparator<Method> |
| { |
| |
| @Override |
| public int compare(Method o1, Method o2) |
| { |
| |
| int comparison = o1.getName().compareTo(o2.getName()); |
| |
| if (comparison == 0) |
| { |
| comparison = o1.getParameterTypes().length - o2.getParameterTypes().length; |
| } |
| |
| if (comparison == 0) |
| { |
| final int count = o1.getParameterTypes().length; |
| for (int i = 0; i < count; i++) |
| { |
| Class p1 = o1.getParameterTypes()[i]; |
| Class p2 = o1.getParameterTypes()[i]; |
| if (!p1.equals(p2)) |
| { |
| comparison = p1.getName().compareTo(p2.getName()); |
| break; |
| } |
| } |
| } |
| return comparison; |
| } |
| } |
| |
| private List<Method> getUniqueMethods(Class interfaceType) |
| { |
| final List<Method> unique = new ArrayList<>(Arrays.asList(interfaceType.getMethods())); |
| Collections.sort(unique, METHOD_COMPARATOR); |
| Method last = null; |
| Iterator<Method> iterator = unique.iterator(); |
| while (iterator.hasNext()) |
| { |
| Method m = iterator.next(); |
| if (last != null && METHOD_COMPARATOR.compare(m, last) == 0) |
| { |
| last = m; |
| iterator.remove(); |
| } |
| } |
| return unique; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return String.format("PlasticClassImpl[%s]", className); |
| } |
| } |