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    }