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