    001    // Copyright 2011, 2012 The Apache Software Foundation
    002    //
    003    // Licensed under the Apache License, Version 2.0 (the "License");
    004    // you may not use this file except in compliance with the License.
    005    // You may obtain a copy of the License at
    006    //
    007    // http://www.apache.org/licenses/LICENSE-2.0
    008    //
    009    // Unless required by applicable law or agreed to in writing, software
    010    // distributed under the License is distributed on an "AS IS" BASIS,
    011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    012    // See the License for the specific language governing permissions and
    013    // limitations under the License.
    014
    015    package org.apache.tapestry5.internal.plastic;
    016
    017    import org.apache.tapestry5.internal.plastic.asm.Opcodes;
    018    import org.apache.tapestry5.internal.plastic.asm.Type;
    019    import org.apache.tapestry5.internal.plastic.asm.tree.*;
    020    import org.apache.tapestry5.plastic.*;
    021
    022    import java.lang.annotation.Annotation;
    023    import java.lang.reflect.Constructor;
    024    import java.lang.reflect.Method;
    025    import java.lang.reflect.Modifier;
    026    import java.util.*;
    027
    028    @SuppressWarnings("all")
    029    public 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        /**
    171         * @param classNode
    172         * @param pool
    173         * @param parentInheritanceData
    174         * @param parentStaticContext
    175         * @param proxy
    176         */
    177        public PlasticClassImpl(ClassNode classNode, PlasticClassPool pool, InheritanceData parentInheritanceData,
    178                                StaticContext parentStaticContext, boolean proxy)
    179        {
    180            this.classNode = classNode;
    181            this.pool = pool;
    182            this.proxy = proxy;
    183
    184            staticContext = parentStaticContext.dupe();
    185
    186            className = PlasticInternalUtils.toClassName(classNode.name);
    187            superClassName = PlasticInternalUtils.toClassName(classNode.superName);
    188
    189            fieldInstrumentations = new FieldInstrumentations(classNode.superName);
    190
    191            annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations),
    192                    pool.createAnnotationAccess(superClassName));
    193
    194            this.parentInheritanceData = parentInheritanceData;
    195            inheritanceData = parentInheritanceData.createChild(className);
    196
    197            for (String interfaceName : classNode.interfaces)
    198            {
    199                inheritanceData.addInterface(interfaceName);
    200            }
    201
    202            methods = new ArrayList(classNode.methods.size());
    203
    204            String invalidConstructorMessage = invalidConstructorMessage();
    205
    206            for (MethodNode node : classNode.methods)
    207            {
    208                if (node.name.equals(CONSTRUCTOR_NAME))
    209                {
    210                    if (node.desc.equals(NOTHING_TO_VOID))
    211                    {
    212                        originalConstructor = node;
    213                        fieldTransformMethods.add(node);
    214                    } else
    215                    {
    216                        node.instructions.clear();
    217
    218                        newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage);
    219                    }
    220
    221                    continue;
    222                }
    223
    224                /**
    225                 * Static methods are not visible to the main API methods, but they must still be transformed,
    226                 * in case they directly access fields. In addition, track their names to avoid collisions.
    227                 */
    228                if (Modifier.isStatic(node.access))
    229                {
    230                    if (!Modifier.isPrivate(node.access))
    231                    {
    232                        inheritanceData.addMethod(node.name, node.desc);
    233                    }
    234
    235                    methodNames.add(node.name);
    236
    237                    fieldTransformMethods.add(node);
    238
    239                    continue;
    240                }
    241
    242                if (!Modifier.isAbstract(node.access))
    243                {
    244                    fieldTransformMethods.add(node);
    245                }
    246
    247                PlasticMethodImpl pmi = new PlasticMethodImpl(this, node);
    248
    249                methods.add(pmi);
    250                description2method.put(pmi.getDescription(), pmi);
    251
    252                if (isInheritableMethod(node))
    253                {
    254                    inheritanceData.addMethod(node.name, node.desc);
    255                }
    256
    257                methodNames.add(node.name);
    258            }
    259
    260            methodNames.addAll(parentInheritanceData.methodNames());
    261
    262            Collections.sort(methods);
    263
    264            fields = new ArrayList(classNode.fields.size());
    265
    266            for (FieldNode node : classNode.fields)
    267            {
    268                fieldNames.add(node.name);
    269
    270                // Ignore static fields.
    271
    272                if (Modifier.isStatic(node.access))
    273                    continue;
    274
    275                // When we instrument the field such that it must be private, we'll get an exception.
    276
    277                fields.add(new PlasticFieldImpl(this, node));
    278            }
    279
    280            Collections.sort(fields);
    281
    282            // TODO: Make the output class's constructor protected, and create a shim class to instantiate it
    283            // efficiently (without reflection).
    284            newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null);
    285            constructorBuilder = newBuilder(newConstructor);
    286
    287            // Start by calling the super-class no args constructor
    288
    289            if (parentInheritanceData.isTransformed())
    290            {
    291                // If the parent is transformed, our first step is always to invoke its constructor.
    292
    293                constructorBuilder.loadThis().loadArgument(0).loadArgument(1);
    294                constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(),
    295                        InstanceContext.class.getName());
    296            } else
    297            {
    298                // Assumes the base class includes a visible constructor that takes no arguments.
    299                // TODO: Do a proper check for this case and throw a meaningful exception
    300                // if not present.
    301
    302                constructorBuilder.loadThis().invokeConstructor(superClassName);
    303            }
    304
    305            // During the transformation, we'll be adding code to the constructor to pull values
    306            // out of the static or instance context and assign them to fields.
    307
    308            // Later on, we'll add the RETURN opcode
    309        }
    310
    311        private String invalidConstructorMessage()
    312        {
    313            return String.format("Class %s has been transformed and may not be directly instantiated.", className);
    314        }
    315
    316        public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
    317        {
    318            check();
    319
    320            return annotationAccess.hasAnnotation(annotationType);
    321        }
    322
    323        public <T extends Annotation> T getAnnotation(Class<T> annotationType)
    324        {
    325            check();
    326
    327            return annotationAccess.getAnnotation(annotationType);
    328        }
    329
    330        public PlasticClass proxyInterface(Class interfaceType, PlasticField field)
    331        {
    332            check();
    333
    334            assert field != null;
    335
    336            introduceInterface(interfaceType);
    337
    338            for (Method m : interfaceType.getMethods())
    339            {
    340                introduceMethod(m).delegateTo(field);
    341            }
    342
    343            return this;
    344        }
    345
    346        public ClassInstantiator createInstantiator()
    347        {
    348            lock();
    349
    350            createShimIfNeeded();
    351
    352            interceptFieldAccess();
    353
    354            rewriteAdvisedMethods();
    355
    356            completeConstructor();
    357
    358            transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
    359
    360            return createInstantiatorFromClass(transformedClass);
    361        }
    362
    363        private ClassInstantiator createInstantiatorFromClass(Class clazz)
    364        {
    365            try
    366            {
    367                Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
    368
    369                return new ClassInstantiatorImpl(clazz, ctor, staticContext);
    370            } catch (Exception ex)
    371            {
    372                throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
    373                        clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
    374            }
    375        }
    376
    377        private void completeConstructor()
    378        {
    379            if (originalConstructor != null)
    380            {
    381                convertOriginalConstructorToMethod();
    382            }
    383
    384            invokeCallbacks();
    385
    386            constructorBuilder.returnResult();
    387
    388            classNode.methods.add(newConstructor);
    389        }
    390
    391        private void invokeCallbacks()
    392        {
    393            for (ConstructorCallback callback : constructorCallbacks)
    394            {
    395                invokeCallback(callback);
    396            }
    397        }
    398
    399        private void invokeCallback(ConstructorCallback callback)
    400        {
    401            int index = staticContext.store(callback);
    402
    403            // First, load the callback
    404
    405            constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
    406
    407            // Load this and the InstanceContext
    408            constructorBuilder.loadThis().loadArgument(1);
    409
    410            constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
    411        }
    412
    413
    414        /**
    415         * Convert the original constructor into a private method invoked from the
    416         * generated constructor.
    417         */
    418        private void convertOriginalConstructorToMethod()
    419        {
    420            String initializerName = makeUnique(methodNames, "initializeInstance");
    421
    422            int originalAccess = originalConstructor.access;
    423
    424            originalConstructor.access = ACC_PRIVATE;
    425            originalConstructor.name = initializerName;
    426
    427            stripOutSuperConstructorCall(originalConstructor);
    428
    429            constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
    430
    431            // And replace it with a constructor that throws an exception
    432
    433            MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
    434                    null);
    435
    436            newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
    437
    438            classNode.methods.add(replacementConstructor);
    439        }
    440
    441        private void stripOutSuperConstructorCall(MethodNode cons)
    442        {
    443            InsnList ins = cons.instructions;
    444
    445            ListIterator li = ins.iterator();
    446
    447            // Look for the ALOAD 0 (i.e., push this on the stack)
    448            while (li.hasNext())
    449            {
    450                AbstractInsnNode node = (AbstractInsnNode) li.next();
    451
    452                if (node.getOpcode() == ALOAD)
    453                {
    454                    VarInsnNode varNode = (VarInsnNode) node;
    455
    456                    assert varNode.var == 0;
    457
    458                    // Remove the ALOAD
    459                    li.remove();
    460                    break;
    461                }
    462            }
    463
    464            // Look for the call to the super-class, an INVOKESPECIAL
    465            while (li.hasNext())
    466            {
    467                AbstractInsnNode node = (AbstractInsnNode) li.next();
    468
    469                if (node.getOpcode() == INVOKESPECIAL)
    470                {
    471                    MethodInsnNode mnode = (MethodInsnNode) node;
    472
    473                    assert mnode.owner.equals(classNode.superName);
    474                    assert mnode.name.equals(CONSTRUCTOR_NAME);
    475                    assert mnode.desc.equals(cons.desc);
    476
    477                    li.remove();
    478                    return;
    479                }
    480            }
    481
    482            throw new AssertionError("Could not convert constructor to simple method.");
    483        }
    484
    485        public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
    486        {
    487            check();
    488
    489            List<PlasticField> result = getAllFields();
    490
    491            Iterator<PlasticField> iterator = result.iterator();
    492
    493            while (iterator.hasNext())
    494            {
    495                PlasticField plasticField = iterator.next();
    496
    497                if (!plasticField.hasAnnotation(annotationType))
    498                    iterator.remove();
    499            }
    500
    501            return result;
    502        }
    503
    504        public List<PlasticField> getAllFields()
    505        {
    506            check();
    507
    508            return new ArrayList<PlasticField>(fields);
    509        }
    510
    511        public List<PlasticField> getUnclaimedFields()
    512        {
    513            check();
    514
    515            // Initially null, and set back to null by PlasticField.claim().
    516
    517            if (unclaimedFields == null)
    518            {
    519                unclaimedFields = new ArrayList<PlasticField>(fields.size());
    520
    521                for (PlasticField f : fields)
    522                {
    523                    if (!f.isClaimed())
    524                        unclaimedFields.add(f);
    525                }
    526            }
    527
    528            return unclaimedFields;
    529        }
    530
    531        public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
    532                                                    String[] exceptionTypes)
    533        {
    534            check();
    535
    536            assert PlasticInternalUtils.isNonBlank(typeName);
    537            assert PlasticInternalUtils.isNonBlank(suggestedName);
    538
    539            String name = makeUnique(methodNames, suggestedName);
    540
    541            MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
    542                    exceptionTypes);
    543
    544            return introduceMethod(description);
    545        }
    546
    547        public PlasticField introduceField(String className, String suggestedName)
    548        {
    549            check();
    550
    551            assert PlasticInternalUtils.isNonBlank(className);
    552            assert PlasticInternalUtils.isNonBlank(suggestedName);
    553
    554            String name = makeUnique(fieldNames, suggestedName);
    555
    556            // No signature and no initial value
    557
    558            FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
    559
    560            classNode.fields.add(fieldNode);
    561
    562            fieldNames.add(name);
    563
    564            PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
    565
    566            return newField;
    567        }
    568
    569        public PlasticField introduceField(Class fieldType, String suggestedName)
    570        {
    571            assert fieldType != null;
    572
    573            return introduceField(nameCache.toTypeName(fieldType), suggestedName);
    574        }
    575
    576        String makeUnique(Set<String> values, String input)
    577        {
    578            return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
    579        }
    580
    581        public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
    582        {
    583            check();
    584
    585            List<PlasticMethod> result = getMethods();
    586            Iterator<PlasticMethod> iterator = result.iterator();
    587
    588            while (iterator.hasNext())
    589            {
    590                PlasticMethod method = iterator.next();
    591
    592                if (!method.hasAnnotation(annotationType))
    593                    iterator.remove();
    594            }
    595
    596            return result;
    597        }
    598
    599        public List<PlasticMethod> getMethods()
    600        {
    601            check();
    602
    603            return new ArrayList<PlasticMethod>(methods);
    604        }
    605
    606        public PlasticMethod introduceMethod(MethodDescription description)
    607        {
    608            check();
    609
    610            if (Modifier.isAbstract(description.modifiers))
    611            {
    612                description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
    613            }
    614
    615            PlasticMethod result = description2method.get(description);
    616
    617            if (result == null)
    618            {
    619                result = createNewMethod(description);
    620
    621                description2method.put(description, result);
    622            }
    623
    624            methodNames.add(description.methodName);
    625
    626            // Note that is it not necessary to add the new MethodNode to
    627            // fieldTransformMethods (the default implementations provided by introduceMethod() do not
    628            // ever access instance fields) ... unless the caller invokes changeImplementation().
    629
    630            return result;
    631        }
    632
    633        public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
    634        {
    635            check();
    636
    637            // TODO: optimize this so that a default implementation is not created.
    638
    639            return introduceMethod(description).changeImplementation(callback);
    640        }
    641
    642        public PlasticMethod introduceMethod(Method method)
    643        {
    644            check();
    645
    646            return introduceMethod(new MethodDescription(method));
    647        }
    648
    649        void addMethod(MethodNode methodNode)
    650        {
    651            classNode.methods.add(methodNode);
    652
    653            methodNames.add(methodNode.name);
    654
    655            if (!Modifier.isPrivate(methodNode.access))
    656                inheritanceData.addMethod(methodNode.name, methodNode.desc);
    657        }
    658
    659        private PlasticMethod createNewMethod(MethodDescription description)
    660        {
    661            if (Modifier.isStatic(description.modifiers))
    662                throw new IllegalArgumentException(String.format(
    663                        "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
    664                        description, className));
    665
    666            String desc = nameCache.toDesc(description);
    667
    668            String[] exceptions = new String[description.checkedExceptionTypes.length];
    669            for (int i = 0; i < exceptions.length; i++)
    670            {
    671                exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
    672            }
    673
    674            MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
    675                    description.genericSignature, exceptions);
    676            boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
    677
    678            if (isOverride)
    679                createOverrideOfBaseClassImpl(description, methodNode);
    680            else
    681                createNewMethodImpl(description, methodNode);
    682
    683            addMethod(methodNode);
    684
    685            return new PlasticMethodImpl(this, methodNode);
    686        }
    687
    688        private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
    689        {
    690            newBuilder(methodDescription, methodNode).returnDefaultValue();
    691        }
    692
    693        private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
    694        {
    695            InstructionBuilder builder = newBuilder(methodDescription, methodNode);
    696
    697            builder.loadThis();
    698            builder.loadArguments();
    699            builder.invokeSpecial(superClassName, methodDescription);
    700            builder.returnResult();
    701        }
    702
    703        /**
    704         * Iterates over all non-introduced methods, including the original constructor. For each
    705         * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
    706         * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
    707         * for the class has been created, as the shim may create methods that contain references to fields that may be
    708         * subject to field access interception.
    709         */
    710        private void interceptFieldAccess()
    711        {
    712            for (MethodNode node : fieldTransformMethods)
    713            {
    714                // Intercept field access inside the method, tracking which access methods
    715                // are actually used by removing them from accessMethods
    716
    717                interceptFieldAccess(node);
    718            }
    719        }
    720
    721        /**
    722         * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
    723         * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
    724         */
    725        private void createShimIfNeeded()
    726        {
    727            if (shimFields.isEmpty() && shimMethods.isEmpty())
    728                return;
    729
    730            PlasticClassHandleShim shim = createShimInstance();
    731
    732            installShim(shim);
    733        }
    734
    735        public void installShim(PlasticClassHandleShim shim)
    736        {
    737            for (PlasticFieldImpl f : shimFields)
    738            {
    739                f.installShim(shim);
    740            }
    741
    742            for (PlasticMethodImpl m : shimMethods)
    743            {
    744                m.installShim(shim);
    745            }
    746        }
    747
    748        public PlasticClassHandleShim createShimInstance()
    749        {
    750            String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
    751
    752            ClassNode shimClassNode = new ClassNode();
    753
    754            shimClassNode.visit(V1_5, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
    755                    null);
    756
    757            implementConstructor(shimClassNode);
    758
    759            if (!shimFields.isEmpty())
    760            {
    761                implementShimGet(shimClassNode);
    762                implementShimSet(shimClassNode);
    763            }
    764
    765            if (!shimMethods.isEmpty())
    766            {
    767                implementShimInvoke(shimClassNode);
    768            }
    769
    770            return instantiateShim(shimClassNode);
    771        }
    772
    773        private void implementConstructor(ClassNode shimClassNode)
    774        {
    775            MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
    776
    777            InstructionBuilder builder = newBuilder(mn);
    778
    779            builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
    780
    781            shimClassNode.methods.add(mn);
    782
    783        }
    784
    785        private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
    786        {
    787            try
    788            {
    789                Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
    790
    791                return (PlasticClassHandleShim) shimClass.newInstance();
    792            } catch (Exception ex)
    793            {
    794                throw new RuntimeException(
    795                        String.format("Unable to instantiate shim class %s for plastic class %s: %s",
    796                                PlasticInternalUtils.toClassName(shimClassNode.name), className,
    797                                PlasticInternalUtils.toMessage(ex)), ex);
    798            }
    799        }
    800
    801        private void implementShimGet(ClassNode shimClassNode)
    802        {
    803            MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
    804
    805            InstructionBuilder builder = newBuilder(mn);
    806
    807            // Arg 0 is the target instance
    808            // Arg 1 is the index
    809
    810            builder.loadArgument(0).checkcast(className);
    811            builder.loadArgument(1);
    812
    813            builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
    814            {
    815                public void doSwitch(SwitchBlock block)
    816                {
    817                    for (PlasticFieldImpl f : shimFields)
    818                    {
    819                        f.extendShimGet(block);
    820                    }
    821                }
    822            });
    823
    824            shimClassNode.methods.add(mn);
    825        }
    826
    827        private void implementShimSet(ClassNode shimClassNode)
    828        {
    829            MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
    830
    831            InstructionBuilder builder = newBuilder(mn);
    832
    833            // Arg 0 is the target instance
    834            // Arg 1 is the index
    835            // Arg 2 is the new value
    836
    837            builder.loadArgument(0).checkcast(className);
    838            builder.loadArgument(2);
    839
    840            builder.loadArgument(1);
    841
    842            builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
    843            {
    844                public void doSwitch(SwitchBlock block)
    845                {
    846                    for (PlasticFieldImpl f : shimFields)
    847                    {
    848                        f.extendShimSet(block);
    849                    }
    850                }
    851            });
    852
    853            builder.returnResult();
    854
    855            shimClassNode.methods.add(mn);
    856        }
    857
    858        private void implementShimInvoke(ClassNode shimClassNode)
    859        {
    860            MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
    861                    null);
    862
    863            InstructionBuilder builder = newBuilder(mn);
    864
    865            // Arg 0 is the target instance
    866            // Arg 1 is the index
    867            // Arg 2 is the object array of parameters
    868
    869            builder.loadArgument(0).checkcast(className);
    870
    871            builder.loadArgument(1);
    872
    873            builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
    874            {
    875                public void doSwitch(SwitchBlock block)
    876                {
    877                    for (PlasticMethodImpl m : shimMethods)
    878                    {
    879                        m.extendShimInvoke(block);
    880                    }
    881                }
    882            });
    883
    884            shimClassNode.methods.add(mn);
    885        }
    886
    887        private void rewriteAdvisedMethods()
    888        {
    889            for (PlasticMethodImpl method : advisedMethods)
    890            {
    891                method.rewriteMethodForAdvice();
    892            }
    893        }
    894
    895        private void interceptFieldAccess(MethodNode methodNode)
    896        {
    897            InsnList insns = methodNode.instructions;
    898
    899            ListIterator it = insns.iterator();
    900
    901            while (it.hasNext())
    902            {
    903                AbstractInsnNode node = (AbstractInsnNode) it.next();
    904
    905                int opcode = node.getOpcode();
    906
    907                if (opcode != GETFIELD && opcode != PUTFIELD)
    908                {
    909                    continue;
    910                }
    911
    912                FieldInsnNode fnode = (FieldInsnNode) node;
    913
    914                FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
    915
    916                if (instrumentation == null)
    917                {
    918                    continue;
    919                }
    920
    921                // Replace the field access node with the appropriate method invocation.
    922
    923                insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription));
    924
    925                it.remove();
    926            }
    927        }
    928
    929        private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
    930        {
    931            // First look in the local fieldInstrumentations, which contains private field instrumentations
    932            // (as well as non-private ones).
    933
    934            String searchStart = node.owner;
    935
    936            if (searchStart.equals(classNode.name))
    937            {
    938                FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
    939
    940                if (result != null)
    941                {
    942                    return result;
    943                }
    944
    945                // Slight optimization: start the search in the super-classes' fields, since we've already
    946                // checked this classes fields.
    947
    948                searchStart = classNode.superName;
    949            }
    950
    951            return pool.getFieldInstrumentation(searchStart, node.name, forRead);
    952        }
    953
    954        String getInstanceContextFieldName()
    955        {
    956            if (instanceContextFieldName == null)
    957            {
    958                instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
    959
    960                // TODO: We could use a protected field and only initialize
    961                // it once, in the first base class where it is needed, though that raises the possibilities
    962                // of name conflicts (a subclass might introduce a field with a conflicting name).
    963
    964                FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
    965                        null, null);
    966
    967                classNode.fields.add(node);
    968
    969                // Extend the constructor to store the context in a field.
    970
    971                constructorBuilder.loadThis().loadArgument(1)
    972                        .putField(className, instanceContextFieldName, InstanceContext.class);
    973            }
    974
    975            return instanceContextFieldName;
    976        }
    977
    978        /**
    979         * Creates a new private final field and initializes its value (using the StaticContext).
    980         */
    981        String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
    982                                                         Object injectedFieldValue)
    983        {
    984            String name = makeUnique(fieldNames, suggestedFieldName);
    985
    986            FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
    987
    988            classNode.fields.add(field);
    989
    990            initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
    991
    992            return name;
    993        }
    994
    995        /**
    996         * Initializes a field from the static context. The injected value is added to the static
    997         * context and the class constructor updated to assign the value from the context (which includes casting and
    998         * possibly unboxing).
    999         */
    1000        void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
    1001        {
    1002            int index = staticContext.store(injectedFieldValue);
    1003
    1004            // Although it feels nicer to do the loadThis() later and then swap(), that breaks
    1005            // on primitive longs and doubles, so its just easier to do the loadThis() first
    1006            // so its at the right place on the stack for the putField().
    1007
    1008            constructorBuilder.loadThis();
    1009
    1010            constructorBuilder.loadArgument(0).loadConstant(index);
    1011            constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
    1012            constructorBuilder.castOrUnbox(fieldType);
    1013
    1014            constructorBuilder.putField(className, fieldName, fieldType);
    1015        }
    1016
    1017        void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
    1018        {
    1019            builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
    1020        }
    1021
    1022        public PlasticClass getPlasticClass()
    1023        {
    1024            return this;
    1025        }
    1026
    1027        public Class<?> getTransformedClass()
    1028        {
    1029            if (transformedClass == null)
    1030                throw new IllegalStateException(String.format(
    1031                        "Transformed class %s is not yet available because the transformation is not yet complete.",
    1032                        className));
    1033
    1034            return transformedClass;
    1035        }
    1036
    1037        private boolean isInheritableMethod(MethodNode node)
    1038        {
    1039            return (node.access & (ACC_ABSTRACT | ACC_PRIVATE)) == 0;
    1040        }
    1041
    1042        public String getClassName()
    1043        {
    1044            return className;
    1045        }
    1046
    1047        InstructionBuilderImpl newBuilder(MethodNode mn)
    1048        {
    1049            return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
    1050        }
    1051
    1052        InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
    1053        {
    1054            return new InstructionBuilderImpl(description, mn, nameCache);
    1055        }
    1056
    1057        public Set<PlasticMethod> introduceInterface(Class interfaceType)
    1058        {
    1059            check();
    1060
    1061            assert interfaceType != null;
    1062
    1063            if (!interfaceType.isInterface())
    1064                throw new IllegalArgumentException(String.format(
    1065                        "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName()));
    1066
    1067            String interfaceName = nameCache.toInternalName(interfaceType);
    1068
    1069            if (!inheritanceData.isInterfaceImplemented(interfaceName))
    1070            {
    1071                classNode.interfaces.add(interfaceName);
    1072                inheritanceData.addInterface(interfaceName);
    1073            }
    1074
    1075            Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
    1076
    1077            for (Method m : interfaceType.getMethods())
    1078            {
    1079                MethodDescription description = new MethodDescription(m);
    1080
    1081                if (!isMethodImplemented(description))
    1082                {
    1083                    introducedMethods.add(introduceMethod(m));
    1084                }
    1085            }
    1086
    1087            return introducedMethods;
    1088        }
    1089
    1090        public PlasticClass addToString(final String toStringValue)
    1091        {
    1092            check();
    1093
    1094            if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
    1095            {
    1096                introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
    1097                {
    1098                    public void doBuild(InstructionBuilder builder)
    1099                    {
    1100                        builder.loadConstant(toStringValue).returnResult();
    1101                    }
    1102                });
    1103            }
    1104
    1105            return this;
    1106        }
    1107
    1108        public boolean isMethodImplemented(MethodDescription description)
    1109        {
    1110            return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
    1111        }
    1112
    1113        public boolean isInterfaceImplemented(Class interfaceType)
    1114        {
    1115            assert interfaceType != null;
    1116            assert interfaceType.isInterface();
    1117
    1118            String interfaceName = nameCache.toInternalName(interfaceType);
    1119
    1120            return inheritanceData.isInterfaceImplemented(interfaceName);
    1121        }
    1122
    1123        public String getSuperClassName()
    1124        {
    1125            return superClassName;
    1126        }
    1127
    1128        public PlasticClass onConstruct(ConstructorCallback callback)
    1129        {
    1130            check();
    1131
    1132            assert callback != null;
    1133
    1134            constructorCallbacks.add(callback);
    1135
    1136            return this;
    1137        }
    1138
    1139        void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
    1140        {
    1141            FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
    1142
    1143            fieldInstrumentations.write.put(fieldName, fi);
    1144
    1145            if (!(proxy || privateField))
    1146            {
    1147                pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
    1148            }
    1149        }
    1150
    1151        void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
    1152        {
    1153            FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
    1154
    1155            fieldInstrumentations.read.put(fieldName, fi);
    1156
    1157            if (!(proxy || privateField))
    1158            {
    1159                pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
    1160            }
    1161        }
    1162    }