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