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