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 : getUniqueMethods(interfaceType))
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        return introduceMethod(new MethodDescription(method));
972    }
973
974    void addMethod(MethodNode methodNode)
975    {
976        classNode.methods.add(methodNode);
977
978        methodNames.add(methodNode.name);
979
980        if (isInheritableMethod(methodNode))
981        {
982            inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0);
983        }
984    }
985
986    private PlasticMethod createNewMethod(MethodDescription description)
987    {
988        if (Modifier.isStatic(description.modifiers))
989            throw new IllegalArgumentException(String.format(
990                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
991                    description, className));
992
993        String desc = nameCache.toDesc(description);
994
995        String[] exceptions = new String[description.checkedExceptionTypes.length];
996        for (int i = 0; i < exceptions.length; i++)
997        {
998            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
999        }
1000
1001        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
1002                description.genericSignature, exceptions);
1003        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
1004
1005        if (!isOverride)
1006        {
1007            addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode);
1008            addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode);
1009            removeDuplicatedAnnotations(methodNode);
1010        }
1011
1012        if (isOverride)
1013            createOverrideOfBaseClassImpl(description, methodNode);
1014        else
1015            createNewMethodImpl(description, methodNode);
1016
1017        addMethod(methodNode);
1018
1019        return new PlasticMethodImpl(this, methodNode);
1020    }
1021
1022    private boolean isDefaultMethod(Method method)
1023    {
1024        return method.getDeclaringClass().isInterface() &&
1025                (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC;
1026    }
1027
1028    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
1029    {
1030        newBuilder(methodDescription, methodNode).returnDefaultValue();
1031    }
1032
1033    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
1034    {
1035        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
1036
1037        builder.loadThis();
1038        builder.loadArguments();
1039        builder.invokeSpecial(superClassName, methodDescription);
1040        builder.returnResult();
1041    }
1042
1043    /**
1044     * Iterates over all non-introduced methods, including the original constructor. For each
1045     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
1046     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
1047     * for the class has been created, as the shim may create methods that contain references to fields that may be
1048     * subject to field access interception.
1049     */
1050    private void interceptFieldAccess()
1051    {
1052        for (MethodNode node : fieldTransformMethods)
1053        {
1054            // Intercept field access inside the method, tracking which access methods
1055            // are actually used by removing them from accessMethods
1056
1057            interceptFieldAccess(node);
1058        }
1059    }
1060
1061    /**
1062     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
1063     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
1064     */
1065    private void createShimIfNeeded()
1066    {
1067        if (shimFields.isEmpty() && shimMethods.isEmpty())
1068            return;
1069
1070        PlasticClassHandleShim shim = createShimInstance();
1071
1072        installShim(shim);
1073    }
1074
1075    public void installShim(PlasticClassHandleShim shim)
1076    {
1077        for (PlasticFieldImpl f : shimFields)
1078        {
1079            f.installShim(shim);
1080        }
1081
1082        for (PlasticMethodImpl m : shimMethods)
1083        {
1084            m.installShim(shim);
1085        }
1086    }
1087
1088    public PlasticClassHandleShim createShimInstance()
1089    {
1090        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
1091
1092        ClassNode shimClassNode = new ClassNode();
1093
1094        shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
1095                null);
1096
1097        implementConstructor(shimClassNode);
1098
1099        if (!shimFields.isEmpty())
1100        {
1101            implementShimGet(shimClassNode);
1102            implementShimSet(shimClassNode);
1103        }
1104
1105        if (!shimMethods.isEmpty())
1106        {
1107            implementShimInvoke(shimClassNode);
1108        }
1109
1110        return instantiateShim(shimClassNode);
1111    }
1112
1113    private void implementConstructor(ClassNode shimClassNode)
1114    {
1115        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
1116
1117        InstructionBuilder builder = newBuilder(mn);
1118
1119        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
1120
1121        shimClassNode.methods.add(mn);
1122
1123    }
1124
1125    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
1126    {
1127        try
1128        {
1129            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
1130
1131            return (PlasticClassHandleShim) shimClass.newInstance();
1132        } catch (Exception ex)
1133        {
1134            throw new RuntimeException(
1135                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
1136                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
1137                            PlasticInternalUtils.toMessage(ex)), ex);
1138        }
1139    }
1140
1141    private void implementShimGet(ClassNode shimClassNode)
1142    {
1143        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
1144
1145        InstructionBuilder builder = newBuilder(mn);
1146
1147        // Arg 0 is the target instance
1148        // Arg 1 is the index
1149
1150        builder.loadArgument(0).checkcast(className);
1151        builder.loadArgument(1);
1152
1153        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1154        {
1155            @Override
1156            public void doSwitch(SwitchBlock block)
1157            {
1158                for (PlasticFieldImpl f : shimFields)
1159                {
1160                    f.extendShimGet(block);
1161                }
1162            }
1163        });
1164
1165        shimClassNode.methods.add(mn);
1166    }
1167
1168    private void implementShimSet(ClassNode shimClassNode)
1169    {
1170        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
1171
1172        InstructionBuilder builder = newBuilder(mn);
1173
1174        // Arg 0 is the target instance
1175        // Arg 1 is the index
1176        // Arg 2 is the new value
1177
1178        builder.loadArgument(0).checkcast(className);
1179        builder.loadArgument(2);
1180
1181        builder.loadArgument(1);
1182
1183        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1184        {
1185            @Override
1186            public void doSwitch(SwitchBlock block)
1187            {
1188                for (PlasticFieldImpl f : shimFields)
1189                {
1190                    f.extendShimSet(block);
1191                }
1192            }
1193        });
1194
1195        builder.returnResult();
1196
1197        shimClassNode.methods.add(mn);
1198    }
1199
1200    private void implementShimInvoke(ClassNode shimClassNode)
1201    {
1202        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
1203                null);
1204
1205        InstructionBuilder builder = newBuilder(mn);
1206
1207        // Arg 0 is the target instance
1208        // Arg 1 is the index
1209        // Arg 2 is the object array of parameters
1210
1211        builder.loadArgument(0).checkcast(className);
1212
1213        builder.loadArgument(1);
1214
1215        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
1216        {
1217            @Override
1218            public void doSwitch(SwitchBlock block)
1219            {
1220                for (PlasticMethodImpl m : shimMethods)
1221                {
1222                    m.extendShimInvoke(block);
1223                }
1224            }
1225        });
1226
1227        shimClassNode.methods.add(mn);
1228    }
1229
1230    private void rewriteAdvisedMethods()
1231    {
1232        for (PlasticMethodImpl method : advisedMethods)
1233        {
1234            method.rewriteMethodForAdvice();
1235        }
1236    }
1237
1238    private void interceptFieldAccess(MethodNode methodNode)
1239    {
1240        InsnList insns = methodNode.instructions;
1241
1242        ListIterator it = insns.iterator();
1243
1244        while (it.hasNext())
1245        {
1246            AbstractInsnNode node = (AbstractInsnNode) it.next();
1247
1248            int opcode = node.getOpcode();
1249
1250            if (opcode != GETFIELD && opcode != PUTFIELD)
1251            {
1252                continue;
1253            }
1254
1255            FieldInsnNode fnode = (FieldInsnNode) node;
1256
1257            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
1258
1259            if (instrumentation == null)
1260            {
1261                continue;
1262            }
1263
1264            // Replace the field access node with the appropriate method invocation.
1265
1266            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription));
1267
1268            it.remove();
1269        }
1270    }
1271
1272    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
1273    {
1274        // First look in the local fieldInstrumentations, which contains private field instrumentations
1275        // (as well as non-private ones).
1276
1277        String searchStart = node.owner;
1278
1279        if (searchStart.equals(classNode.name))
1280        {
1281            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
1282
1283            if (result != null)
1284            {
1285                return result;
1286            }
1287
1288            // Slight optimization: start the search in the super-classes' fields, since we've already
1289            // checked this classes fields.
1290
1291            searchStart = classNode.superName;
1292        }
1293
1294        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
1295    }
1296
1297    String getInstanceContextFieldName()
1298    {
1299        if (instanceContextFieldName == null)
1300        {
1301            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
1302
1303            // TODO: We could use a protected field and only initialize
1304            // it once, in the first base class where it is needed, though that raises the possibilities
1305            // of name conflicts (a subclass might introduce a field with a conflicting name).
1306
1307            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
1308                    null, null);
1309
1310            classNode.fields.add(node);
1311
1312            // Extend the constructor to store the context in a field.
1313
1314            constructorBuilder.loadThis().loadArgument(1)
1315                    .putField(className, instanceContextFieldName, InstanceContext.class);
1316        }
1317
1318        return instanceContextFieldName;
1319    }
1320
1321    /**
1322     * Creates a new private final field and initializes its value (using the StaticContext).
1323     */
1324    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
1325                                                     Object injectedFieldValue)
1326    {
1327        String name = makeUnique(fieldNames, suggestedFieldName);
1328
1329        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
1330
1331        classNode.fields.add(field);
1332
1333        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
1334
1335        return name;
1336    }
1337
1338    /**
1339     * Initializes a field from the static context. The injected value is added to the static
1340     * context and the class constructor updated to assign the value from the context (which includes casting and
1341     * possibly unboxing).
1342     */
1343    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
1344    {
1345        int index = staticContext.store(injectedFieldValue);
1346
1347        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
1348        // on primitive longs and doubles, so its just easier to do the loadThis() first
1349        // so its at the right place on the stack for the putField().
1350
1351        constructorBuilder.loadThis();
1352
1353        constructorBuilder.loadArgument(0).loadConstant(index);
1354        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
1355        constructorBuilder.castOrUnbox(fieldType);
1356
1357        constructorBuilder.putField(className, fieldName, fieldType);
1358    }
1359
1360    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
1361    {
1362        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
1363    }
1364
1365    @Override
1366    public PlasticClass getPlasticClass()
1367    {
1368        return this;
1369    }
1370
1371    @Override
1372    public Class<?> getTransformedClass()
1373    {
1374        if (transformedClass == null)
1375            throw new IllegalStateException(String.format(
1376                    "Transformed class %s is not yet available because the transformation is not yet complete.",
1377                    className));
1378
1379        return transformedClass;
1380    }
1381
1382    private boolean isInheritableMethod(MethodNode node)
1383    {
1384        return !Modifier.isPrivate(node.access);
1385    }
1386
1387    @Override
1388    public String getClassName()
1389    {
1390        return className;
1391    }
1392
1393    InstructionBuilderImpl newBuilder(MethodNode mn)
1394    {
1395        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
1396    }
1397
1398    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
1399    {
1400        return new InstructionBuilderImpl(description, mn, nameCache);
1401    }
1402
1403    public Set<PlasticMethod> introduceInterface(Class interfaceType)
1404    {
1405        return introduceInterface(interfaceType, null);
1406    }
1407    
1408    private Set<PlasticMethod> introduceInterface(Class interfaceType, PlasticMethod method)
1409    {
1410        check();
1411
1412        assert interfaceType != null;
1413
1414        if (!interfaceType.isInterface())
1415            throw new IllegalArgumentException(String.format(
1416                    "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName()));
1417
1418        String interfaceName = nameCache.toInternalName(interfaceType);
1419
1420        try
1421        {
1422            interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader());
1423        } catch (IOException e)
1424        {
1425            throw new RuntimeException(e);
1426        }
1427
1428        if (!inheritanceData.isInterfaceImplemented(interfaceName))
1429        {
1430            classNode.interfaces.add(interfaceName);
1431            inheritanceData.addInterface(interfaceName);
1432        }
1433
1434        addClassAnnotations(interfaceClassNode);
1435
1436        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
1437        Set<Method> alreadyIntroducedMethods = new HashSet<Method>();
1438
1439        Method[] sortedMethods = interfaceType.getMethods();
1440        Arrays.sort(sortedMethods, METHOD_COMPARATOR);
1441        for (Method m : sortedMethods)
1442        {
1443            MethodDescription description = new MethodDescription(m);
1444
1445            if (!isMethodImplemented(description) && !isDefaultMethod(m) && !contains(alreadyIntroducedMethods, m))
1446            {
1447                PlasticMethod introducedMethod = introduceMethod(m);
1448                introducedMethods.add(introducedMethod);
1449                if (method != null) {
1450                    introducedMethod.delegateTo(method);
1451                }
1452                alreadyIntroducedMethods.add(m);
1453            }
1454        }
1455
1456        interfaceClassNode = null;
1457
1458        return introducedMethods;
1459    }
1460    
1461    @Override
1462    public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method)
1463    {
1464        check();
1465        assert method != null;
1466
1467        introduceInterface(interfaceType, method);
1468        
1469        return this;
1470    }
1471
1472    private boolean contains(Set<Method> alreadyIntroducedMethods, Method m) {
1473        boolean contains = false;
1474        for (Method method : alreadyIntroducedMethods) 
1475        {
1476            if (METHOD_COMPARATOR.compare(method, m) == 0)
1477            {
1478                contains = true;
1479                break;
1480            }
1481        }
1482        return false;
1483    }
1484
1485    @Override
1486    public PlasticClass addToString(final String toStringValue)
1487    {
1488        check();
1489
1490        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
1491        {
1492            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
1493            {
1494                @Override
1495                public void doBuild(InstructionBuilder builder)
1496                {
1497                    builder.loadConstant(toStringValue).returnResult();
1498                }
1499            });
1500        }
1501
1502        return this;
1503    }
1504
1505    @Override
1506    public boolean isMethodImplemented(MethodDescription description)
1507    {
1508        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
1509    }
1510
1511    @Override
1512    public boolean isInterfaceImplemented(Class interfaceType)
1513    {
1514        assert interfaceType != null;
1515        assert interfaceType.isInterface();
1516
1517        String interfaceName = nameCache.toInternalName(interfaceType);
1518
1519        return inheritanceData.isInterfaceImplemented(interfaceName);
1520    }
1521
1522    @Override
1523    public String getSuperClassName()
1524    {
1525        return superClassName;
1526    }
1527
1528    @Override
1529    public PlasticClass onConstruct(ConstructorCallback callback)
1530    {
1531        check();
1532
1533        assert callback != null;
1534
1535        constructorCallbacks.add(callback);
1536
1537        return this;
1538    }
1539
1540    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
1541    {
1542        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1543
1544        fieldInstrumentations.write.put(fieldName, fi);
1545
1546        if (!(proxy || privateField))
1547        {
1548            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
1549        }
1550    }
1551
1552    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
1553    {
1554        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1555
1556        fieldInstrumentations.read.put(fieldName, fi);
1557
1558        if (!(proxy || privateField))
1559        {
1560            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
1561        }
1562    }
1563    
1564    final private MethodComparator METHOD_COMPARATOR = new MethodComparator();
1565    
1566    final private class MethodComparator implements Comparator<Method> 
1567    {
1568
1569        @Override
1570        public int compare(Method o1, Method o2) 
1571        {
1572            
1573            int comparison = o1.getName().compareTo(o2.getName());
1574            
1575            if (comparison == 0) 
1576            {
1577                comparison = o1.getParameterTypes().length - o2.getParameterTypes().length;
1578            }
1579            
1580            if (comparison == 0) 
1581            {
1582                final int count = o1.getParameterTypes().length;
1583                for (int i = 0; i < count; i++) 
1584                {
1585                    Class p1 = o1.getParameterTypes()[i];
1586                    Class p2 = o1.getParameterTypes()[i];
1587                    if (!p1.equals(p2)) 
1588                    {
1589                        comparison = p1.getName().compareTo(p2.getName());
1590                        break;
1591                    }
1592                }
1593            }
1594            return comparison;
1595        }
1596    }
1597    
1598    private List<Method> getUniqueMethods(Class interfaceType) 
1599    {
1600        final List<Method> unique = new ArrayList<Method>(Arrays.asList(interfaceType.getMethods()));
1601        Collections.sort(unique, METHOD_COMPARATOR);
1602        Method last = null;
1603        Iterator<Method> iterator = unique.iterator();
1604        while (iterator.hasNext()) 
1605        {
1606            Method m = iterator.next();
1607            if (last != null && METHOD_COMPARATOR.compare(m, last) == 0)
1608            {
1609                last = m;
1610                iterator.remove();
1611            }
1612        }
1613        return unique;
1614    }
1615
1616    @Override
1617    public String toString()
1618    {
1619        return String.format("PlasticClassImpl[%s]", className);
1620    }
1621}