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