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