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.Array;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.Method;
026import java.lang.reflect.Modifier;
027import java.util.*;
028import java.util.regex.Matcher;
029import java.util.regex.Pattern;
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        int lastIndexOfDot = className.lastIndexOf('.');
198
199        String packageName = lastIndexOfDot > -1 ? className.substring(0, lastIndexOfDot) : "";
200
201        fieldInstrumentations = new FieldInstrumentations(classNode.superName);
202
203        annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations),
204                pool.createAnnotationAccess(superClassName));
205
206        this.parentInheritanceData = parentInheritanceData;
207
208        inheritanceData = parentInheritanceData.createChild(packageName);
209
210        for (String interfaceName : classNode.interfaces)
211        {
212            inheritanceData.addInterface(interfaceName);
213        }
214
215        methods = new ArrayList(classNode.methods.size());
216
217        String invalidConstructorMessage = invalidConstructorMessage();
218
219        for (MethodNode node : classNode.methods)
220        {
221            if (node.name.equals(CONSTRUCTOR_NAME))
222            {
223                if (node.desc.equals(NOTHING_TO_VOID))
224                {
225                    originalConstructor = node;
226                    fieldTransformMethods.add(node);
227                } else
228                {
229                    node.instructions.clear();
230
231                    newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage);
232                }
233
234                continue;
235            }
236
237            /**
238             * Static methods are not visible to the main API methods, but they must still be transformed,
239             * in case they directly access fields. In addition, track their names to avoid collisions.
240             */
241            if (Modifier.isStatic(node.access))
242            {
243                if (isInheritableMethod(node))
244                {
245                    inheritanceData.addMethod(node.name, node.desc, node.access == 0);
246                }
247
248                methodNames.add(node.name);
249
250                fieldTransformMethods.add(node);
251
252                continue;
253            }
254
255            if (!Modifier.isAbstract(node.access))
256            {
257                fieldTransformMethods.add(node);
258            }
259
260            PlasticMethodImpl pmi = new PlasticMethodImpl(this, node);
261
262            methods.add(pmi);
263            description2method.put(pmi.getDescription(), pmi);
264
265            if (isInheritableMethod(node))
266            {
267                inheritanceData.addMethod(node.name, node.desc, node.access == 0);
268            }
269
270            methodNames.add(node.name);
271        }
272
273        methodNames.addAll(parentInheritanceData.methodNames());
274
275        Collections.sort(methods);
276
277        fields = new ArrayList(classNode.fields.size());
278
279        for (FieldNode node : classNode.fields)
280        {
281            fieldNames.add(node.name);
282
283            // Ignore static fields.
284
285            if (Modifier.isStatic(node.access))
286                continue;
287
288            // When we instrument the field such that it must be private, we'll get an exception.
289
290            fields.add(new PlasticFieldImpl(this, node));
291        }
292
293        Collections.sort(fields);
294
295        // TODO: Make the output class's constructor protected, and create a shim class to instantiate it
296        // efficiently (without reflection).
297        newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null);
298        constructorBuilder = newBuilder(newConstructor);
299
300        // Start by calling the super-class no args constructor
301
302        if (parentInheritanceData.isTransformed())
303        {
304            // If the parent is transformed, our first step is always to invoke its constructor.
305
306            constructorBuilder.loadThis().loadArgument(0).loadArgument(1);
307            constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(),
308                    InstanceContext.class.getName());
309        } else
310        {
311            // Assumes the base class includes a visible constructor that takes no arguments.
312            // TODO: Do a proper check for this case and throw a meaningful exception
313            // if not present.
314
315            constructorBuilder.loadThis().invokeConstructor(superClassName);
316        }
317
318        // During the transformation, we'll be adding code to the constructor to pull values
319        // out of the static or instance context and assign them to fields.
320
321        // Later on, we'll add the RETURN opcode
322    }
323
324    private String invalidConstructorMessage()
325    {
326        return String.format("Class %s has been transformed and may not be directly instantiated.", className);
327    }
328
329    @Override
330    public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
331    {
332        check();
333
334        return annotationAccess.hasAnnotation(annotationType);
335    }
336
337    @Override
338    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
339    {
340        check();
341
342        return annotationAccess.getAnnotation(annotationType);
343    }
344
345    private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, MethodNode implementationMethodNode)
346    {
347        // visits the method attributes
348        int i, j, n;
349        if (implementationMethodNode.annotationDefault != null)
350        {
351            AnnotationVisitor av = methodNode.visitAnnotationDefault();
352            AnnotationNode.accept(av, null, implementationMethodNode.annotationDefault);
353            if (av != null)
354            {
355                av.visitEnd();
356            }
357        }
358        n = implementationMethodNode.visibleAnnotations == null ? 0 : implementationMethodNode.visibleAnnotations.size();
359        for (i = 0; i < n; ++i)
360        {
361            AnnotationNode an = implementationMethodNode.visibleAnnotations.get(i);
362            an.accept(methodNode.visitAnnotation(an.desc, true));
363        }
364        n = implementationMethodNode.invisibleAnnotations == null ? 0 : implementationMethodNode.invisibleAnnotations.size();
365        for (i = 0; i < n; ++i)
366        {
367            AnnotationNode an = implementationMethodNode.invisibleAnnotations.get(i);
368            an.accept(methodNode.visitAnnotation(an.desc, false));
369        }
370        n = implementationMethodNode.visibleParameterAnnotations == null
371                ? 0
372                : implementationMethodNode.visibleParameterAnnotations.length;
373        for (i = 0; i < n; ++i)
374        {
375            List<?> l = implementationMethodNode.visibleParameterAnnotations[i];
376            if (l == null)
377            {
378                continue;
379            }
380            for (j = 0; j < l.size(); ++j)
381            {
382                AnnotationNode an = (AnnotationNode) l.get(j);
383                an.accept(methodNode.visitParameterAnnotation(i, an.desc, true));
384            }
385        }
386        n = implementationMethodNode.invisibleParameterAnnotations == null
387                ? 0
388                : implementationMethodNode.invisibleParameterAnnotations.length;
389        for (i = 0; i < n; ++i)
390        {
391            List<?> l = implementationMethodNode.invisibleParameterAnnotations[i];
392            if (l == null)
393            {
394                continue;
395            }
396            for (j = 0; j < l.size(); ++j)
397            {
398                AnnotationNode an = (AnnotationNode) l.get(j);
399                an.accept(methodNode.visitParameterAnnotation(i, an.desc, false));
400            }
401        }
402
403        methodNode.visitEnd();
404
405    }
406
407    private static void removeDuplicatedAnnotations(MethodNode node)
408    {
409
410        removeDuplicatedAnnotations(node.visibleAnnotations);
411        removeDuplicatedAnnotations(node.invisibleAnnotations);
412
413        if (node.visibleParameterAnnotations != null)
414        {
415            for (List<AnnotationNode> list : node.visibleParameterAnnotations)
416            {
417                removeDuplicatedAnnotations(list);
418            }
419        }
420
421        if (node.invisibleParameterAnnotations != null)
422        {
423            for (List<AnnotationNode> list : node.invisibleParameterAnnotations)
424            {
425                removeDuplicatedAnnotations(list);
426            }
427        }
428
429    }
430
431    private static void removeDuplicatedAnnotations(ClassNode node)
432    {
433        removeDuplicatedAnnotations(node.visibleAnnotations, true);
434        removeDuplicatedAnnotations(node.invisibleAnnotations, true);
435    }
436
437    private static void removeDuplicatedAnnotations(List<AnnotationNode> list) {
438        removeDuplicatedAnnotations(list, false);
439    }
440
441    private static void removeDuplicatedAnnotations(List<AnnotationNode> list, boolean reverse) {
442
443        if (list != null)
444        {
445
446            final Set<String> annotations = new HashSet<String>();
447            final List<AnnotationNode> toBeRemoved = new ArrayList<AnnotationNode>();
448            final List<AnnotationNode> toBeIterated;
449
450            if (reverse)
451            {
452                toBeIterated = new ArrayList<AnnotationNode>(list);
453                Collections.reverse(toBeIterated);
454            }
455            else {
456                toBeIterated = list;
457            }
458
459            for (AnnotationNode annotationNode : toBeIterated)
460            {
461                if (annotations.contains(annotationNode.desc))
462                {
463                    toBeRemoved.add(annotationNode);
464                }
465                else
466                {
467                    annotations.add(annotationNode.desc);
468                }
469            }
470
471            for (AnnotationNode annotationNode : toBeRemoved)
472            {
473                list.remove(annotationNode);
474            }
475
476        }
477
478    }
479
480    private static String getParametersDesc(MethodNode methodNode) {
481        return methodNode.desc.substring(methodNode.desc.indexOf('(') + 1, methodNode.desc.lastIndexOf(')'));
482    }
483
484    private static MethodNode findExactMatchMethod(MethodNode methodNode, ClassNode source) {
485
486        MethodNode found = null;
487
488        final String methodDescription = getParametersDesc(methodNode);
489
490        for (MethodNode implementationMethodNode : source.methods)
491        {
492
493            final String implementationMethodDescription = getParametersDesc(implementationMethodNode);
494            if (methodNode.name.equals(implementationMethodNode.name) &&
495                    // We don't want synthetic methods.
496                    ((implementationMethodNode.access & Opcodes.ACC_SYNTHETIC) == 0)
497                    && (methodDescription.equals(implementationMethodDescription)))
498            {
499                found = implementationMethodNode;
500                break;
501            }
502        }
503
504        return found;
505
506    }
507
508    private static List<Class> getJavaParameterTypes(MethodNode methodNode) {
509        final ClassLoader classLoader = PlasticInternalUtils.class.getClassLoader();
510        Type[] parameterTypes = Type.getArgumentTypes(methodNode.desc);
511        List<Class> list = new ArrayList<Class>();
512        for (Type type : parameterTypes)
513        {
514            try
515            {
516                list.add(PlasticInternalUtils.toClass(classLoader, type.getClassName()));
517            }
518            catch (ClassNotFoundException e)
519            {
520                throw new RuntimeException(e); // shouldn't happen anyway
521            }
522        }
523        return list;
524    }
525
526    /**
527     * Returns the first method which matches the given methodNode.
528     * FIXME: this may not find the correct method if the correct one is declared after
529     * another in which all parameters are supertypes of the parameters of methodNode.
530     * To solve this, we would need to dig way deeper than we have time for this.
531     * @param methodNode
532     * @param classNode
533     * @return
534     */
535    private static MethodNode findGenericMethod(MethodNode methodNode, ClassNode classNode)
536    {
537
538        MethodNode found = null;
539
540        List<Class> parameterTypes = getJavaParameterTypes(methodNode);
541
542        for (MethodNode implementationMethodNode : classNode.methods)
543        {
544
545            if (methodNode.name.equals(implementationMethodNode.name))
546            {
547
548                final List<Class> implementationParameterTypes = getJavaParameterTypes(implementationMethodNode);
549
550                if (parameterTypes.size() == implementationParameterTypes.size())
551                {
552
553                    boolean matches = true;
554                    for (int i = 0; i < parameterTypes.size(); i++)
555                    {
556                        final Class implementationParameterType = implementationParameterTypes.get(i);
557                        final Class parameterType = parameterTypes.get(i);
558                        if (!parameterType.isAssignableFrom(implementationParameterType)) {
559                            matches = false;
560                            break;
561                        }
562
563                    }
564
565                    if (matches && !isBridge(implementationMethodNode))
566                    {
567                        found = implementationMethodNode;
568                        break;
569                    }
570
571                }
572
573            }
574
575        }
576
577        return found;
578
579    }
580
581    private static void addMethodAndParameterAnnotationsFromExistingClass(MethodNode methodNode, ClassNode source)
582    {
583        if (source != null)
584        {
585
586            MethodNode candidate = findExactMatchMethod(methodNode, source);
587
588            final String parametersDesc = getParametersDesc(methodNode);
589
590            // candidate will be null when the method has generic parameters
591            if (candidate == null && parametersDesc.trim().length() > 0)
592            {
593                candidate = findGenericMethod(methodNode, source);
594            }
595
596            if (candidate != null)
597            {
598                addMethodAndParameterAnnotationsFromExistingClass(methodNode, candidate);
599            }
600
601        }
602
603    }
604
605    /**
606     * Tells whether a given method is a bridge one or not.
607     * Notice the flag for bridge method is the same as volatile field. Java 6 doesn't have
608     * Modifiers.isBridge(), so we use a workaround.
609     */
610    private static boolean isBridge(MethodNode methodNode)
611    {
612        return Modifier.isVolatile(methodNode.access);
613    }
614
615    @Override
616    public PlasticClass proxyInterface(Class interfaceType, PlasticField field)
617    {
618        check();
619
620        assert field != null;
621
622        introduceInterface(interfaceType);
623
624        for (Method m : interfaceType.getMethods())
625        {
626            introduceMethod(m).delegateTo(field);
627        }
628
629        return this;
630    }
631
632    @Override
633    public ClassInstantiator createInstantiator()
634    {
635        lock();
636
637        addClassAnnotations(implementationClassNode);
638        removeDuplicatedAnnotations(classNode);
639
640        createShimIfNeeded();
641
642        interceptFieldAccess();
643
644        rewriteAdvisedMethods();
645
646        completeConstructor();
647
648        transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
649
650        return createInstantiatorFromClass(transformedClass);
651    }
652
653    private void addClassAnnotations(ClassNode otherClassNode)
654    {
655        // Copy annotations from implementation if available.
656        // Code adapted from ClassNode.accept(), as we just want to copy
657        // the annotations and nothing more.
658        if (otherClassNode != null)
659        {
660
661            int i, n;
662            n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size();
663            for (i = 0; i < n; ++i)
664            {
665                AnnotationNode an = otherClassNode.visibleAnnotations.get(i);
666                an.accept(classNode.visitAnnotation(an.desc, true));
667            }
668            n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size();
669            for (i = 0; i < n; ++i)
670            {
671                AnnotationNode an = otherClassNode.invisibleAnnotations.get(i);
672                an.accept(classNode.visitAnnotation(an.desc, false));
673            }
674
675        }
676    }
677
678    private ClassInstantiator createInstantiatorFromClass(Class clazz)
679    {
680        try
681        {
682            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
683
684            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
685        } catch (Exception ex)
686        {
687            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
688                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
689        }
690    }
691
692    private void completeConstructor()
693    {
694        if (originalConstructor != null)
695        {
696            convertOriginalConstructorToMethod();
697        }
698
699        invokeCallbacks();
700
701        constructorBuilder.returnResult();
702
703        classNode.methods.add(newConstructor);
704    }
705
706    private void invokeCallbacks()
707    {
708        for (ConstructorCallback callback : constructorCallbacks)
709        {
710            invokeCallback(callback);
711        }
712    }
713
714    private void invokeCallback(ConstructorCallback callback)
715    {
716        int index = staticContext.store(callback);
717
718        // First, load the callback
719
720        constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
721
722        // Load this and the InstanceContext
723        constructorBuilder.loadThis().loadArgument(1);
724
725        constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
726    }
727
728
729    /**
730     * Convert the original constructor into a private method invoked from the
731     * generated constructor.
732     */
733    private void convertOriginalConstructorToMethod()
734    {
735        String initializerName = makeUnique(methodNames, "initializeInstance");
736
737        int originalAccess = originalConstructor.access;
738
739        originalConstructor.access = ACC_PRIVATE;
740        originalConstructor.name = initializerName;
741
742        stripOutSuperConstructorCall(originalConstructor);
743
744        constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
745
746        // And replace it with a constructor that throws an exception
747
748        MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
749                null);
750
751        newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
752
753        classNode.methods.add(replacementConstructor);
754    }
755
756    private void stripOutSuperConstructorCall(MethodNode cons)
757    {
758        InsnList ins = cons.instructions;
759
760        ListIterator li = ins.iterator();
761
762        // Look for the ALOAD 0 (i.e., push this on the stack)
763        while (li.hasNext())
764        {
765            AbstractInsnNode node = (AbstractInsnNode) li.next();
766
767            if (node.getOpcode() == ALOAD)
768            {
769                VarInsnNode varNode = (VarInsnNode) node;
770
771                assert varNode.var == 0;
772
773                // Remove the ALOAD
774                li.remove();
775                break;
776            }
777        }
778
779        // Look for the call to the super-class, an INVOKESPECIAL
780        while (li.hasNext())
781        {
782            AbstractInsnNode node = (AbstractInsnNode) li.next();
783
784            if (node.getOpcode() == INVOKESPECIAL)
785            {
786                MethodInsnNode mnode = (MethodInsnNode) node;
787
788                assert mnode.owner.equals(classNode.superName);
789                assert mnode.name.equals(CONSTRUCTOR_NAME);
790                assert mnode.desc.equals(cons.desc);
791
792                li.remove();
793                return;
794            }
795        }
796
797        throw new AssertionError("Could not convert constructor to simple method.");
798    }
799
800    @Override
801    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
802    {
803        check();
804
805        List<PlasticField> result = getAllFields();
806
807        Iterator<PlasticField> iterator = result.iterator();
808
809        while (iterator.hasNext())
810        {
811            PlasticField plasticField = iterator.next();
812
813            if (!plasticField.hasAnnotation(annotationType))
814                iterator.remove();
815        }
816
817        return result;
818    }
819
820    @Override
821    public List<PlasticField> getAllFields()
822    {
823        check();
824
825        return new ArrayList<PlasticField>(fields);
826    }
827
828    @Override
829    public List<PlasticField> getUnclaimedFields()
830    {
831        check();
832
833        // Initially null, and set back to null by PlasticField.claim().
834
835        if (unclaimedFields == null)
836        {
837            unclaimedFields = new ArrayList<PlasticField>(fields.size());
838
839            for (PlasticField f : fields)
840            {
841                if (!f.isClaimed())
842                    unclaimedFields.add(f);
843            }
844        }
845
846        return unclaimedFields;
847    }
848
849    @Override
850    public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
851                                                String[] exceptionTypes)
852    {
853        check();
854
855        assert PlasticInternalUtils.isNonBlank(typeName);
856        assert PlasticInternalUtils.isNonBlank(suggestedName);
857
858        String name = makeUnique(methodNames, suggestedName);
859
860        MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
861                exceptionTypes);
862
863        return introduceMethod(description);
864    }
865
866    @Override
867    public PlasticField introduceField(String className, String suggestedName)
868    {
869        check();
870
871        assert PlasticInternalUtils.isNonBlank(className);
872        assert PlasticInternalUtils.isNonBlank(suggestedName);
873
874        String name = makeUnique(fieldNames, suggestedName);
875
876        // No signature and no initial value
877
878        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
879
880        classNode.fields.add(fieldNode);
881
882        fieldNames.add(name);
883
884        PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
885
886        return newField;
887    }
888
889    @Override
890    public PlasticField introduceField(Class fieldType, String suggestedName)
891    {
892        assert fieldType != null;
893
894        return introduceField(nameCache.toTypeName(fieldType), suggestedName);
895    }
896
897    String makeUnique(Set<String> values, String input)
898    {
899        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
900    }
901
902    @Override
903    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
904    {
905        check();
906
907        List<PlasticMethod> result = getMethods();
908        Iterator<PlasticMethod> iterator = result.iterator();
909
910        while (iterator.hasNext())
911        {
912            PlasticMethod method = iterator.next();
913
914            if (!method.hasAnnotation(annotationType))
915                iterator.remove();
916        }
917
918        return result;
919    }
920
921    @Override
922    public List<PlasticMethod> getMethods()
923    {
924        check();
925
926        return new ArrayList<PlasticMethod>(methods);
927    }
928
929    @Override
930    public PlasticMethod introduceMethod(MethodDescription description)
931    {
932        check();
933
934        if (Modifier.isAbstract(description.modifiers))
935        {
936            description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
937        }
938
939        PlasticMethod result = description2method.get(description);
940
941        if (result == null)
942        {
943            result = createNewMethod(description);
944
945            description2method.put(description, result);
946        }
947
948        methodNames.add(description.methodName);
949
950        // Note that is it not necessary to add the new MethodNode to
951        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
952        // ever access instance fields) ... unless the caller invokes changeImplementation().
953
954        return result;
955    }
956
957    @Override
958    public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
959    {
960        check();
961
962        // TODO: optimize this so that a default implementation is not created.
963
964        return introduceMethod(description).changeImplementation(callback);
965    }
966
967    @Override
968    public PlasticMethod introduceMethod(Method method)
969    {
970        check();
971
972        return introduceMethod(new MethodDescription(method));
973    }
974
975    void addMethod(MethodNode methodNode)
976    {
977        classNode.methods.add(methodNode);
978
979        methodNames.add(methodNode.name);
980
981        if (isInheritableMethod(methodNode))
982        {
983            inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0);
984        }
985    }
986
987    private PlasticMethod createNewMethod(MethodDescription description)
988    {
989        if (Modifier.isStatic(description.modifiers))
990            throw new IllegalArgumentException(String.format(
991                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
992                    description, className));
993
994        String desc = nameCache.toDesc(description);
995
996        String[] exceptions = new String[description.checkedExceptionTypes.length];
997        for (int i = 0; i < exceptions.length; i++)
998        {
999            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
1000        }
1001
1002        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
1003                description.genericSignature, exceptions);
1004        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
1005
1006        if (!isOverride)
1007        {
1008            addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode);
1009            addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode);
1010            removeDuplicatedAnnotations(methodNode);
1011        }
1012
1013        if (isOverride)
1014            createOverrideOfBaseClassImpl(description, methodNode);
1015        else
1016            createNewMethodImpl(description, methodNode);
1017
1018        addMethod(methodNode);
1019
1020        return new PlasticMethodImpl(this, methodNode);
1021    }
1022
1023    private boolean isDefaultMethod(Method method)
1024    {
1025        return method.getDeclaringClass().isInterface() &&
1026                (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC;
1027    }
1028
1029    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
1030    {
1031        newBuilder(methodDescription, methodNode).returnDefaultValue();
1032    }
1033
1034    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
1035    {
1036        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
1037
1038        builder.loadThis();
1039        builder.loadArguments();
1040        builder.invokeSpecial(superClassName, methodDescription);
1041        builder.returnResult();
1042    }
1043
1044    /**
1045     * Iterates over all non-introduced methods, including the original constructor. For each
1046     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
1047     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
1048     * for the class has been created, as the shim may create methods that contain references to fields that may be
1049     * subject to field access interception.
1050     */
1051    private void interceptFieldAccess()
1052    {
1053        for (MethodNode node : fieldTransformMethods)
1054        {
1055            // Intercept field access inside the method, tracking which access methods
1056            // are actually used by removing them from accessMethods
1057
1058            interceptFieldAccess(node);
1059        }
1060    }
1061
1062    /**
1063     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
1064     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
1065     */
1066    private void createShimIfNeeded()
1067    {
1068        if (shimFields.isEmpty() && shimMethods.isEmpty())
1069            return;
1070
1071        PlasticClassHandleShim shim = createShimInstance();
1072
1073        installShim(shim);
1074    }
1075
1076    public void installShim(PlasticClassHandleShim shim)
1077    {
1078        for (PlasticFieldImpl f : shimFields)
1079        {
1080            f.installShim(shim);
1081        }
1082
1083        for (PlasticMethodImpl m : shimMethods)
1084        {
1085            m.installShim(shim);
1086        }
1087    }
1088
1089    public PlasticClassHandleShim createShimInstance()
1090    {
1091        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
1092
1093        ClassNode shimClassNode = new ClassNode();
1094
1095        shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
1096                null);
1097
1098        implementConstructor(shimClassNode);
1099
1100        if (!shimFields.isEmpty())
1101        {
1102            implementShimGet(shimClassNode);
1103            implementShimSet(shimClassNode);
1104        }
1105
1106        if (!shimMethods.isEmpty())
1107        {
1108            implementShimInvoke(shimClassNode);
1109        }
1110
1111        return instantiateShim(shimClassNode);
1112    }
1113
1114    private void implementConstructor(ClassNode shimClassNode)
1115    {
1116        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
1117
1118        InstructionBuilder builder = newBuilder(mn);
1119
1120        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
1121
1122        shimClassNode.methods.add(mn);
1123
1124    }
1125
1126    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
1127    {
1128        try
1129        {
1130            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
1131
1132            return (PlasticClassHandleShim) shimClass.newInstance();
1133        } catch (Exception ex)
1134        {
1135            throw new RuntimeException(
1136                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
1137                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
1138                            PlasticInternalUtils.toMessage(ex)), ex);
1139        }
1140    }
1141
1142    private void implementShimGet(ClassNode shimClassNode)
1143    {
1144        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
1145
1146        InstructionBuilder builder = newBuilder(mn);
1147
1148        // Arg 0 is the target instance
1149        // Arg 1 is the index
1150
1151        builder.loadArgument(0).checkcast(className);
1152        builder.loadArgument(1);
1153
1154        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1155        {
1156            @Override
1157            public void doSwitch(SwitchBlock block)
1158            {
1159                for (PlasticFieldImpl f : shimFields)
1160                {
1161                    f.extendShimGet(block);
1162                }
1163            }
1164        });
1165
1166        shimClassNode.methods.add(mn);
1167    }
1168
1169    private void implementShimSet(ClassNode shimClassNode)
1170    {
1171        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
1172
1173        InstructionBuilder builder = newBuilder(mn);
1174
1175        // Arg 0 is the target instance
1176        // Arg 1 is the index
1177        // Arg 2 is the new value
1178
1179        builder.loadArgument(0).checkcast(className);
1180        builder.loadArgument(2);
1181
1182        builder.loadArgument(1);
1183
1184        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1185        {
1186            @Override
1187            public void doSwitch(SwitchBlock block)
1188            {
1189                for (PlasticFieldImpl f : shimFields)
1190                {
1191                    f.extendShimSet(block);
1192                }
1193            }
1194        });
1195
1196        builder.returnResult();
1197
1198        shimClassNode.methods.add(mn);
1199    }
1200
1201    private void implementShimInvoke(ClassNode shimClassNode)
1202    {
1203        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
1204                null);
1205
1206        InstructionBuilder builder = newBuilder(mn);
1207
1208        // Arg 0 is the target instance
1209        // Arg 1 is the index
1210        // Arg 2 is the object array of parameters
1211
1212        builder.loadArgument(0).checkcast(className);
1213
1214        builder.loadArgument(1);
1215
1216        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
1217        {
1218            @Override
1219            public void doSwitch(SwitchBlock block)
1220            {
1221                for (PlasticMethodImpl m : shimMethods)
1222                {
1223                    m.extendShimInvoke(block);
1224                }
1225            }
1226        });
1227
1228        shimClassNode.methods.add(mn);
1229    }
1230
1231    private void rewriteAdvisedMethods()
1232    {
1233        for (PlasticMethodImpl method : advisedMethods)
1234        {
1235            method.rewriteMethodForAdvice();
1236        }
1237    }
1238
1239    private void interceptFieldAccess(MethodNode methodNode)
1240    {
1241        InsnList insns = methodNode.instructions;
1242
1243        ListIterator it = insns.iterator();
1244
1245        while (it.hasNext())
1246        {
1247            AbstractInsnNode node = (AbstractInsnNode) it.next();
1248
1249            int opcode = node.getOpcode();
1250
1251            if (opcode != GETFIELD && opcode != PUTFIELD)
1252            {
1253                continue;
1254            }
1255
1256            FieldInsnNode fnode = (FieldInsnNode) node;
1257
1258            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
1259
1260            if (instrumentation == null)
1261            {
1262                continue;
1263            }
1264
1265            // Replace the field access node with the appropriate method invocation.
1266
1267            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription));
1268
1269            it.remove();
1270        }
1271    }
1272
1273    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
1274    {
1275        // First look in the local fieldInstrumentations, which contains private field instrumentations
1276        // (as well as non-private ones).
1277
1278        String searchStart = node.owner;
1279
1280        if (searchStart.equals(classNode.name))
1281        {
1282            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
1283
1284            if (result != null)
1285            {
1286                return result;
1287            }
1288
1289            // Slight optimization: start the search in the super-classes' fields, since we've already
1290            // checked this classes fields.
1291
1292            searchStart = classNode.superName;
1293        }
1294
1295        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
1296    }
1297
1298    String getInstanceContextFieldName()
1299    {
1300        if (instanceContextFieldName == null)
1301        {
1302            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
1303
1304            // TODO: We could use a protected field and only initialize
1305            // it once, in the first base class where it is needed, though that raises the possibilities
1306            // of name conflicts (a subclass might introduce a field with a conflicting name).
1307
1308            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
1309                    null, null);
1310
1311            classNode.fields.add(node);
1312
1313            // Extend the constructor to store the context in a field.
1314
1315            constructorBuilder.loadThis().loadArgument(1)
1316                    .putField(className, instanceContextFieldName, InstanceContext.class);
1317        }
1318
1319        return instanceContextFieldName;
1320    }
1321
1322    /**
1323     * Creates a new private final field and initializes its value (using the StaticContext).
1324     */
1325    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
1326                                                     Object injectedFieldValue)
1327    {
1328        String name = makeUnique(fieldNames, suggestedFieldName);
1329
1330        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
1331
1332        classNode.fields.add(field);
1333
1334        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
1335
1336        return name;
1337    }
1338
1339    /**
1340     * Initializes a field from the static context. The injected value is added to the static
1341     * context and the class constructor updated to assign the value from the context (which includes casting and
1342     * possibly unboxing).
1343     */
1344    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
1345    {
1346        int index = staticContext.store(injectedFieldValue);
1347
1348        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
1349        // on primitive longs and doubles, so its just easier to do the loadThis() first
1350        // so its at the right place on the stack for the putField().
1351
1352        constructorBuilder.loadThis();
1353
1354        constructorBuilder.loadArgument(0).loadConstant(index);
1355        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
1356        constructorBuilder.castOrUnbox(fieldType);
1357
1358        constructorBuilder.putField(className, fieldName, fieldType);
1359    }
1360
1361    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
1362    {
1363        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
1364    }
1365
1366    @Override
1367    public PlasticClass getPlasticClass()
1368    {
1369        return this;
1370    }
1371
1372    @Override
1373    public Class<?> getTransformedClass()
1374    {
1375        if (transformedClass == null)
1376            throw new IllegalStateException(String.format(
1377                    "Transformed class %s is not yet available because the transformation is not yet complete.",
1378                    className));
1379
1380        return transformedClass;
1381    }
1382
1383    private boolean isInheritableMethod(MethodNode node)
1384    {
1385        return !Modifier.isPrivate(node.access);
1386    }
1387
1388    @Override
1389    public String getClassName()
1390    {
1391        return className;
1392    }
1393
1394    InstructionBuilderImpl newBuilder(MethodNode mn)
1395    {
1396        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
1397    }
1398
1399    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
1400    {
1401        return new InstructionBuilderImpl(description, mn, nameCache);
1402    }
1403
1404    @Override
1405    public Set<PlasticMethod> introduceInterface(Class interfaceType)
1406    {
1407        check();
1408
1409        assert interfaceType != null;
1410
1411        if (!interfaceType.isInterface())
1412            throw new IllegalArgumentException(String.format(
1413                    "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName()));
1414
1415        String interfaceName = nameCache.toInternalName(interfaceType);
1416
1417        try
1418        {
1419            interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader());
1420        } catch (IOException e)
1421        {
1422            throw new RuntimeException(e);
1423        }
1424
1425        if (!inheritanceData.isInterfaceImplemented(interfaceName))
1426        {
1427            classNode.interfaces.add(interfaceName);
1428            inheritanceData.addInterface(interfaceName);
1429        }
1430
1431        addClassAnnotations(interfaceClassNode);
1432
1433        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
1434
1435        for (Method m : interfaceType.getMethods())
1436        {
1437            MethodDescription description = new MethodDescription(m);
1438
1439            if (!isMethodImplemented(description) && !isDefaultMethod(m))
1440            {
1441                introducedMethods.add(introduceMethod(m));
1442            }
1443        }
1444
1445        interfaceClassNode = null;
1446
1447        return introducedMethods;
1448    }
1449
1450    @Override
1451    public PlasticClass addToString(final String toStringValue)
1452    {
1453        check();
1454
1455        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
1456        {
1457            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
1458            {
1459                @Override
1460                public void doBuild(InstructionBuilder builder)
1461                {
1462                    builder.loadConstant(toStringValue).returnResult();
1463                }
1464            });
1465        }
1466
1467        return this;
1468    }
1469
1470    @Override
1471    public boolean isMethodImplemented(MethodDescription description)
1472    {
1473        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
1474    }
1475
1476    @Override
1477    public boolean isInterfaceImplemented(Class interfaceType)
1478    {
1479        assert interfaceType != null;
1480        assert interfaceType.isInterface();
1481
1482        String interfaceName = nameCache.toInternalName(interfaceType);
1483
1484        return inheritanceData.isInterfaceImplemented(interfaceName);
1485    }
1486
1487    @Override
1488    public String getSuperClassName()
1489    {
1490        return superClassName;
1491    }
1492
1493    @Override
1494    public PlasticClass onConstruct(ConstructorCallback callback)
1495    {
1496        check();
1497
1498        assert callback != null;
1499
1500        constructorCallbacks.add(callback);
1501
1502        return this;
1503    }
1504
1505    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
1506    {
1507        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1508
1509        fieldInstrumentations.write.put(fieldName, fi);
1510
1511        if (!(proxy || privateField))
1512        {
1513            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
1514        }
1515    }
1516
1517    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
1518    {
1519        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1520
1521        fieldInstrumentations.read.put(fieldName, fi);
1522
1523        if (!(proxy || privateField))
1524        {
1525            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
1526        }
1527    }
1528
1529    @Override
1530    public String toString()
1531    {
1532        return String.format("PlasticClassImpl[%s]", className);
1533    }
1534}