    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        // TAP5-2582: avoiding adding/delegating the same method more than once
    625//        for (Method m : interfaceType.getMethods())
    626//        {
    627//            introduceMethod(m).delegateTo(field);
    628//        }
    629
    630        Map<MethodSignature, MethodDescription> map = createMethodSignatureMap(interfaceType);
    631        for (MethodSignature methodSignature : map.keySet())
    632        {
    633            introduceMethod(map.get(methodSignature)).delegateTo(field);
    634        }
    635
    636        return this;
    637    }
    638
    639    @Override
    640    public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method)
    641    {
    642        check();
    643
    644        assert method != null;
    645
    646        introduceInterface(interfaceType);
    647
    648        // TAP5-2582: avoiding adding/delegating the same method more than once
    649        Map<MethodSignature, MethodDescription> map = createMethodSignatureMap(interfaceType);
    650        for (MethodSignature methodSignature : map.keySet())
    651        {
    652            introduceMethod(map.get(methodSignature)).delegateTo(method);
    653        }
    654
    655        return this;
    656    }
    657
    658    @Override
    659    public ClassInstantiator createInstantiator()
    660    {
    661        lock();
    662
    663        addClassAnnotations(implementationClassNode);
    664        removeDuplicatedAnnotations(classNode);
    665
    666        createShimIfNeeded();
    667
    668        interceptFieldAccess();
    669
    670        rewriteAdvisedMethods();
    671
    672        completeConstructor();
    673
    674        transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
    675
    676        return createInstantiatorFromClass(transformedClass);
    677    }
    678
    679    private void addClassAnnotations(ClassNode otherClassNode)
    680    {
    681        // Copy annotations from implementation if available.
    682        // Code adapted from ClassNode.accept(), as we just want to copy
    683        // the annotations and nothing more.
    684        if (otherClassNode != null)
    685        {
    686
    687            int i, n;
    688            n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size();
    689            for (i = 0; i < n; ++i)
    690            {
    691                AnnotationNode an = otherClassNode.visibleAnnotations.get(i);
    692                an.accept(classNode.visitAnnotation(an.desc, true));
    693            }
    694            n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size();
    695            for (i = 0; i < n; ++i)
    696            {
    697                AnnotationNode an = otherClassNode.invisibleAnnotations.get(i);
    698                an.accept(classNode.visitAnnotation(an.desc, false));
    699            }
    700
    701        }
    702    }
    703
    704    private ClassInstantiator createInstantiatorFromClass(Class clazz)
    705    {
    706        try
    707        {
    708            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
    709
    710            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
    711        } catch (Exception ex)
    712        {
    713            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
    714                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
    715        }
    716    }
    717
    718    private void completeConstructor()
    719    {
    720        if (originalConstructor != null)
    721        {
    722            convertOriginalConstructorToMethod();
    723        }
    724
    725        invokeCallbacks();
    726
    727        constructorBuilder.returnResult();
    728
    729        classNode.methods.add(newConstructor);
    730    }
    731
    732    private void invokeCallbacks()
    733    {
    734        for (ConstructorCallback callback : constructorCallbacks)
    735        {
    736            invokeCallback(callback);
    737        }
    738    }
    739
    740    private void invokeCallback(ConstructorCallback callback)
    741    {
    742        int index = staticContext.store(callback);
    743
    744        // First, load the callback
    745
    746        constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
    747
    748        // Load this and the InstanceContext
    749        constructorBuilder.loadThis().loadArgument(1);
    750
    751        constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
    752    }
    753
    754
    755    /**
    756     * Convert the original constructor into a private method invoked from the
    757     * generated constructor.
    758     */
    759    private void convertOriginalConstructorToMethod()
    760    {
    761        String initializerName = makeUnique(methodNames, "initializeInstance");
    762
    763        int originalAccess = originalConstructor.access;
    764
    765        originalConstructor.access = ACC_PRIVATE;
    766        originalConstructor.name = initializerName;
    767
    768        stripOutSuperConstructorCall(originalConstructor);
    769
    770        constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
    771
    772        // And replace it with a constructor that throws an exception
    773
    774        MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
    775                null);
    776
    777        newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
    778
    779        classNode.methods.add(replacementConstructor);
    780    }
    781
    782    private void stripOutSuperConstructorCall(MethodNode cons)
    783    {
    784        InsnList ins = cons.instructions;
    785
    786        ListIterator li = ins.iterator();
    787
    788        // Look for the ALOAD 0 (i.e., push this on the stack)
    789        while (li.hasNext())
    790        {
    791            AbstractInsnNode node = (AbstractInsnNode) li.next();
    792
    793            if (node.getOpcode() == ALOAD)
    794            {
    795                VarInsnNode varNode = (VarInsnNode) node;
    796
    797                assert varNode.var == 0;
    798
    799                // Remove the ALOAD
    800                li.remove();
    801                break;
    802            }
    803        }
    804
    805        // Look for the call to the super-class, an INVOKESPECIAL
    806        while (li.hasNext())
    807        {
    808            AbstractInsnNode node = (AbstractInsnNode) li.next();
    809
    810            if (node.getOpcode() == INVOKESPECIAL)
    811            {
    812                MethodInsnNode mnode = (MethodInsnNode) node;
    813
    814                assert mnode.owner.equals(classNode.superName);
    815                assert mnode.name.equals(CONSTRUCTOR_NAME);
    816                assert mnode.desc.equals(cons.desc);
    817
    818                li.remove();
    819                return;
    820            }
    821        }
    822
    823        throw new AssertionError("Could not convert constructor to simple method.");
    824    }
    825
    826    @Override
    827    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
    828    {
    829        check();
    830
    831        List<PlasticField> result = getAllFields();
    832
    833        Iterator<PlasticField> iterator = result.iterator();
    834
    835        while (iterator.hasNext())
    836        {
    837            PlasticField plasticField = iterator.next();
    838
    839            if (!plasticField.hasAnnotation(annotationType))
    840                iterator.remove();
    841        }
    842
    843        return result;
    844    }
    845
    846    @Override
    847    public List<PlasticField> getAllFields()
    848    {
    849        check();
    850
    851        return new ArrayList<PlasticField>(fields);
    852    }
    853
    854    @Override
    855    public List<PlasticField> getUnclaimedFields()
    856    {
    857        check();
    858
    859        // Initially null, and set back to null by PlasticField.claim().
    860
    861        if (unclaimedFields == null)
    862        {
    863            unclaimedFields = new ArrayList<PlasticField>(fields.size());
    864
    865            for (PlasticField f : fields)
    866            {
    867                if (!f.isClaimed())
    868                    unclaimedFields.add(f);
    869            }
    870        }
    871
    872        return unclaimedFields;
    873    }
    874
    875    @Override
    876    public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
    877                                                String[] exceptionTypes)
    878    {
    879        check();
    880
    881        assert PlasticInternalUtils.isNonBlank(typeName);
    882        assert PlasticInternalUtils.isNonBlank(suggestedName);
    883
    884        String name = makeUnique(methodNames, suggestedName);
    885
    886        MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
    887                exceptionTypes);
    888
    889        return introduceMethod(description);
    890    }
    891
    892    @Override
    893    public PlasticField introduceField(String className, String suggestedName)
    894    {
    895        check();
    896
    897        assert PlasticInternalUtils.isNonBlank(className);
    898        assert PlasticInternalUtils.isNonBlank(suggestedName);
    899
    900        String name = makeUnique(fieldNames, suggestedName);
    901
    902        // No signature and no initial value
    903
    904        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
    905
    906        classNode.fields.add(fieldNode);
    907
    908        fieldNames.add(name);
    909
    910        PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
    911
    912        return newField;
    913    }
    914
    915    @Override
    916    public PlasticField introduceField(Class fieldType, String suggestedName)
    917    {
    918        assert fieldType != null;
    919
    920        return introduceField(nameCache.toTypeName(fieldType), suggestedName);
    921    }
    922
    923    String makeUnique(Set<String> values, String input)
    924    {
    925        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
    926    }
    927
    928    @Override
    929    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
    930    {
    931        check();
    932
    933        List<PlasticMethod> result = getMethods();
    934        Iterator<PlasticMethod> iterator = result.iterator();
    935
    936        while (iterator.hasNext())
    937        {
    938            PlasticMethod method = iterator.next();
    939
    940            if (!method.hasAnnotation(annotationType))
    941                iterator.remove();
    942        }
    943
    944        return result;
    945    }
    946
    947    @Override
    948    public List<PlasticMethod> getMethods()
    949    {
    950        check();
    951
    952        return new ArrayList<PlasticMethod>(methods);
    953    }
    954
    955    @Override
    956    public PlasticMethod introduceMethod(MethodDescription description)
    957    {
    958        check();
    959
    960        if (Modifier.isAbstract(description.modifiers))
    961        {
    962            description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
    963        }
    964
    965        PlasticMethod result = description2method.get(description);
    966
    967        if (result == null)
    968        {
    969            result = createNewMethod(description);
    970
    971            description2method.put(description, result);
    972        }
    973
    974        methodNames.add(description.methodName);
    975
    976        // Note that is it not necessary to add the new MethodNode to
    977        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
    978        // ever access instance fields) ... unless the caller invokes changeImplementation().
    979
    980        return result;
    981    }
    982
    983    @Override
    984    public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
    985    {
    986        check();
    987
    988        // TODO: optimize this so that a default implementation is not created.
    989
    990        return introduceMethod(description).changeImplementation(callback);
    991    }
    992
    993    @Override
    994    public PlasticMethod introduceMethod(Method method)
    995    {
    996        check();
    997
    998        return introduceMethod(new MethodDescription(method));
    999    }
    1000
    1001    void addMethod(MethodNode methodNode)
    1002    {
    1003        classNode.methods.add(methodNode);
    1004
    1005        methodNames.add(methodNode.name);
    1006
    1007        if (isInheritableMethod(methodNode))
    1008        {
    1009            inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0);
    1010        }
    1011    }
    1012
    1013    private PlasticMethod createNewMethod(MethodDescription description)
    1014    {
    1015        if (Modifier.isStatic(description.modifiers))
    1016            throw new IllegalArgumentException(String.format(
    1017                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
    1018                    description, className));
    1019
    1020        String desc = nameCache.toDesc(description);
    1021
    1022        String[] exceptions = new String[description.checkedExceptionTypes.length];
    1023        for (int i = 0; i < exceptions.length; i++)
    1024        {
    1025            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
    1026        }
    1027
    1028        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
    1029                description.genericSignature, exceptions);
    1030        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
    1031
    1032        if (!isOverride)
    1033        {
    1034            addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode);
    1035            addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode);
    1036            removeDuplicatedAnnotations(methodNode);
    1037        }
    1038
    1039        if (isOverride)
    1040            createOverrideOfBaseClassImpl(description, methodNode);
    1041        else
    1042            createNewMethodImpl(description, methodNode);
    1043
    1044        addMethod(methodNode);
    1045
    1046        return new PlasticMethodImpl(this, methodNode);
    1047    }
    1048
    1049    private boolean isDefaultMethod(Method method)
    1050    {
    1051        return method.getDeclaringClass().isInterface() &&
    1052                (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC;
    1053    }
    1054
    1055    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
    1056    {
    1057        newBuilder(methodDescription, methodNode).returnDefaultValue();
    1058    }
    1059
    1060    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
    1061    {
    1062        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
    1063
    1064        builder.loadThis();
    1065        builder.loadArguments();
    1066        builder.invokeSpecial(superClassName, methodDescription);
    1067        builder.returnResult();
    1068    }
    1069
    1070    /**
    1071     * Iterates over all non-introduced methods, including the original constructor. For each
    1072     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
    1073     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
    1074     * for the class has been created, as the shim may create methods that contain references to fields that may be
    1075     * subject to field access interception.
    1076     */
    1077    private void interceptFieldAccess()
    1078    {
    1079        for (MethodNode node : fieldTransformMethods)
    1080        {
    1081            // Intercept field access inside the method, tracking which access methods
    1082            // are actually used by removing them from accessMethods
    1083
    1084            interceptFieldAccess(node);
    1085        }
    1086    }
    1087
    1088    /**
    1089     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
    1090     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
    1091     */
    1092    private void createShimIfNeeded()
    1093    {
    1094        if (shimFields.isEmpty() && shimMethods.isEmpty())
    1095            return;
    1096
    1097        PlasticClassHandleShim shim = createShimInstance();
    1098
    1099        installShim(shim);
    1100    }
    1101
    1102    public void installShim(PlasticClassHandleShim shim)
    1103    {
    1104        for (PlasticFieldImpl f : shimFields)
    1105        {
    1106            f.installShim(shim);
    1107        }
    1108
    1109        for (PlasticMethodImpl m : shimMethods)
    1110        {
    1111            m.installShim(shim);
    1112        }
    1113    }
    1114
    1115    public PlasticClassHandleShim createShimInstance()
    1116    {
    1117        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
    1118
    1119        ClassNode shimClassNode = new ClassNode();
    1120
    1121        shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
    1122                null);
    1123
    1124        implementConstructor(shimClassNode);
    1125
    1126        if (!shimFields.isEmpty())
    1127        {
    1128            implementShimGet(shimClassNode);
    1129            implementShimSet(shimClassNode);
    1130        }
    1131
    1132        if (!shimMethods.isEmpty())
    1133        {
    1134            implementShimInvoke(shimClassNode);
    1135        }
    1136
    1137        return instantiateShim(shimClassNode);
    1138    }
    1139
    1140    private void implementConstructor(ClassNode shimClassNode)
    1141    {
    1142        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
    1143
    1144        InstructionBuilder builder = newBuilder(mn);
    1145
    1146        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
    1147
    1148        shimClassNode.methods.add(mn);
    1149
    1150    }
    1151
    1152    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
    1153    {
    1154        try
    1155        {
    1156            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
    1157
    1158            return (PlasticClassHandleShim) shimClass.newInstance();
    1159        } catch (Exception ex)
    1160        {
    1161            throw new RuntimeException(
    1162                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
    1163                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
    1164                            PlasticInternalUtils.toMessage(ex)), ex);
    1165        }
    1166    }
    1167
    1168    private void implementShimGet(ClassNode shimClassNode)
    1169    {
    1170        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
    1171
    1172        InstructionBuilder builder = newBuilder(mn);
    1173
    1174        // Arg 0 is the target instance
    1175        // Arg 1 is the index
    1176
    1177        builder.loadArgument(0).checkcast(className);
    1178        builder.loadArgument(1);
    1179
    1180        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
    1181        {
    1182            @Override
    1183            public void doSwitch(SwitchBlock block)
    1184            {
    1185                for (PlasticFieldImpl f : shimFields)
    1186                {
    1187                    f.extendShimGet(block);
    1188                }
    1189            }
    1190        });
    1191
    1192        shimClassNode.methods.add(mn);
    1193    }
    1194
    1195    private void implementShimSet(ClassNode shimClassNode)
    1196    {
    1197        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
    1198
    1199        InstructionBuilder builder = newBuilder(mn);
    1200
    1201        // Arg 0 is the target instance
    1202        // Arg 1 is the index
    1203        // Arg 2 is the new value
    1204
    1205        builder.loadArgument(0).checkcast(className);
    1206        builder.loadArgument(2);
    1207
    1208        builder.loadArgument(1);
    1209
    1210        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
    1211        {
    1212            @Override
    1213            public void doSwitch(SwitchBlock block)
    1214            {
    1215                for (PlasticFieldImpl f : shimFields)
    1216                {
    1217                    f.extendShimSet(block);
    1218                }
    1219            }
    1220        });
    1221
    1222        builder.returnResult();
    1223
    1224        shimClassNode.methods.add(mn);
    1225    }
    1226
    1227    private void implementShimInvoke(ClassNode shimClassNode)
    1228    {
    1229        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
    1230                null);
    1231
    1232        InstructionBuilder builder = newBuilder(mn);
    1233
    1234        // Arg 0 is the target instance
    1235        // Arg 1 is the index
    1236        // Arg 2 is the object array of parameters
    1237
    1238        builder.loadArgument(0).checkcast(className);
    1239
    1240        builder.loadArgument(1);
    1241
    1242        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
    1243        {
    1244            @Override
    1245            public void doSwitch(SwitchBlock block)
    1246            {
    1247                for (PlasticMethodImpl m : shimMethods)
    1248                {
    1249                    m.extendShimInvoke(block);
    1250                }
    1251            }
    1252        });
    1253
    1254        shimClassNode.methods.add(mn);
    1255    }
    1256
    1257    private void rewriteAdvisedMethods()
    1258    {
    1259        for (PlasticMethodImpl method : advisedMethods)
    1260        {
    1261            method.rewriteMethodForAdvice();
    1262        }
    1263    }
    1264
    1265    private void interceptFieldAccess(MethodNode methodNode)
    1266    {
    1267        InsnList insns = methodNode.instructions;
    1268
    1269        ListIterator it = insns.iterator();
    1270
    1271        while (it.hasNext())
    1272        {
    1273            AbstractInsnNode node = (AbstractInsnNode) it.next();
    1274
    1275            int opcode = node.getOpcode();
    1276
    1277            if (opcode != GETFIELD && opcode != PUTFIELD)
    1278            {
    1279                continue;
    1280            }
    1281
    1282            FieldInsnNode fnode = (FieldInsnNode) node;
    1283
    1284            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
    1285
    1286            if (instrumentation == null)
    1287            {
    1288                continue;
    1289            }
    1290
    1291            // Replace the field access node with the appropriate method invocation.
    1292
    1293            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription));
    1294
    1295            it.remove();
    1296        }
    1297    }
    1298
    1299    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
    1300    {
    1301        // First look in the local fieldInstrumentations, which contains private field instrumentations
    1302        // (as well as non-private ones).
    1303
    1304        String searchStart = node.owner;
    1305
    1306        if (searchStart.equals(classNode.name))
    1307        {
    1308            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
    1309
    1310            if (result != null)
    1311            {
    1312                return result;
    1313            }
    1314
    1315            // Slight optimization: start the search in the super-classes' fields, since we've already
    1316            // checked this classes fields.
    1317
    1318            searchStart = classNode.superName;
    1319        }
    1320
    1321        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
    1322    }
    1323
    1324    String getInstanceContextFieldName()
    1325    {
    1326        if (instanceContextFieldName == null)
    1327        {
    1328            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
    1329
    1330            // TODO: We could use a protected field and only initialize
    1331            // it once, in the first base class where it is needed, though that raises the possibilities
    1332            // of name conflicts (a subclass might introduce a field with a conflicting name).
    1333
    1334            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
    1335                    null, null);
    1336
    1337            classNode.fields.add(node);
    1338
    1339            // Extend the constructor to store the context in a field.
    1340
    1341            constructorBuilder.loadThis().loadArgument(1)
    1342                    .putField(className, instanceContextFieldName, InstanceContext.class);
    1343        }
    1344
    1345        return instanceContextFieldName;
    1346    }
    1347
    1348    /**
    1349     * Creates a new private final field and initializes its value (using the StaticContext).
    1350     */
    1351    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
    1352                                                     Object injectedFieldValue)
    1353    {
    1354        String name = makeUnique(fieldNames, suggestedFieldName);
    1355
    1356        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
    1357
    1358        classNode.fields.add(field);
    1359
    1360        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
    1361
    1362        return name;
    1363    }
    1364
    1365    /**
    1366     * Initializes a field from the static context. The injected value is added to the static
    1367     * context and the class constructor updated to assign the value from the context (which includes casting and
    1368     * possibly unboxing).
    1369     */
    1370    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
    1371    {
    1372        int index = staticContext.store(injectedFieldValue);
    1373
    1374        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
    1375        // on primitive longs and doubles, so its just easier to do the loadThis() first
    1376        // so its at the right place on the stack for the putField().
    1377
    1378        constructorBuilder.loadThis();
    1379
    1380        constructorBuilder.loadArgument(0).loadConstant(index);
    1381        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
    1382        constructorBuilder.castOrUnbox(fieldType);
    1383
    1384        constructorBuilder.putField(className, fieldName, fieldType);
    1385    }
    1386
    1387    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
    1388    {
    1389        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
    1390    }
    1391
    1392    @Override
    1393    public PlasticClass getPlasticClass()
    1394    {
    1395        return this;
    1396    }
    1397
    1398    @Override
    1399    public Class<?> getTransformedClass()
    1400    {
    1401        if (transformedClass == null)
    1402            throw new IllegalStateException(String.format(
    1403                    "Transformed class %s is not yet available because the transformation is not yet complete.",
    1404                    className));
    1405
    1406        return transformedClass;
    1407    }
    1408
    1409    private boolean isInheritableMethod(MethodNode node)
    1410    {
    1411        return !Modifier.isPrivate(node.access);
    1412    }
    1413
    1414    @Override
    1415    public String getClassName()
    1416    {
    1417        return className;
    1418    }
    1419
    1420    InstructionBuilderImpl newBuilder(MethodNode mn)
    1421    {
    1422        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
    1423    }
    1424
    1425    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
    1426    {
    1427        return new InstructionBuilderImpl(description, mn, nameCache);
    1428    }
    1429
    1430    @Override
    1431    public Set<PlasticMethod> introduceInterface(Class interfaceType)
    1432    {
    1433        check();
    1434
    1435        assert interfaceType != null;
    1436
    1437        if (!interfaceType.isInterface())
    1438            throw new IllegalArgumentException(String.format(
    1439                    "Class %s is not an interface; only interfaces may be introduced.", interfaceType.getName()));
    1440
    1441        String interfaceName = nameCache.toInternalName(interfaceType);
    1442
    1443        try
    1444        {
    1445            interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader());
    1446        } catch (IOException e)
    1447        {
    1448            throw new RuntimeException(e);
    1449        }
    1450
    1451        if (!inheritanceData.isInterfaceImplemented(interfaceName))
    1452        {
    1453            classNode.interfaces.add(interfaceName);
    1454            inheritanceData.addInterface(interfaceName);
    1455        }
    1456
    1457        addClassAnnotations(interfaceClassNode);
    1458
    1459        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
    1460
    1461        Map<MethodSignature, MethodDescription> map = createMethodSignatureMap(interfaceType);
    1462
    1463        // for (Method m : interfaceType.getMethods())
    1464        for (MethodSignature methodSignature : map.keySet())
    1465        {
    1466            // MethodDescription description = new MethodDescription(m);
    1467            final MethodDescription description = map.get(methodSignature);
    1468
    1469            if (!isMethodImplemented(description) && !isDefaultMethod(methodSignature.method))
    1470            {
    1471                // introducedMethods.add(introduceMethod(m));
    1472                introducedMethods.add(introduceMethod(description));
    1473            }
    1474        }
    1475
    1476        interfaceClassNode = null;
    1477
    1478        return introducedMethods;
    1479    }
    1480
    1481    private Map<MethodSignature, MethodDescription> createMethodSignatureMap(Class interfaceType) {
    1482        // TAP-2582: preprocessing the method list so we don't add duplicated
    1483        // methods, something that happens when an interface has superinterfaces
    1484        // and they define the same method signature.
    1485        // In addition, we collect all the thrown checked exceptions, just in case.
    1486        Map<MethodSignature, MethodDescription> map = new HashMap<MethodSignature, MethodDescription>();
    1487        for (Method m : interfaceType.getMethods())
    1488        {
    1489            final MethodSignature methodSignature = new MethodSignature(m);
    1490            final MethodDescription newMethodDescription = new MethodDescription(m);
    1491            if (!map.containsKey(methodSignature))
    1492            {
    1493                map.put(methodSignature, newMethodDescription);
    1494            }
    1495            else
    1496            {
    1497                if (newMethodDescription.checkedExceptionTypes != null && newMethodDescription.checkedExceptionTypes.length > 0)
    1498                {
    1499                    final MethodDescription methodDescription = map.get(methodSignature);
    1500                    final Set<String> checkedExceptionTypes = new HashSet<String>();
    1501                    checkedExceptionTypes.addAll(Arrays.asList(methodDescription.checkedExceptionTypes));
    1502                    checkedExceptionTypes.addAll(Arrays.asList(newMethodDescription.checkedExceptionTypes));
    1503                    map.put(methodSignature, new MethodDescription(
    1504                            methodDescription,
    1505                            checkedExceptionTypes.toArray(new String[checkedExceptionTypes.size()])));
    1506                }
    1507            }
    1508        }
    1509        return map;
    1510    }
    1511
    1512    final private static class MethodSignature implements Comparable<MethodSignature>{
    1513
    1514        final private Method method;
    1515        final private String name;
    1516        final private Class<?>[] parameterTypes;
    1517
    1518        public MethodSignature(Method method) {
    1519            this.method = method;
    1520            this.name = method.getName();
    1521            this.parameterTypes = method.getParameterTypes();
    1522        }
    1523
    1524        @Override
    1525        public int hashCode() {
    1526            final int prime = 31;
    1527            int result = 1;
    1528            result = prime * result + Arrays.hashCode(parameterTypes);
    1529            result = prime * result + ((name == null) ? 0 : name.hashCode());
    1530            return result;
    1531        }
    1532
    1533        @Override
    1534        public boolean equals(Object obj) {
    1535            if (this == obj)
    1536                return true;
    1537            if (obj == null)
    1538                return false;
    1539            if (getClass() != obj.getClass())
    1540                return false;
    1541            MethodSignature other = (MethodSignature) obj;
    1542            if (!Arrays.equals(parameterTypes, other.parameterTypes))
    1543                return false;
    1544            if (name == null) {
    1545                if (other.name != null)
    1546                    return false;
    1547            } else if (!name.equals(other.name))
    1548                return false;
    1549            return true;
    1550        }
    1551
    1552        @Override
    1553        public int compareTo(MethodSignature o) {
    1554            return method.getName().compareTo(o.method.getName());
    1555        }
    1556    }
    1557
    1558    @Override
    1559    public PlasticClass addToString(final String toStringValue)
    1560    {
    1561        check();
    1562
    1563        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
    1564        {
    1565            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
    1566            {
    1567                @Override
    1568                public void doBuild(InstructionBuilder builder)
    1569                {
    1570                    builder.loadConstant(toStringValue).returnResult();
    1571                }
    1572            });
    1573        }
    1574
    1575        return this;
    1576    }
    1577
    1578    @Override
    1579    public boolean isMethodImplemented(MethodDescription description)
    1580    {
    1581        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
    1582    }
    1583
    1584    @Override
    1585    public boolean isInterfaceImplemented(Class interfaceType)
    1586    {
    1587        assert interfaceType != null;
    1588        assert interfaceType.isInterface();
    1589
    1590        String interfaceName = nameCache.toInternalName(interfaceType);
    1591
    1592        return inheritanceData.isInterfaceImplemented(interfaceName);
    1593    }
    1594
    1595    @Override
    1596    public String getSuperClassName()
    1597    {
    1598        return superClassName;
    1599    }
    1600
    1601    @Override
    1602    public PlasticClass onConstruct(ConstructorCallback callback)
    1603    {
    1604        check();
    1605
    1606        assert callback != null;
    1607
    1608        constructorCallbacks.add(callback);
    1609
    1610        return this;
    1611    }
    1612
    1613    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
    1614    {
    1615        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
    1616
    1617        fieldInstrumentations.write.put(fieldName, fi);
    1618
    1619        if (!(proxy || privateField))
    1620        {
    1621            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
    1622        }
    1623    }
    1624
    1625    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
    1626    {
    1627        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
    1628
    1629        fieldInstrumentations.read.put(fieldName, fi);
    1630
    1631        if (!(proxy || privateField))
    1632        {
    1633            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
    1634        }
    1635    }
    1636
    1637    @Override
    1638    public String toString()
    1639    {
    1640        return String.format("PlasticClassImpl[%s]", className);
    1641    }
    1642}