    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.internal.plastic;
    014
    015import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor;
    016import org.apache.tapestry5.internal.plastic.asm.Opcodes;
    017import org.apache.tapestry5.internal.plastic.asm.Type;
    018import org.apache.tapestry5.internal.plastic.asm.tree.*;
    019import org.apache.tapestry5.plastic.*;
    020
    021import java.io.IOException;
    022import java.lang.annotation.Annotation;
    023import java.lang.reflect.Constructor;
    024import java.lang.reflect.Method;
    025import java.lang.reflect.Modifier;
    026import java.util.*;
    027
    028@SuppressWarnings("all")
    029public class PlasticClassImpl extends Lockable implements PlasticClass, InternalPlasticClassTransformation, Opcodes
    030{
    031    private static final String NOTHING_TO_VOID = "()V";
    032
    033    static final String CONSTRUCTOR_NAME = "<init>";
    034
    035    private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;";
    036
    037    private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V";
    038
    039    private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format(
    040            "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class)));
    041
    042    static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils
    043            .toInternalName(AbstractMethodInvocation.class.getName());
    044
    045    private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type
    046            .getInternalName(PlasticClassHandleShim.class);
    047
    048    static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class);
    049
    050    private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class);
    051
    052    private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME);
    053
    054    private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME,
    055            INSTANCE_CONTEXT_INTERNAL_NAME);
    056
    057    static final Method STATIC_CONTEXT_GET_METHOD = toMethod(StaticContext.class, "get", int.class);
    058
    059    static final Method COMPUTED_VALUE_GET_METHOD = toMethod(ComputedValue.class, "get", InstanceContext.class);
    060
    061    private static final Method CONSTRUCTOR_CALLBACK_METHOD = toMethod(ConstructorCallback.class, "onConstruct",
    062            Object.class, InstanceContext.class);
    063
    064    private static String toDesc(String internalName)
    065    {
    066        return "L" + internalName + ";";
    067    }
    068
    069    private static Method toMethod(Class declaringClass, String methodName, Class... parameterTypes)
    070    {
    071        return PlasticUtils.getMethod(declaringClass, methodName, parameterTypes);
    072    }
    073
    074    static <T> T safeArrayDeref(T[] array, int index)
    075    {
    076        if (array == null)
    077            return null;
    078
    079        return array[index];
    080    }
    081
    082    // Now past the inner classes; these are the instance variables of PlasticClassImpl proper:
    083
    084    final ClassNode classNode;
    085
    086    final PlasticClassPool pool;
    087
    088    private final boolean proxy;
    089
    090    final String className;
    091
    092    private final String superClassName;
    093
    094    private final AnnotationAccess annotationAccess;
    095
    096    // All the non-introduced (and non-constructor) methods, in sorted order
    097
    098    private final List<PlasticMethodImpl> methods;
    099
    100    private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<>();
    101
    102    final Set<String> methodNames = new HashSet<>();
    103
    104    private final List<ConstructorCallback> constructorCallbacks = PlasticInternalUtils.newList();
    105
    106    // All non-introduced instance fields
    107
    108    private final List<PlasticFieldImpl> fields;
    109
    110    /**
    111     * Methods that require special attention inside {@link #createInstantiator()} because they
    112     * have method advice.
    113     */
    114    final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet();
    115
    116    final NameCache nameCache = new NameCache();
    117
    118    // This is generated from fields, as necessary
    119    List<PlasticField> unclaimedFields;
    120
    121    private final Set<String> fieldNames = PlasticInternalUtils.newSet();
    122
    123    final StaticContext staticContext;
    124
    125    final InheritanceData parentInheritanceData, inheritanceData;
    126
    127    // MethodNodes in which field transformations should occur; this is most existing and
    128    // introduced methods, outside of special access methods.
    129
    130    final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet();
    131
    132    // Tracks any methods that the Shim class uses to gain access to fields; used to ensure that
    133    // such methods are not optimized away incorrectly.
    134    final Set<MethodNode> shimInvokedMethods = PlasticInternalUtils.newSet();
    135
    136
    137    /**
    138     * Tracks instrumentations of fields of this class, including private fields which are not published into the
    139     * {@link PlasticClassPool}.
    140     */
    141    private final FieldInstrumentations fieldInstrumentations;
    142
    143    /**
    144     * This normal no-arguments constructor, or null. By the end of the transformation
    145     * this will be converted into an ordinary method.
    146     */
    147    private MethodNode originalConstructor;
    148
    149    private final MethodNode newConstructor;
    150
    151    final InstructionBuilder constructorBuilder;
    152
    153    private String instanceContextFieldName;
    154
    155    private Class<?> transformedClass;
    156
    157    // Indexes used to identify fields or methods in the shim
    158    int nextFieldIndex = 0;
    159
    160    int nextMethodIndex = 0;
    161
    162    // Set of fields that need to contribute to the shim and gain access to it
    163
    164    final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet();
    165
    166    // Set of methods that need to contribute to the shim and gain access to it
    167
    168    final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet();
    169
    170    final ClassNode implementationClassNode;
    171
    172    private ClassNode interfaceClassNode;
    173
    174    /**
    175     * @param classNode
    176     * @param implementationClassNode
    177     * @param pool
    178     * @param parentInheritanceData
    179     * @param parentStaticContext
    180     * @param proxy
    181     */
    182    public PlasticClassImpl(ClassNode classNode, ClassNode implementationClassNode, PlasticClassPool pool, InheritanceData parentInheritanceData,
    183                            StaticContext parentStaticContext, boolean proxy)
    184    {
    185        this.classNode = classNode;
    186        this.pool = pool;
    187        this.proxy = proxy;
    188        this.implementationClassNode = implementationClassNode;
    189
    190        staticContext = parentStaticContext.dupe();
    191
    192        className = PlasticInternalUtils.toClassName(classNode.name);
    193        superClassName = PlasticInternalUtils.toClassName(classNode.superName);
    194        int lastIndexOfDot = className.lastIndexOf('.');
    195
    196        String packageName = lastIndexOfDot > -1 ? className.substring(0, lastIndexOfDot) : "";
    197
    198        fieldInstrumentations = new FieldInstrumentations(classNode.superName);
    199
    200        annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations),
    201                pool.createAnnotationAccess(superClassName));
    202
    203        this.parentInheritanceData = parentInheritanceData;
    204
    205        inheritanceData = parentInheritanceData.createChild(packageName);
    206
    207        for (String interfaceName : classNode.interfaces)
    208        {
    209            inheritanceData.addInterface(interfaceName);
    210        }
    211
    212        methods = new ArrayList<>(classNode.methods.size());
    213
    214        String invalidConstructorMessage = invalidConstructorMessage();
    215
    216        for (MethodNode node : classNode.methods)
    217        {
    218            if (node.name.equals(CONSTRUCTOR_NAME))
    219            {
    220                if (node.desc.equals(NOTHING_TO_VOID))
    221                {
    222                    originalConstructor = node;
    223                    fieldTransformMethods.add(node);
    224                } else
    225                {
    226                    node.instructions.clear();
    227
    228                    newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage);
    229                }
    230
    231                continue;
    232            }
    233
    234            /*
    235             * Static methods are not visible to the main API methods, but they must still be transformed,
    236             * in case they directly access fields. In addition, track their names to avoid collisions.
    237             */
    238            if (Modifier.isStatic(node.access))
    239            {
    240                if (isInheritableMethod(node))
    241                {
    242                    inheritanceData.addMethod(node.name, node.desc, node.access == 0);
    243                }
    244
    245                methodNames.add(node.name);
    246
    247                fieldTransformMethods.add(node);
    248
    249                continue;
    250            }
    251
    252            if (!Modifier.isAbstract(node.access))
    253            {
    254                fieldTransformMethods.add(node);
    255            }
    256
    257            PlasticMethodImpl pmi = new PlasticMethodImpl(this, node);
    258
    259            methods.add(pmi);
    260            description2method.put(pmi.getDescription(), pmi);
    261
    262            if (isInheritableMethod(node))
    263            {
    264                inheritanceData.addMethod(node.name, node.desc, node.access == 0);
    265            }
    266
    267            methodNames.add(node.name);
    268        }
    269
    270        methodNames.addAll(parentInheritanceData.methodNames());
    271
    272        Collections.sort(methods);
    273
    274        fields = new ArrayList<>(classNode.fields.size());
    275
    276        for (FieldNode node : classNode.fields)
    277        {
    278            fieldNames.add(node.name);
    279
    280            // Ignore static fields.
    281
    282            if (Modifier.isStatic(node.access))
    283                continue;
    284
    285            // When we instrument the field such that it must be private, we'll get an exception.
    286
    287            fields.add(new PlasticFieldImpl(this, node));
    288        }
    289
    290        Collections.sort(fields);
    291
    292        // TODO: Make the output class's constructor protected, and create a shim class to instantiate it
    293        // efficiently (without reflection).
    294        newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null);
    295        constructorBuilder = newBuilder(newConstructor);
    296
    297        // Start by calling the super-class no args constructor
    298
    299        if (parentInheritanceData.isTransformed())
    300        {
    301            // If the parent is transformed, our first step is always to invoke its constructor.
    302
    303            constructorBuilder.loadThis().loadArgument(0).loadArgument(1);
    304            constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(),
    305                    InstanceContext.class.getName());
    306        } else
    307        {
    308            // Assumes the base class includes a visible constructor that takes no arguments.
    309            // TODO: Do a proper check for this case and throw a meaningful exception
    310            // if not present.
    311
    312            constructorBuilder.loadThis().invokeConstructor(superClassName);
    313        }
    314
    315        // During the transformation, we'll be adding code to the constructor to pull values
    316        // out of the static or instance context and assign them to fields.
    317
    318        // Later on, we'll add the RETURN opcode
    319    }
    320
    321    private String invalidConstructorMessage()
    322    {
    323        return String.format("Class %s has been transformed and may not be directly instantiated.", className);
    324    }
    325
    326    @Override
    327    public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
    328    {
    329        check();
    330
    331        return annotationAccess.hasAnnotation(annotationType);
    332    }
    333
    334    @Override
    335    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
    336    {
    337        check();
    338
    339        return annotationAccess.getAnnotation(annotationType);
    340    }
    341
    342    private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, MethodNode implementationMethodNode)
    343    {
    344        // visits the method attributes
    345        int i, j, n;
    346        if (implementationMethodNode.annotationDefault != null)
    347        {
    348            AnnotationVisitor av = methodNode.visitAnnotationDefault();
    349            AnnotationNode.accept(av, null, implementationMethodNode.annotationDefault);
    350            if (av != null)
    351            {
    352                av.visitEnd();
    353            }
    354        }
    355        n = implementationMethodNode.visibleAnnotations == null ? 0 : implementationMethodNode.visibleAnnotations.size();
    356        for (i = 0; i < n; ++i)
    357        {
    358            AnnotationNode an = implementationMethodNode.visibleAnnotations.get(i);
    359            an.accept(methodNode.visitAnnotation(an.desc, true));
    360        }
    361        n = implementationMethodNode.invisibleAnnotations == null ? 0 : implementationMethodNode.invisibleAnnotations.size();
    362        for (i = 0; i < n; ++i)
    363        {
    364            AnnotationNode an = implementationMethodNode.invisibleAnnotations.get(i);
    365            an.accept(methodNode.visitAnnotation(an.desc, false));
    366        }
    367        n = implementationMethodNode.visibleParameterAnnotations == null
    368                ? 0
    369                : implementationMethodNode.visibleParameterAnnotations.length;
    370        for (i = 0; i < n; ++i)
    371        {
    372            List<?> l = implementationMethodNode.visibleParameterAnnotations[i];
    373            if (l == null)
    374            {
    375                continue;
    376            }
    377            for (j = 0; j < l.size(); ++j)
    378            {
    379                AnnotationNode an = (AnnotationNode) l.get(j);
    380                an.accept(methodNode.visitParameterAnnotation(i, an.desc, true));
    381            }
    382        }
    383        n = implementationMethodNode.invisibleParameterAnnotations == null
    384                ? 0
    385                : implementationMethodNode.invisibleParameterAnnotations.length;
    386        for (i = 0; i < n; ++i)
    387        {
    388            List<?> l = implementationMethodNode.invisibleParameterAnnotations[i];
    389            if (l == null)
    390            {
    391                continue;
    392            }
    393            for (j = 0; j < l.size(); ++j)
    394            {
    395                AnnotationNode an = (AnnotationNode) l.get(j);
    396                an.accept(methodNode.visitParameterAnnotation(i, an.desc, false));
    397            }
    398        }
    399
    400        methodNode.visitEnd();
    401
    402    }
    403
    404    private static void removeDuplicatedAnnotations(MethodNode node)
    405    {
    406
    407        removeDuplicatedAnnotations(node.visibleAnnotations);
    408        removeDuplicatedAnnotations(node.invisibleAnnotations);
    409
    410        if (node.visibleParameterAnnotations != null)
    411        {
    412            for (List<AnnotationNode> list : node.visibleParameterAnnotations)
    413            {
    414                removeDuplicatedAnnotations(list);
    415            }
    416        }
    417
    418        if (node.invisibleParameterAnnotations != null)
    419        {
    420            for (List<AnnotationNode> list : node.invisibleParameterAnnotations)
    421            {
    422                removeDuplicatedAnnotations(list);
    423            }
    424        }
    425
    426    }
    427
    428    private static void removeDuplicatedAnnotations(ClassNode node)
    429    {
    430        removeDuplicatedAnnotations(node.visibleAnnotations, true);
    431        removeDuplicatedAnnotations(node.invisibleAnnotations, true);
    432    }
    433
    434    private static void removeDuplicatedAnnotations(List<AnnotationNode> list) {
    435        removeDuplicatedAnnotations(list, false);
    436    }
    437
    438    private static void removeDuplicatedAnnotations(List<AnnotationNode> list, boolean reverse) {
    439
    440        if (list != null)
    441        {
    442
    443            final Set<String> annotations = new HashSet<>();
    444            final List<AnnotationNode> toBeRemoved = new ArrayList<>();
    445            final List<AnnotationNode> toBeIterated;
    446
    447            if (reverse)
    448            {
    449                toBeIterated = new ArrayList<>(list);
    450                Collections.reverse(toBeIterated);
    451            }
    452            else {
    453                toBeIterated = list;
    454            }
    455
    456            for (AnnotationNode annotationNode : toBeIterated)
    457            {
    458                if (annotations.contains(annotationNode.desc))
    459                {
    460                    toBeRemoved.add(annotationNode);
    461                }
    462                else
    463                {
    464                    annotations.add(annotationNode.desc);
    465                }
    466            }
    467
    468            for (AnnotationNode annotationNode : toBeRemoved)
    469            {
    470                list.remove(annotationNode);
    471            }
    472
    473        }
    474
    475    }
    476
    477    private static String getParametersDesc(MethodNode methodNode) {
    478        return methodNode.desc.substring(methodNode.desc.indexOf('(') + 1, methodNode.desc.lastIndexOf(')'));
    479    }
    480
    481    private static MethodNode findExactMatchMethod(MethodNode methodNode, ClassNode source) {
    482
    483        MethodNode found = null;
    484
    485        final String methodDescription = getParametersDesc(methodNode);
    486
    487        for (MethodNode implementationMethodNode : source.methods)
    488        {
    489
    490            final String implementationMethodDescription = getParametersDesc(implementationMethodNode);
    491            if (methodNode.name.equals(implementationMethodNode.name) &&
    492                    // We don't want synthetic methods.
    493                    ((implementationMethodNode.access & Opcodes.ACC_SYNTHETIC) == 0)
    494                    && (methodDescription.equals(implementationMethodDescription)))
    495            {
    496                found = implementationMethodNode;
    497                break;
    498            }
    499        }
    500
    501        return found;
    502
    503    }
    504
    505    private static List<Class> getJavaParameterTypes(MethodNode methodNode) {
    506        final ClassLoader classLoader = PlasticInternalUtils.class.getClassLoader();
    507        Type[] parameterTypes = Type.getArgumentTypes(methodNode.desc);
    508        List<Class> list = new ArrayList<>();
    509        for (Type type : parameterTypes)
    510        {
    511            try
    512            {
    513                list.add(PlasticInternalUtils.toClass(classLoader, type.getClassName()));
    514            }
    515            catch (ClassNotFoundException e)
    516            {
    517                throw new RuntimeException(e); // shouldn't happen anyway
    518            }
    519        }
    520        return list;
    521    }
    522
    523    /**
    524     * Returns the first method which matches the given methodNode.
    525     * FIXME: this may not find the correct method if the correct one is declared after
    526     * another in which all parameters are supertypes of the parameters of methodNode.
    527     * To solve this, we would need to dig way deeper than we have time for this.
    528     * @param methodNode
    529     * @param classNode
    530     * @return
    531     */
    532    private static MethodNode findGenericMethod(MethodNode methodNode, ClassNode classNode)
    533    {
    534
    535        MethodNode found = null;
    536
    537        List<Class> parameterTypes = getJavaParameterTypes(methodNode);
    538
    539        for (MethodNode implementationMethodNode : classNode.methods)
    540        {
    541
    542            if (methodNode.name.equals(implementationMethodNode.name))
    543            {
    544
    545                final List<Class> implementationParameterTypes = getJavaParameterTypes(implementationMethodNode);
    546
    547                if (parameterTypes.size() == implementationParameterTypes.size())
    548                {
    549
    550                    boolean matches = true;
    551                    for (int i = 0; i < parameterTypes.size(); i++)
    552                    {
    553                        final Class implementationParameterType = implementationParameterTypes.get(i);
    554                        final Class parameterType = parameterTypes.get(i);
    555                        if (!parameterType.isAssignableFrom(implementationParameterType)) {
    556                            matches = false;
    557                            break;
    558                        }
    559
    560                    }
    561
    562                    if (matches && !isBridge(implementationMethodNode))
    563                    {
    564                        found = implementationMethodNode;
    565                        break;
    566                    }
    567
    568                }
    569
    570            }
    571
    572        }
    573
    574        return found;
    575
    576    }
    577
    578    private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, ClassNode source)
    579    {
    580        if (source != null)
    581        {
    582
    583            MethodNode candidate = findExactMatchMethod(methodNode, source);
    584
    585            final String parametersDesc = getParametersDesc(methodNode);
    586
    587            // candidate will be null when the method has generic parameters
    588            if (candidate == null && parametersDesc.trim().length() > 0)
    589            {
    590                candidate = findGenericMethod(methodNode, source);
    591            }
    592
    593            if (candidate != null)
    594            {
    595                addMethodAndParameterAnnotationsFromExistingClass(methodNode, candidate);
    596            }
    597
    598        }
    599
    600    }
    601
    602    /**
    603     * Tells whether a given method is a bridge one or not.
    604     * Notice the flag for bridge method is the same as volatile field. Java 6 doesn't have
    605     * Modifiers.isBridge(), so we use a workaround.
    606     */
    607    private static boolean isBridge(MethodNode methodNode)
    608    {
    609        return Modifier.isVolatile(methodNode.access);
    610    }
    611
    612    @Override
    613    public PlasticClass proxyInterface(Class interfaceType, PlasticField field)
    614    {
    615        check();
    616
    617        assert field != null;
    618
    619        introduceInterface(interfaceType);
    620
    621        for (Method m : getUniqueMethods(interfaceType))
    622        {
    623            final MethodDescription description = new MethodDescription(m);
    624            if(Modifier.isStatic(description.modifiers))
    625            {
    626                continue;
    627            }
    628            introduceMethod(description).delegateTo(field);
    629        }
    630
    631        return this;
    632    }
    633
    634    @Override
    635    public ClassInstantiator createInstantiator()
    636    {
    637        lock();
    638
    639        addClassAnnotations(implementationClassNode);
    640        removeDuplicatedAnnotations(classNode);
    641
    642        createShimIfNeeded();
    643
    644        interceptFieldAccess();
    645
    646        rewriteAdvisedMethods();
    647
    648        completeConstructor();
    649
    650        transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
    651
    652        return createInstantiatorFromClass(transformedClass);
    653    }
    654
    655    private void addClassAnnotations(ClassNode otherClassNode)
    656    {
    657        // Copy annotations from implementation if available.
    658        // Code adapted from ClassNode.accept(), as we just want to copy
    659        // the annotations and nothing more.
    660        if (otherClassNode != null)
    661        {
    662
    663            int i, n;
    664            n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size();
    665            for (i = 0; i < n; ++i)
    666            {
    667                AnnotationNode an = otherClassNode.visibleAnnotations.get(i);
    668                an.accept(classNode.visitAnnotation(an.desc, true));
    669            }
    670            n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size();
    671            for (i = 0; i < n; ++i)
    672            {
    673                AnnotationNode an = otherClassNode.invisibleAnnotations.get(i);
    674                an.accept(classNode.visitAnnotation(an.desc, false));
    675            }
    676
    677        }
    678    }
    679
    680    private ClassInstantiator createInstantiatorFromClass(Class clazz)
    681    {
    682        try
    683        {
    684            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
    685
    686            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
    687        } catch (Exception ex)
    688        {
    689            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
    690                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
    691        }
    692    }
    693
    694    private void completeConstructor()
    695    {
    696        if (originalConstructor != null)
    697        {
    698            convertOriginalConstructorToMethod();
    699        }
    700
    701        invokeCallbacks();
    702
    703        constructorBuilder.returnResult();
    704
    705        classNode.methods.add(newConstructor);
    706    }
    707
    708    private void invokeCallbacks()
    709    {
    710        for (ConstructorCallback callback : constructorCallbacks)
    711        {
    712            invokeCallback(callback);
    713        }
    714    }
    715
    716    private void invokeCallback(ConstructorCallback callback)
    717    {
    718        int index = staticContext.store(callback);
    719
    720        // First, load the callback
    721
    722        constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
    723
    724        // Load this and the InstanceContext
    725        constructorBuilder.loadThis().loadArgument(1);
    726
    727        constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
    728    }
    729
    730
    731    /**
    732     * Convert the original constructor into a private method invoked from the
    733     * generated constructor.
    734     */
    735    private void convertOriginalConstructorToMethod()
    736    {
    737        String initializerName = makeUnique(methodNames, "initializeInstance");
    738
    739        int originalAccess = originalConstructor.access;
    740
    741        originalConstructor.access = ACC_PRIVATE;
    742        originalConstructor.name = initializerName;
    743
    744        stripOutSuperConstructorCall(originalConstructor);
    745
    746        constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
    747
    748        // And replace it with a constructor that throws an exception
    749
    750        MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
    751                null);
    752
    753        newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
    754
    755        classNode.methods.add(replacementConstructor);
    756    }
    757
    758    private void stripOutSuperConstructorCall(MethodNode cons)
    759    {
    760        InsnList ins = cons.instructions;
    761
    762        ListIterator li = ins.iterator();
    763
    764        // Look for the ALOAD 0 (i.e., push this on the stack)
    765        while (li.hasNext())
    766        {
    767            AbstractInsnNode node = (AbstractInsnNode) li.next();
    768
    769            if (node.getOpcode() == ALOAD)
    770            {
    771                VarInsnNode varNode = (VarInsnNode) node;
    772
    773                assert varNode.var == 0;
    774
    775                // Remove the ALOAD
    776                li.remove();
    777                break;
    778            }
    779        }
    780
    781        // Look for the call to the super-class, an INVOKESPECIAL
    782        while (li.hasNext())
    783        {
    784            AbstractInsnNode node = (AbstractInsnNode) li.next();
    785
    786            if (node.getOpcode() == INVOKESPECIAL)
    787            {
    788                MethodInsnNode mnode = (MethodInsnNode) node;
    789
    790                assert mnode.owner.equals(classNode.superName);
    791                assert mnode.name.equals(CONSTRUCTOR_NAME);
    792                assert mnode.desc.equals(cons.desc);
    793
    794                li.remove();
    795                return;
    796            }
    797        }
    798
    799        throw new AssertionError("Could not convert constructor to simple method.");
    800    }
    801
    802    @Override
    803    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
    804    {
    805        check();
    806
    807        List<PlasticField> result = getAllFields();
    808
    809        Iterator<PlasticField> iterator = result.iterator();
    810
    811        while (iterator.hasNext())
    812        {
    813            PlasticField plasticField = iterator.next();
    814
    815            if (!plasticField.hasAnnotation(annotationType))
    816                iterator.remove();
    817        }
    818
    819        return result;
    820    }
    821
    822    @Override
    823    public List<PlasticField> getAllFields()
    824    {
    825        check();
    826
    827        return new ArrayList<>(fields);
    828    }
    829
    830    @Override
    831    public List<PlasticField> getUnclaimedFields()
    832    {
    833        check();
    834
    835        // Initially null, and set back to null by PlasticField.claim().
    836
    837        if (unclaimedFields == null)
    838        {
    839            unclaimedFields = new ArrayList<>(fields.size());
    840
    841            for (PlasticField f : fields)
    842            {
    843                if (!f.isClaimed())
    844                    unclaimedFields.add(f);
    845            }
    846        }
    847
    848        return unclaimedFields;
    849    }
    850
    851    @Override
    852    public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
    853                                                String[] exceptionTypes)
    854    {
    855        check();
    856
    857        assert PlasticInternalUtils.isNonBlank(typeName);
    858        assert PlasticInternalUtils.isNonBlank(suggestedName);
    859
    860        String name = makeUnique(methodNames, suggestedName);
    861
    862        MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
    863                exceptionTypes);
    864
    865        return introduceMethod(description);
    866    }
    867
    868    @Override
    869    public PlasticField introduceField(String className, String suggestedName)
    870    {
    871        check();
    872
    873        assert PlasticInternalUtils.isNonBlank(className);
    874        assert PlasticInternalUtils.isNonBlank(suggestedName);
    875
    876        String name = makeUnique(fieldNames, suggestedName);
    877
    878        // No signature and no initial value
    879
    880        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
    881
    882        classNode.fields.add(fieldNode);
    883
    884        fieldNames.add(name);
    885
    886        PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
    887
    888        return newField;
    889    }
    890
    891    @Override
    892    public PlasticField introduceField(Class fieldType, String suggestedName)
    893    {
    894        assert fieldType != null;
    895
    896        return introduceField(nameCache.toTypeName(fieldType), suggestedName);
    897    }
    898
    899    String makeUnique(Set<String> values, String input)
    900    {
    901        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
    902    }
    903
    904    @Override
    905    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
    906    {
    907        check();
    908
    909        List<PlasticMethod> result = getMethods();
    910
    911        result.removeIf(method -> !method.hasAnnotation(annotationType));
    912
    913        return result;
    914    }
    915
    916    @Override
    917    public List<PlasticMethod> getMethods()
    918    {
    919        check();
    920
    921        return new ArrayList<>(methods);
    922    }
    923
    924    @Override
    925    public PlasticMethod introduceMethod(MethodDescription description)
    926    {
    927        check();
    928
    929        if (Modifier.isAbstract(description.modifiers))
    930        {
    931            description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
    932        }
    933
    934        PlasticMethod result = description2method.get(description);
    935
    936        if (result == null)
    937        {
    938            result = createNewMethod(description);
    939
    940            description2method.put(description, result);
    941        }
    942
    943        methodNames.add(description.methodName);
    944
    945        // Note that is it not necessary to add the new MethodNode to
    946        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
    947        // ever access instance fields) ... unless the caller invokes changeImplementation().
    948
    949        return result;
    950    }
    951
    952    @Override
    953    public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
    954    {
    955        check();
    956
    957        // TODO: optimize this so that a default implementation is not created.
    958
    959        return introduceMethod(description).changeImplementation(callback);
    960    }
    961
    962    @Override
    963    public PlasticMethod introduceMethod(Method method)
    964    {
    965        check();
    966        return introduceMethod(new MethodDescription(method));
    967    }
    968
    969    void addMethod(MethodNode methodNode)
    970    {
    971        classNode.methods.add(methodNode);
    972
    973        methodNames.add(methodNode.name);
    974
    975        if (isInheritableMethod(methodNode))
    976        {
    977            inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0);
    978        }
    979    }
    980
    981    private PlasticMethod createNewMethod(MethodDescription description)
    982    {
    983        if (Modifier.isStatic(description.modifiers))
    984            throw new IllegalArgumentException(String.format(
    985                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
    986                    description, className));
    987
    988        String desc = nameCache.toDesc(description);
    989
    990        String[] exceptions = new String[description.checkedExceptionTypes.length];
    991        for (int i = 0; i < exceptions.length; i++)
    992        {
    993            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
    994        }
    995
    996        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
    997                description.genericSignature, exceptions);
    998        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
    999
    1000        if (!isOverride)
    1001        {
    1002            addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode);
    1003            addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode);
    1004            removeDuplicatedAnnotations(methodNode);
    1005        }
    1006
    1007        if (isOverride)
    1008            createOverrideOfBaseClassImpl(description, methodNode);
    1009        else
    1010            createNewMethodImpl(description, methodNode);
    1011
    1012        addMethod(methodNode);
    1013
    1014        return new PlasticMethodImpl(this, methodNode);
    1015    }
    1016
    1017    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
    1018    {
    1019        newBuilder(methodDescription, methodNode).returnDefaultValue();
    1020    }
    1021
    1022    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
    1023    {
    1024        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
    1025
    1026        builder.loadThis();
    1027        builder.loadArguments();
    1028        builder.invokeSpecial(superClassName, methodDescription);
    1029        builder.returnResult();
    1030    }
    1031
    1032    /**
    1033     * Iterates over all non-introduced methods, including the original constructor. For each
    1034     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
    1035     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
    1036     * for the class has been created, as the shim may create methods that contain references to fields that may be
    1037     * subject to field access interception.
    1038     */
    1039    private void interceptFieldAccess()
    1040    {
    1041        for (MethodNode node : fieldTransformMethods)
    1042        {
    1043            // Intercept field access inside the method, tracking which access methods
    1044            // are actually used by removing them from accessMethods
    1045
    1046            interceptFieldAccess(node);
    1047        }
    1048    }
    1049
    1050    /**
    1051     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
    1052     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
    1053     */
    1054    private void createShimIfNeeded()
    1055    {
    1056        if (shimFields.isEmpty() && shimMethods.isEmpty())
    1057            return;
    1058
    1059        PlasticClassHandleShim shim = createShimInstance();
    1060
    1061        installShim(shim);
    1062    }
    1063
    1064    public void installShim(PlasticClassHandleShim shim)
    1065    {
    1066        for (PlasticFieldImpl f : shimFields)
    1067        {
    1068            f.installShim(shim);
    1069        }
    1070
    1071        for (PlasticMethodImpl m : shimMethods)
    1072        {
    1073            m.installShim(shim);
    1074        }
    1075    }
    1076
    1077    public PlasticClassHandleShim createShimInstance()
    1078    {
    1079        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
    1080
    1081        ClassNode shimClassNode = new ClassNode();
    1082
    1083        shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
    1084                null);
    1085
    1086        implementConstructor(shimClassNode);
    1087
    1088        if (!shimFields.isEmpty())
    1089        {
    1090            implementShimGet(shimClassNode);
    1091            implementShimSet(shimClassNode);
    1092        }
    1093
    1094        if (!shimMethods.isEmpty())
    1095        {
    1096            implementShimInvoke(shimClassNode);
    1097        }
    1098
    1099        return instantiateShim(shimClassNode);
    1100    }
    1101
    1102    private void implementConstructor(ClassNode shimClassNode)
    1103    {
    1104        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
    1105
    1106        InstructionBuilder builder = newBuilder(mn);
    1107
    1108        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
    1109
    1110        shimClassNode.methods.add(mn);
    1111
    1112    }
    1113
    1114    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
    1115    {
    1116        try
    1117        {
    1118            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
    1119
    1120            return (PlasticClassHandleShim) shimClass.newInstance();
    1121        } catch (Exception ex)
    1122        {
    1123            throw new RuntimeException(
    1124                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
    1125                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
    1126                            PlasticInternalUtils.toMessage(ex)), ex);
    1127        }
    1128    }
    1129
    1130    private void implementShimGet(ClassNode shimClassNode)
    1131    {
    1132        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
    1133
    1134        InstructionBuilder builder = newBuilder(mn);
    1135
    1136        // Arg 0 is the target instance
    1137        // Arg 1 is the index
    1138
    1139        builder.loadArgument(0).checkcast(className);
    1140        builder.loadArgument(1);
    1141
    1142        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
    1143        {
    1144            @Override
    1145            public void doSwitch(SwitchBlock block)
    1146            {
    1147                for (PlasticFieldImpl f : shimFields)
    1148                {
    1149                    f.extendShimGet(block);
    1150                }
    1151            }
    1152        });
    1153
    1154        shimClassNode.methods.add(mn);
    1155    }
    1156
    1157    private void implementShimSet(ClassNode shimClassNode)
    1158    {
    1159        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
    1160
    1161        InstructionBuilder builder = newBuilder(mn);
    1162
    1163        // Arg 0 is the target instance
    1164        // Arg 1 is the index
    1165        // Arg 2 is the new value
    1166
    1167        builder.loadArgument(0).checkcast(className);
    1168        builder.loadArgument(2);
    1169
    1170        builder.loadArgument(1);
    1171
    1172        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
    1173        {
    1174            @Override
    1175            public void doSwitch(SwitchBlock block)
    1176            {
    1177                for (PlasticFieldImpl f : shimFields)
    1178                {
    1179                    f.extendShimSet(block);
    1180                }
    1181            }
    1182        });
    1183
    1184        builder.returnResult();
    1185
    1186        shimClassNode.methods.add(mn);
    1187    }
    1188
    1189    private void implementShimInvoke(ClassNode shimClassNode)
    1190    {
    1191        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
    1192                null);
    1193
    1194        InstructionBuilder builder = newBuilder(mn);
    1195
    1196        // Arg 0 is the target instance
    1197        // Arg 1 is the index
    1198        // Arg 2 is the object array of parameters
    1199
    1200        builder.loadArgument(0).checkcast(className);
    1201
    1202        builder.loadArgument(1);
    1203
    1204        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
    1205        {
    1206            @Override
    1207            public void doSwitch(SwitchBlock block)
    1208            {
    1209                for (PlasticMethodImpl m : shimMethods)
    1210                {
    1211                    m.extendShimInvoke(block);
    1212                }
    1213            }
    1214        });
    1215
    1216        shimClassNode.methods.add(mn);
    1217    }
    1218
    1219    private void rewriteAdvisedMethods()
    1220    {
    1221        for (PlasticMethodImpl method : advisedMethods)
    1222        {
    1223            method.rewriteMethodForAdvice();
    1224        }
    1225    }
    1226
    1227    private void interceptFieldAccess(MethodNode methodNode)
    1228    {
    1229        InsnList insns = methodNode.instructions;
    1230
    1231        ListIterator<AbstractInsnNode> it = insns.iterator();
    1232
    1233        while (it.hasNext())
    1234        {
    1235            AbstractInsnNode node = it.next();
    1236
    1237            int opcode = node.getOpcode();
    1238
    1239            if (opcode != GETFIELD && opcode != PUTFIELD)
    1240            {
    1241                continue;
    1242            }
    1243
    1244            FieldInsnNode fnode = (FieldInsnNode) node;
    1245
    1246            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
    1247
    1248            if (instrumentation == null)
    1249            {
    1250                continue;
    1251            }
    1252
    1253            // Replace the field access node with the appropriate method invocation.
    1254
    1255            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription, false));
    1256
    1257            it.remove();
    1258        }
    1259    }
    1260
    1261    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
    1262    {
    1263        // First look in the local fieldInstrumentations, which contains private field instrumentations
    1264        // (as well as non-private ones).
    1265
    1266        String searchStart = node.owner;
    1267
    1268        if (searchStart.equals(classNode.name))
    1269        {
    1270            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
    1271
    1272            if (result != null)
    1273            {
    1274                return result;
    1275            }
    1276
    1277            // Slight optimization: start the search in the super-classes' fields, since we've already
    1278            // checked this classes fields.
    1279
    1280            searchStart = classNode.superName;
    1281        }
    1282
    1283        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
    1284    }
    1285
    1286    String getInstanceContextFieldName()
    1287    {
    1288        if (instanceContextFieldName == null)
    1289        {
    1290            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
    1291
    1292            // TODO: We could use a protected field and only initialize
    1293            // it once, in the first base class where it is needed, though that raises the possibilities
    1294            // of name conflicts (a subclass might introduce a field with a conflicting name).
    1295
    1296            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
    1297                    null, null);
    1298
    1299            classNode.fields.add(node);
    1300
    1301            // Extend the constructor to store the context in a field.
    1302
    1303            constructorBuilder.loadThis().loadArgument(1)
    1304                    .putField(className, instanceContextFieldName, InstanceContext.class);
    1305        }
    1306
    1307        return instanceContextFieldName;
    1308    }
    1309
    1310    /**
    1311     * Creates a new private final field and initializes its value (using the StaticContext).
    1312     */
    1313    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
    1314                                                     Object injectedFieldValue)
    1315    {
    1316        String name = makeUnique(fieldNames, suggestedFieldName);
    1317
    1318        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
    1319
    1320        classNode.fields.add(field);
    1321
    1322        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
    1323
    1324        return name;
    1325    }
    1326
    1327    /**
    1328     * Initializes a field from the static context. The injected value is added to the static
    1329     * context and the class constructor updated to assign the value from the context (which includes casting and
    1330     * possibly unboxing).
    1331     */
    1332    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
    1333    {
    1334        int index = staticContext.store(injectedFieldValue);
    1335
    1336        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
    1337        // on primitive longs and doubles, so its just easier to do the loadThis() first
    1338        // so its at the right place on the stack for the putField().
    1339
    1340        constructorBuilder.loadThis();
    1341
    1342        constructorBuilder.loadArgument(0).loadConstant(index);
    1343        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
    1344        constructorBuilder.castOrUnbox(fieldType);
    1345
    1346        constructorBuilder.putField(className, fieldName, fieldType);
    1347    }
    1348
    1349    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
    1350    {
    1351        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
    1352    }
    1353
    1354    @Override
    1355    public PlasticClass getPlasticClass()
    1356    {
    1357        return this;
    1358    }
    1359
    1360    @Override
    1361    public Class<?> getTransformedClass()
    1362    {
    1363        if (transformedClass == null)
    1364            throw new IllegalStateException(String.format(
    1365                    "Transformed class %s is not yet available because the transformation is not yet complete.",
    1366                    className));
    1367
    1368        return transformedClass;
    1369    }
    1370
    1371    private boolean isInheritableMethod(MethodNode node)
    1372    {
    1373        return !Modifier.isPrivate(node.access);
    1374    }
    1375
    1376    @Override
    1377    public String getClassName()
    1378    {
    1379        return className;
    1380    }
    1381
    1382    InstructionBuilderImpl newBuilder(MethodNode mn)
    1383    {
    1384        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
    1385    }
    1386
    1387    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
    1388    {
    1389        return new InstructionBuilderImpl(description, mn, nameCache);
    1390    }
    1391
    1392    public Set<PlasticMethod> introduceInterface(Class interfaceType)
    1393    {
    1394        return introduceInterface(interfaceType, null);
    1395    }
    1396
    1397    private Set<PlasticMethod> introduceInterface(Class interfaceType, PlasticMethod method)
    1398    {
    1399        check();
    1400
    1401        assert interfaceType != null;
    1402
    1403        if (!interfaceType.isInterface())
    1404            throw new IllegalArgumentException(String.format(
    1405                    "Class %s is not an interface; only interfaces may be introduced.", interfaceType.getName()));
    1406
    1407        String interfaceName = nameCache.toInternalName(interfaceType);
    1408
    1409        try
    1410        {
    1411            interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader());
    1412        } catch (IOException e)
    1413        {
    1414            throw new RuntimeException(e);
    1415        }
    1416
    1417        if (!inheritanceData.isInterfaceImplemented(interfaceName))
    1418        {
    1419            classNode.interfaces.add(interfaceName);
    1420            inheritanceData.addInterface(interfaceName);
    1421        }
    1422
    1423        addClassAnnotations(interfaceClassNode);
    1424
    1425        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
    1426        Set<Method> alreadyIntroducedMethods = new HashSet<>();
    1427
    1428        Method[] sortedMethods = interfaceType.getMethods();
    1429        Arrays.sort(sortedMethods, METHOD_COMPARATOR);
    1430        for (Method m : sortedMethods)
    1431        {
    1432            MethodDescription description = new MethodDescription(m);
    1433
    1434            if (!isMethodImplemented(description) &&
    1435                    !(m.isDefault() && m.isBridge()) &&
    1436                    !Modifier.isStatic(description.modifiers) &&
    1437                    !contains(alreadyIntroducedMethods, m))
    1438            {
    1439                PlasticMethod introducedMethod = introduceMethod(m);
    1440                introducedMethods.add(introducedMethod);
    1441                if (method != null) {
    1442                    introducedMethod.delegateTo(method);
    1443                }
    1444                alreadyIntroducedMethods.add(m);
    1445            }
    1446        }
    1447
    1448        interfaceClassNode = null;
    1449
    1450        return introducedMethods;
    1451    }
    1452
    1453    @Override
    1454    public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method)
    1455    {
    1456        check();
    1457        assert method != null;
    1458
    1459        introduceInterface(interfaceType, method);
    1460
    1461        return this;
    1462    }
    1463
    1464    private boolean contains(Set<Method> alreadyIntroducedMethods, Method m) {
    1465        boolean contains = false;
    1466        for (Method method : alreadyIntroducedMethods)
    1467        {
    1468            if (METHOD_COMPARATOR.compare(method, m) == 0)
    1469            {
    1470                contains = true;
    1471                break;
    1472            }
    1473        }
    1474        return false;
    1475    }
    1476
    1477    private Map<MethodSignature, MethodDescription> createMethodSignatureMap(Class interfaceType) {
    1478        // TAP-2582: preprocessing the method list so we don't add duplicated
    1479        // methods, something that happens when an interface has superinterfaces
    1480        // and they define the same method signature.
    1481        // In addition, we collect all the thrown checked exceptions, just in case.
    1482        Map<MethodSignature, MethodDescription> map = new HashMap<>();
    1483        for (Method m : interfaceType.getMethods())
    1484        {
    1485            final MethodSignature methodSignature = new MethodSignature(m);
    1486            final MethodDescription newMethodDescription = new MethodDescription(m);
    1487            if (!map.containsKey(methodSignature))
    1488            {
    1489                map.put(methodSignature, newMethodDescription);
    1490            }
    1491            else
    1492            {
    1493                if (newMethodDescription.checkedExceptionTypes != null && newMethodDescription.checkedExceptionTypes.length > 0)
    1494                {
    1495                    final MethodDescription methodDescription = map.get(methodSignature);
    1496                        final Set<String> checkedExceptionTypes = new HashSet<>();
    1497                        checkedExceptionTypes.addAll(Arrays.asList(methodDescription.checkedExceptionTypes));
    1498                        checkedExceptionTypes.addAll(Arrays.asList(newMethodDescription.checkedExceptionTypes));
    1499                        map.put(methodSignature, new MethodDescription(
    1500                            methodDescription,
    1501                            checkedExceptionTypes.toArray(new String[checkedExceptionTypes.size()])));
    1502                }
    1503            }
    1504        }
    1505        return map;
    1506    }
    1507
    1508    final private static class MethodSignature implements Comparable<MethodSignature> {
    1509        final private Method method;
    1510        final private String name;
    1511        final private Class<?>[] parameterTypes;
    1512
    1513        public MethodSignature(Method method) {
    1514            this.method = method;
    1515            this.name = method.getName();
    1516            this.parameterTypes = method.getParameterTypes();
    1517        }
    1518
    1519        @Override
    1520        public int hashCode() {
    1521            final int prime = 31;
    1522            int result = 1;
    1523            result = prime * result + Arrays.hashCode(parameterTypes);
    1524            result = prime * result + ((name == null) ? 0 : name.hashCode());
    1525            return result;
    1526        }
    1527
    1528        @Override
    1529        public boolean equals(Object obj) {
    1530            if (this == obj) return true;
    1531            if (obj == null) return false;
    1532            if (getClass() != obj.getClass()) return false;
    1533
    1534            MethodSignature other = (MethodSignature) obj;
    1535            if (!Arrays.equals(parameterTypes, other.parameterTypes)) return false;
    1536
    1537            return name == null ? other.name == null : name.equals(other.name);
    1538        }
    1539
    1540        @Override
    1541        public int compareTo(MethodSignature o) {
    1542            return method.getName().compareTo(o.method.getName());
    1543        }
    1544    }
    1545
    1546    @Override
    1547    public PlasticClass addToString(final String toStringValue)
    1548    {
    1549        check();
    1550
    1551        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
    1552        {
    1553            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
    1554            {
    1555                @Override
    1556                public void doBuild(InstructionBuilder builder)
    1557                {
    1558                    builder.loadConstant(toStringValue).returnResult();
    1559                }
    1560            });
    1561        }
    1562
    1563        return this;
    1564    }
    1565
    1566    @Override
    1567    public boolean isMethodImplemented(MethodDescription description)
    1568    {
    1569        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
    1570    }
    1571
    1572    @Override
    1573    public boolean isInterfaceImplemented(Class interfaceType)
    1574    {
    1575        assert interfaceType != null;
    1576        assert interfaceType.isInterface();
    1577
    1578        String interfaceName = nameCache.toInternalName(interfaceType);
    1579
    1580        return inheritanceData.isInterfaceImplemented(interfaceName);
    1581    }
    1582
    1583    @Override
    1584    public String getSuperClassName()
    1585    {
    1586        return superClassName;
    1587    }
    1588
    1589    @Override
    1590    public PlasticClass onConstruct(ConstructorCallback callback)
    1591    {
    1592        check();
    1593
    1594        assert callback != null;
    1595
    1596        constructorCallbacks.add(callback);
    1597
    1598        return this;
    1599    }
    1600
    1601    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
    1602    {
    1603        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
    1604
    1605        fieldInstrumentations.write.put(fieldName, fi);
    1606
    1607        if (!proxy)
    1608        {
    1609            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
    1610        }
    1611    }
    1612
    1613    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
    1614    {
    1615        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
    1616
    1617        fieldInstrumentations.read.put(fieldName, fi);
    1618
    1619        if (!proxy)
    1620        {
    1621            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
    1622        }
    1623    }
    1624
    1625    final private MethodComparator METHOD_COMPARATOR = new MethodComparator();
    1626
    1627    final private class MethodComparator implements Comparator<Method>
    1628    {
    1629
    1630        @Override
    1631        public int compare(Method o1, Method o2)
    1632        {
    1633
    1634            int comparison = comparison = o1.getName().compareTo(o2.getName());
    1635
    1636            if (comparison == 0)
    1637            {
    1638                comparison = o1.getParameterTypes().length - o2.getParameterTypes().length;
    1639            }
    1640
    1641            if (comparison == 0)
    1642            {
    1643                final int count = o1.getParameterTypes().length;
    1644                for (int i = 0; i < count; i++)
    1645                {
    1646                    Class p1 = o1.getParameterTypes()[i];
    1647                    Class p2 = o1.getParameterTypes()[i];
    1648                    if (!p1.equals(p2))
    1649                    {
    1650                        comparison = p1.getName().compareTo(p2.getName());
    1651                        break;
    1652                    }
    1653                }
    1654            }
    1655
    1656            if (comparison == 0)
    1657            {
    1658                // Trying to get methods lower in the interface hierarchy sorted before the same methods
    1659                // higher in the hierarchy
    1660                Class<?> declaringClass1 = o1.getDeclaringClass();
    1661                Class<?> declaringClass2 = o2.getDeclaringClass();
    1662                if (declaringClass1.isInterface() && declaringClass2.isInterface() &&
    1663                        !declaringClass1.equals(declaringClass2)) {
    1664                    if (declaringClass1.isAssignableFrom(declaringClass2))
    1665                    {
    1666                        comparison = -1;
    1667                    }
    1668                    else if (declaringClass1.isAssignableFrom(declaringClass2))
    1669                    {
    1670                        comparison = 1;
    1671                    }
    1672                }
    1673            }
    1674
    1675            return comparison;
    1676        }
    1677    }
    1678
    1679    private List<Method> getUniqueMethods(Class interfaceType)
    1680    {
    1681        final List<Method> unique = new ArrayList<>(Arrays.asList(interfaceType.getMethods()));
    1682        Collections.sort(unique, METHOD_COMPARATOR);
    1683        Method last = null;
    1684        Iterator<Method> iterator = unique.iterator();
    1685        while (iterator.hasNext())
    1686        {
    1687            Method m = iterator.next();
    1688            if (last != null && METHOD_COMPARATOR.compare(m, last) == 0)
    1689            {
    1690                last = m;
    1691                iterator.remove();
    1692            }
    1693        }
    1694        return unique;
    1695    }
    1696
    1697    @Override
    1698    public String toString()
    1699    {
    1700        return String.format("PlasticClassImpl[%s]", className);
    1701    }
    1702}