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