001// Licensed under the Apache License, Version 2.0 (the "License");
002// you may not use this file except in compliance with the License.
003// You may obtain a copy of the License at
004//
005// http://www.apache.org/licenses/LICENSE-2.0
006//
007// Unless required by applicable law or agreed to in writing, software
008// distributed under the License is distributed on an "AS IS" BASIS,
009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010// See the License for the specific language governing permissions and
011// limitations under the License.
012
013package org.apache.tapestry5.internal.plastic;
014
015import org.apache.tapestry5.internal.plastic.asm.AnnotationVisitor;
016import org.apache.tapestry5.internal.plastic.asm.Opcodes;
017import org.apache.tapestry5.internal.plastic.asm.Type;
018import org.apache.tapestry5.internal.plastic.asm.tree.*;
019import org.apache.tapestry5.plastic.*;
020
021import java.io.IOException;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.Method;
025import java.lang.reflect.Modifier;
026import java.util.*;
027
028@SuppressWarnings("all")
029public class PlasticClassImpl extends Lockable implements PlasticClass, InternalPlasticClassTransformation, Opcodes
030{
031    private static final String NOTHING_TO_VOID = "()V";
032
033    static final String CONSTRUCTOR_NAME = "<init>";
034
035    private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;";
036
037    private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V";
038
039    private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format(
040            "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class)));
041
042    static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils
043            .toInternalName(AbstractMethodInvocation.class.getName());
044
045    private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type
046            .getInternalName(PlasticClassHandleShim.class);
047
048    static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class);
049
050    private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class);
051
052    private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME);
053
054    private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME,
055            INSTANCE_CONTEXT_INTERNAL_NAME);
056
057    static final Method STATIC_CONTEXT_GET_METHOD = toMethod(StaticContext.class, "get", int.class);
058
059    static final Method COMPUTED_VALUE_GET_METHOD = toMethod(ComputedValue.class, "get", InstanceContext.class);
060
061    private static final Method CONSTRUCTOR_CALLBACK_METHOD = toMethod(ConstructorCallback.class, "onConstruct",
062            Object.class, InstanceContext.class);
063
064    private static String toDesc(String internalName)
065    {
066        return "L" + internalName + ";";
067    }
068
069    private static Method toMethod(Class declaringClass, String methodName, Class... parameterTypes)
070    {
071        return PlasticUtils.getMethod(declaringClass, methodName, parameterTypes);
072    }
073
074    static <T> T safeArrayDeref(T[] array, int index)
075    {
076        if (array == null)
077            return null;
078
079        return array[index];
080    }
081
082    // Now past the inner classes; these are the instance variables of PlasticClassImpl proper:
083
084    final ClassNode classNode;
085
086    final PlasticClassPool pool;
087
088    private final boolean proxy;
089
090    final String className;
091
092    private final String superClassName;
093
094    private final AnnotationAccess annotationAccess;
095
096    // All the non-introduced (and non-constructor) methods, in sorted order
097
098    private final List<PlasticMethodImpl> methods;
099
100    private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<>();
101
102    final Set<String> methodNames = new HashSet<>();
103
104    private final List<ConstructorCallback> constructorCallbacks = PlasticInternalUtils.newList();
105
106    // All non-introduced instance fields
107
108    private final List<PlasticFieldImpl> fields;
109
110    /**
111     * Methods that require special attention inside {@link #createInstantiator()} because they
112     * have method advice.
113     */
114    final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet();
115
116    final NameCache nameCache = new NameCache();
117
118    // This is generated from fields, as necessary
119    List<PlasticField> unclaimedFields;
120
121    private final Set<String> fieldNames = PlasticInternalUtils.newSet();
122
123    final StaticContext staticContext;
124
125    final InheritanceData parentInheritanceData, inheritanceData;
126
127    // MethodNodes in which field transformations should occur; this is most existing and
128    // introduced methods, outside of special access methods.
129
130    final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet();
131
132    // Tracks any methods that the Shim class uses to gain access to fields; used to ensure that
133    // such methods are not optimized away incorrectly.
134    final Set<MethodNode> shimInvokedMethods = PlasticInternalUtils.newSet();
135
136
137    /**
138     * Tracks instrumentations of fields of this class, including private fields which are not published into the
139     * {@link PlasticClassPool}.
140     */
141    private final FieldInstrumentations fieldInstrumentations;
142
143    /**
144     * This normal no-arguments constructor, or null. By the end of the transformation
145     * this will be converted into an ordinary method.
146     */
147    private MethodNode originalConstructor;
148
149    private final MethodNode newConstructor;
150
151    final InstructionBuilder constructorBuilder;
152
153    private String instanceContextFieldName;
154
155    private Class<?> transformedClass;
156
157    // Indexes used to identify fields or methods in the shim
158    int nextFieldIndex = 0;
159
160    int nextMethodIndex = 0;
161
162    // Set of fields that need to contribute to the shim and gain access to it
163
164    final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet();
165
166    // Set of methods that need to contribute to the shim and gain access to it
167
168    final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet();
169
170    final ClassNode implementationClassNode;
171
172    private ClassNode interfaceClassNode;
173
174    /**
175     * @param classNode
176     * @param implementationClassNode
177     * @param pool
178     * @param parentInheritanceData
179     * @param parentStaticContext
180     * @param proxy
181     */
182    public PlasticClassImpl(ClassNode classNode, ClassNode implementationClassNode, PlasticClassPool pool, InheritanceData parentInheritanceData,
183                            StaticContext parentStaticContext, boolean proxy)
184    {
185        this.classNode = classNode;
186        this.pool = pool;
187        this.proxy = proxy;
188        this.implementationClassNode = implementationClassNode;
189
190        staticContext = parentStaticContext.dupe();
191
192        className = PlasticInternalUtils.toClassName(classNode.name);
193        superClassName = PlasticInternalUtils.toClassName(classNode.superName);
194        int lastIndexOfDot = className.lastIndexOf('.');
195
196        String packageName = lastIndexOfDot > -1 ? className.substring(0, lastIndexOfDot) : "";
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(packageName);
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, node.access == 0);
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, node.access == 0);
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<>();
444            final List<AnnotationNode> toBeRemoved = new ArrayList<>();
445            final List<AnnotationNode> toBeIterated;
446
447            if (reverse)
448            {
449                toBeIterated = new ArrayList<>(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<>();
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    {
609        return Modifier.isVolatile(methodNode.access);
610    }
611
612    @Override
613    public PlasticClass proxyInterface(Class interfaceType, PlasticField field)
614    {
615        check();
616
617        assert field != null;
618
619        introduceInterface(interfaceType);
620
621        for (Method m : getUniqueMethods(interfaceType))
622        {
623            final MethodDescription description = new MethodDescription(m);
624            if(Modifier.isStatic(description.modifiers))
625            {
626                continue;
627            }
628            introduceMethod(description).delegateTo(field);
629        }
630
631        return this;
632    }
633
634    @Override
635    public ClassInstantiator createInstantiator()
636    {
637        lock();
638
639        addClassAnnotations(implementationClassNode);
640        removeDuplicatedAnnotations(classNode);
641
642        createShimIfNeeded();
643
644        interceptFieldAccess();
645
646        rewriteAdvisedMethods();
647
648        completeConstructor();
649
650        transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
651
652        return createInstantiatorFromClass(transformedClass);
653    }
654
655    private void addClassAnnotations(ClassNode otherClassNode)
656    {
657        // Copy annotations from implementation if available.
658        // Code adapted from ClassNode.accept(), as we just want to copy
659        // the annotations and nothing more.
660        if (otherClassNode != null)
661        {
662
663            int i, n;
664            n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size();
665            for (i = 0; i < n; ++i)
666            {
667                AnnotationNode an = otherClassNode.visibleAnnotations.get(i);
668                an.accept(classNode.visitAnnotation(an.desc, true));
669            }
670            n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size();
671            for (i = 0; i < n; ++i)
672            {
673                AnnotationNode an = otherClassNode.invisibleAnnotations.get(i);
674                an.accept(classNode.visitAnnotation(an.desc, false));
675            }
676
677        }
678    }
679
680    private ClassInstantiator createInstantiatorFromClass(Class clazz)
681    {
682        try
683        {
684            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
685
686            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
687        } catch (Exception ex)
688        {
689            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
690                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
691        }
692    }
693
694    private void completeConstructor()
695    {
696        if (originalConstructor != null)
697        {
698            convertOriginalConstructorToMethod();
699        }
700
701        invokeCallbacks();
702
703        constructorBuilder.returnResult();
704
705        classNode.methods.add(newConstructor);
706    }
707
708    private void invokeCallbacks()
709    {
710        for (ConstructorCallback callback : constructorCallbacks)
711        {
712            invokeCallback(callback);
713        }
714    }
715
716    private void invokeCallback(ConstructorCallback callback)
717    {
718        int index = staticContext.store(callback);
719
720        // First, load the callback
721
722        constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
723
724        // Load this and the InstanceContext
725        constructorBuilder.loadThis().loadArgument(1);
726
727        constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
728    }
729
730
731    /**
732     * Convert the original constructor into a private method invoked from the
733     * generated constructor.
734     */
735    private void convertOriginalConstructorToMethod()
736    {
737        String initializerName = makeUnique(methodNames, "initializeInstance");
738
739        int originalAccess = originalConstructor.access;
740
741        originalConstructor.access = ACC_PRIVATE;
742        originalConstructor.name = initializerName;
743
744        stripOutSuperConstructorCall(originalConstructor);
745
746        constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
747
748        // And replace it with a constructor that throws an exception
749
750        MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
751                null);
752
753        newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
754
755        classNode.methods.add(replacementConstructor);
756    }
757
758    private void stripOutSuperConstructorCall(MethodNode cons)
759    {
760        InsnList ins = cons.instructions;
761
762        ListIterator li = ins.iterator();
763
764        // Look for the ALOAD 0 (i.e., push this on the stack)
765        while (li.hasNext())
766        {
767            AbstractInsnNode node = (AbstractInsnNode) li.next();
768
769            if (node.getOpcode() == ALOAD)
770            {
771                VarInsnNode varNode = (VarInsnNode) node;
772
773                assert varNode.var == 0;
774
775                // Remove the ALOAD
776                li.remove();
777                break;
778            }
779        }
780
781        // Look for the call to the super-class, an INVOKESPECIAL
782        while (li.hasNext())
783        {
784            AbstractInsnNode node = (AbstractInsnNode) li.next();
785
786            if (node.getOpcode() == INVOKESPECIAL)
787            {
788                MethodInsnNode mnode = (MethodInsnNode) node;
789
790                assert mnode.owner.equals(classNode.superName);
791                assert mnode.name.equals(CONSTRUCTOR_NAME);
792                assert mnode.desc.equals(cons.desc);
793
794                li.remove();
795                return;
796            }
797        }
798
799        throw new AssertionError("Could not convert constructor to simple method.");
800    }
801
802    @Override
803    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
804    {
805        check();
806
807        List<PlasticField> result = getAllFields();
808
809        Iterator<PlasticField> iterator = result.iterator();
810
811        while (iterator.hasNext())
812        {
813            PlasticField plasticField = iterator.next();
814
815            if (!plasticField.hasAnnotation(annotationType))
816                iterator.remove();
817        }
818
819        return result;
820    }
821
822    @Override
823    public List<PlasticField> getAllFields()
824    {
825        check();
826
827        return new ArrayList<>(fields);
828    }
829
830    @Override
831    public List<PlasticField> getUnclaimedFields()
832    {
833        check();
834
835        // Initially null, and set back to null by PlasticField.claim().
836
837        if (unclaimedFields == null)
838        {
839            unclaimedFields = new ArrayList<>(fields.size());
840
841            for (PlasticField f : fields)
842            {
843                if (!f.isClaimed())
844                    unclaimedFields.add(f);
845            }
846        }
847
848        return unclaimedFields;
849    }
850
851    @Override
852    public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
853                                                String[] exceptionTypes)
854    {
855        check();
856
857        assert PlasticInternalUtils.isNonBlank(typeName);
858        assert PlasticInternalUtils.isNonBlank(suggestedName);
859
860        String name = makeUnique(methodNames, suggestedName);
861
862        MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
863                exceptionTypes);
864
865        return introduceMethod(description);
866    }
867
868    @Override
869    public PlasticField introduceField(String className, String suggestedName)
870    {
871        check();
872
873        assert PlasticInternalUtils.isNonBlank(className);
874        assert PlasticInternalUtils.isNonBlank(suggestedName);
875
876        String name = makeUnique(fieldNames, suggestedName);
877
878        // No signature and no initial value
879
880        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
881
882        classNode.fields.add(fieldNode);
883
884        fieldNames.add(name);
885
886        PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
887
888        return newField;
889    }
890
891    @Override
892    public PlasticField introduceField(Class fieldType, String suggestedName)
893    {
894        assert fieldType != null;
895
896        return introduceField(nameCache.toTypeName(fieldType), suggestedName);
897    }
898
899    String makeUnique(Set<String> values, String input)
900    {
901        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
902    }
903
904    @Override
905    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
906    {
907        check();
908
909        List<PlasticMethod> result = getMethods();
910
911        result.removeIf(method -> !method.hasAnnotation(annotationType));
912
913        return result;
914    }
915
916    @Override
917    public List<PlasticMethod> getMethods()
918    {
919        check();
920
921        return new ArrayList<>(methods);
922    }
923
924    @Override
925    public PlasticMethod introduceMethod(MethodDescription description)
926    {
927        check();
928
929        if (Modifier.isAbstract(description.modifiers))
930        {
931            description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
932        }
933
934        PlasticMethod result = description2method.get(description);
935
936        if (result == null)
937        {
938            result = createNewMethod(description);
939
940            description2method.put(description, result);
941        }
942
943        methodNames.add(description.methodName);
944
945        // Note that is it not necessary to add the new MethodNode to
946        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
947        // ever access instance fields) ... unless the caller invokes changeImplementation().
948
949        return result;
950    }
951
952    @Override
953    public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
954    {
955        check();
956
957        // TODO: optimize this so that a default implementation is not created.
958
959        return introduceMethod(description).changeImplementation(callback);
960    }
961
962    @Override
963    public PlasticMethod introduceMethod(Method method)
964    {
965        check();
966        return introduceMethod(new MethodDescription(method));
967    }
968
969    void addMethod(MethodNode methodNode)
970    {
971        classNode.methods.add(methodNode);
972
973        methodNames.add(methodNode.name);
974
975        if (isInheritableMethod(methodNode))
976        {
977            inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0);
978        }
979    }
980
981    private PlasticMethod createNewMethod(MethodDescription description)
982    {
983        if (Modifier.isStatic(description.modifiers))
984            throw new IllegalArgumentException(String.format(
985                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
986                    description, className));
987
988        String desc = nameCache.toDesc(description);
989
990        String[] exceptions = new String[description.checkedExceptionTypes.length];
991        for (int i = 0; i < exceptions.length; i++)
992        {
993            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
994        }
995
996        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
997                description.genericSignature, exceptions);
998        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
999
1000        if (!isOverride)
1001        {
1002            addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode);
1003            addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode);
1004            removeDuplicatedAnnotations(methodNode);
1005        }
1006
1007        if (isOverride)
1008            createOverrideOfBaseClassImpl(description, methodNode);
1009        else
1010            createNewMethodImpl(description, methodNode);
1011
1012        addMethod(methodNode);
1013
1014        return new PlasticMethodImpl(this, methodNode);
1015    }
1016
1017    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
1018    {
1019        newBuilder(methodDescription, methodNode).returnDefaultValue();
1020    }
1021
1022    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
1023    {
1024        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
1025
1026        builder.loadThis();
1027        builder.loadArguments();
1028        builder.invokeSpecial(superClassName, methodDescription);
1029        builder.returnResult();
1030    }
1031
1032    /**
1033     * Iterates over all non-introduced methods, including the original constructor. For each
1034     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
1035     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
1036     * for the class has been created, as the shim may create methods that contain references to fields that may be
1037     * subject to field access interception.
1038     */
1039    private void interceptFieldAccess()
1040    {
1041        for (MethodNode node : fieldTransformMethods)
1042        {
1043            // Intercept field access inside the method, tracking which access methods
1044            // are actually used by removing them from accessMethods
1045
1046            interceptFieldAccess(node);
1047        }
1048    }
1049
1050    /**
1051     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
1052     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
1053     */
1054    private void createShimIfNeeded()
1055    {
1056        if (shimFields.isEmpty() && shimMethods.isEmpty())
1057            return;
1058
1059        PlasticClassHandleShim shim = createShimInstance();
1060
1061        installShim(shim);
1062    }
1063
1064    public void installShim(PlasticClassHandleShim shim)
1065    {
1066        for (PlasticFieldImpl f : shimFields)
1067        {
1068            f.installShim(shim);
1069        }
1070
1071        for (PlasticMethodImpl m : shimMethods)
1072        {
1073            m.installShim(shim);
1074        }
1075    }
1076
1077    public PlasticClassHandleShim createShimInstance()
1078    {
1079        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
1080
1081        ClassNode shimClassNode = new ClassNode();
1082
1083        shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
1084                null);
1085
1086        implementConstructor(shimClassNode);
1087
1088        if (!shimFields.isEmpty())
1089        {
1090            implementShimGet(shimClassNode);
1091            implementShimSet(shimClassNode);
1092        }
1093
1094        if (!shimMethods.isEmpty())
1095        {
1096            implementShimInvoke(shimClassNode);
1097        }
1098
1099        return instantiateShim(shimClassNode);
1100    }
1101
1102    private void implementConstructor(ClassNode shimClassNode)
1103    {
1104        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
1105
1106        InstructionBuilder builder = newBuilder(mn);
1107
1108        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
1109
1110        shimClassNode.methods.add(mn);
1111
1112    }
1113
1114    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
1115    {
1116        try
1117        {
1118            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
1119
1120            return (PlasticClassHandleShim) shimClass.newInstance();
1121        } catch (Exception ex)
1122        {
1123            throw new RuntimeException(
1124                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
1125                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
1126                            PlasticInternalUtils.toMessage(ex)), ex);
1127        }
1128    }
1129
1130    private void implementShimGet(ClassNode shimClassNode)
1131    {
1132        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
1133
1134        InstructionBuilder builder = newBuilder(mn);
1135
1136        // Arg 0 is the target instance
1137        // Arg 1 is the index
1138
1139        builder.loadArgument(0).checkcast(className);
1140        builder.loadArgument(1);
1141
1142        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1143        {
1144            @Override
1145            public void doSwitch(SwitchBlock block)
1146            {
1147                for (PlasticFieldImpl f : shimFields)
1148                {
1149                    f.extendShimGet(block);
1150                }
1151            }
1152        });
1153
1154        shimClassNode.methods.add(mn);
1155    }
1156
1157    private void implementShimSet(ClassNode shimClassNode)
1158    {
1159        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
1160
1161        InstructionBuilder builder = newBuilder(mn);
1162
1163        // Arg 0 is the target instance
1164        // Arg 1 is the index
1165        // Arg 2 is the new value
1166
1167        builder.loadArgument(0).checkcast(className);
1168        builder.loadArgument(2);
1169
1170        builder.loadArgument(1);
1171
1172        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1173        {
1174            @Override
1175            public void doSwitch(SwitchBlock block)
1176            {
1177                for (PlasticFieldImpl f : shimFields)
1178                {
1179                    f.extendShimSet(block);
1180                }
1181            }
1182        });
1183
1184        builder.returnResult();
1185
1186        shimClassNode.methods.add(mn);
1187    }
1188
1189    private void implementShimInvoke(ClassNode shimClassNode)
1190    {
1191        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
1192                null);
1193
1194        InstructionBuilder builder = newBuilder(mn);
1195
1196        // Arg 0 is the target instance
1197        // Arg 1 is the index
1198        // Arg 2 is the object array of parameters
1199
1200        builder.loadArgument(0).checkcast(className);
1201
1202        builder.loadArgument(1);
1203
1204        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
1205        {
1206            @Override
1207            public void doSwitch(SwitchBlock block)
1208            {
1209                for (PlasticMethodImpl m : shimMethods)
1210                {
1211                    m.extendShimInvoke(block);
1212                }
1213            }
1214        });
1215
1216        shimClassNode.methods.add(mn);
1217    }
1218
1219    private void rewriteAdvisedMethods()
1220    {
1221        for (PlasticMethodImpl method : advisedMethods)
1222        {
1223            method.rewriteMethodForAdvice();
1224        }
1225    }
1226
1227    private void interceptFieldAccess(MethodNode methodNode)
1228    {
1229        InsnList insns = methodNode.instructions;
1230
1231        ListIterator<AbstractInsnNode> it = insns.iterator();
1232
1233        while (it.hasNext())
1234        {
1235            AbstractInsnNode node = it.next();
1236
1237            int opcode = node.getOpcode();
1238
1239            if (opcode != GETFIELD && opcode != PUTFIELD)
1240            {
1241                continue;
1242            }
1243
1244            FieldInsnNode fnode = (FieldInsnNode) node;
1245
1246            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
1247
1248            if (instrumentation == null)
1249            {
1250                continue;
1251            }
1252
1253            // Replace the field access node with the appropriate method invocation.
1254
1255            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription, false));
1256
1257            it.remove();
1258        }
1259    }
1260
1261    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
1262    {
1263        // First look in the local fieldInstrumentations, which contains private field instrumentations
1264        // (as well as non-private ones).
1265
1266        String searchStart = node.owner;
1267
1268        if (searchStart.equals(classNode.name))
1269        {
1270            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
1271
1272            if (result != null)
1273            {
1274                return result;
1275            }
1276
1277            // Slight optimization: start the search in the super-classes' fields, since we've already
1278            // checked this classes fields.
1279
1280            searchStart = classNode.superName;
1281        }
1282
1283        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
1284    }
1285
1286    String getInstanceContextFieldName()
1287    {
1288        if (instanceContextFieldName == null)
1289        {
1290            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
1291
1292            // TODO: We could use a protected field and only initialize
1293            // it once, in the first base class where it is needed, though that raises the possibilities
1294            // of name conflicts (a subclass might introduce a field with a conflicting name).
1295
1296            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
1297                    null, null);
1298
1299            classNode.fields.add(node);
1300
1301            // Extend the constructor to store the context in a field.
1302
1303            constructorBuilder.loadThis().loadArgument(1)
1304                    .putField(className, instanceContextFieldName, InstanceContext.class);
1305        }
1306
1307        return instanceContextFieldName;
1308    }
1309
1310    /**
1311     * Creates a new private final field and initializes its value (using the StaticContext).
1312     */
1313    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
1314                                                     Object injectedFieldValue)
1315    {
1316        String name = makeUnique(fieldNames, suggestedFieldName);
1317
1318        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
1319
1320        classNode.fields.add(field);
1321
1322        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
1323
1324        return name;
1325    }
1326
1327    /**
1328     * Initializes a field from the static context. The injected value is added to the static
1329     * context and the class constructor updated to assign the value from the context (which includes casting and
1330     * possibly unboxing).
1331     */
1332    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
1333    {
1334        int index = staticContext.store(injectedFieldValue);
1335
1336        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
1337        // on primitive longs and doubles, so its just easier to do the loadThis() first
1338        // so its at the right place on the stack for the putField().
1339
1340        constructorBuilder.loadThis();
1341
1342        constructorBuilder.loadArgument(0).loadConstant(index);
1343        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
1344        constructorBuilder.castOrUnbox(fieldType);
1345
1346        constructorBuilder.putField(className, fieldName, fieldType);
1347    }
1348
1349    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
1350    {
1351        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
1352    }
1353
1354    @Override
1355    public PlasticClass getPlasticClass()
1356    {
1357        return this;
1358    }
1359
1360    @Override
1361    public Class<?> getTransformedClass()
1362    {
1363        if (transformedClass == null)
1364            throw new IllegalStateException(String.format(
1365                    "Transformed class %s is not yet available because the transformation is not yet complete.",
1366                    className));
1367
1368        return transformedClass;
1369    }
1370
1371    private boolean isInheritableMethod(MethodNode node)
1372    {
1373        return !Modifier.isPrivate(node.access);
1374    }
1375
1376    @Override
1377    public String getClassName()
1378    {
1379        return className;
1380    }
1381
1382    InstructionBuilderImpl newBuilder(MethodNode mn)
1383    {
1384        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
1385    }
1386
1387    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
1388    {
1389        return new InstructionBuilderImpl(description, mn, nameCache);
1390    }
1391
1392    public Set<PlasticMethod> introduceInterface(Class interfaceType)
1393    {
1394        return introduceInterface(interfaceType, null);
1395    }
1396    
1397    private Set<PlasticMethod> introduceInterface(Class interfaceType, PlasticMethod method)
1398    {
1399        check();
1400
1401        assert interfaceType != null;
1402
1403        if (!interfaceType.isInterface())
1404            throw new IllegalArgumentException(String.format(
1405                    "Class %s is not an interface; only interfaces may be introduced.", interfaceType.getName()));
1406
1407        String interfaceName = nameCache.toInternalName(interfaceType);
1408
1409        try
1410        {
1411            interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader());
1412        } catch (IOException e)
1413        {
1414            throw new RuntimeException(e);
1415        }
1416
1417        if (!inheritanceData.isInterfaceImplemented(interfaceName))
1418        {
1419            classNode.interfaces.add(interfaceName);
1420            inheritanceData.addInterface(interfaceName);
1421        }
1422
1423        addClassAnnotations(interfaceClassNode);
1424
1425        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
1426        Set<Method> alreadyIntroducedMethods = new HashSet<>();
1427
1428        Method[] sortedMethods = interfaceType.getMethods();
1429        Arrays.sort(sortedMethods, METHOD_COMPARATOR);
1430        for (Method m : sortedMethods)
1431        {
1432            MethodDescription description = new MethodDescription(m);
1433
1434            if (!isMethodImplemented(description) && 
1435                    !(m.isDefault() && m.isBridge()) && 
1436                    !Modifier.isStatic(description.modifiers) && 
1437                    !contains(alreadyIntroducedMethods, m))
1438            {
1439                PlasticMethod introducedMethod = introduceMethod(m);
1440                introducedMethods.add(introducedMethod);
1441                if (method != null) {
1442                    introducedMethod.delegateTo(method);
1443                }
1444                alreadyIntroducedMethods.add(m);
1445            }
1446        }
1447
1448        interfaceClassNode = null;
1449
1450        return introducedMethods;
1451    }
1452    
1453    @Override
1454    public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method)
1455    {
1456        check();
1457        assert method != null;
1458
1459        introduceInterface(interfaceType, method);
1460        
1461        return this;
1462    }
1463
1464    private boolean contains(Set<Method> alreadyIntroducedMethods, Method m) {
1465        boolean contains = false;
1466        for (Method method : alreadyIntroducedMethods) 
1467        {
1468            if (METHOD_COMPARATOR.compare(method, m) == 0)
1469            {
1470                contains = true;
1471                break;
1472            }
1473        }
1474        return false;
1475    }
1476
1477    private Map<MethodSignature, MethodDescription> createMethodSignatureMap(Class interfaceType) {
1478        // TAP-2582: preprocessing the method list so we don't add duplicated
1479        // methods, something that happens when an interface has superinterfaces
1480        // and they define the same method signature.
1481        // In addition, we collect all the thrown checked exceptions, just in case.
1482        Map<MethodSignature, MethodDescription> map = new HashMap<>();
1483        for (Method m : interfaceType.getMethods())
1484        {
1485            final MethodSignature methodSignature = new MethodSignature(m);
1486            final MethodDescription newMethodDescription = new MethodDescription(m);
1487            if (!map.containsKey(methodSignature))
1488            {
1489                map.put(methodSignature, newMethodDescription);
1490            }
1491            else
1492            {
1493                if (newMethodDescription.checkedExceptionTypes != null && newMethodDescription.checkedExceptionTypes.length > 0)
1494                {
1495                    final MethodDescription methodDescription = map.get(methodSignature);
1496                        final Set<String> checkedExceptionTypes = new HashSet<>();
1497                        checkedExceptionTypes.addAll(Arrays.asList(methodDescription.checkedExceptionTypes));
1498                        checkedExceptionTypes.addAll(Arrays.asList(newMethodDescription.checkedExceptionTypes));
1499                        map.put(methodSignature, new MethodDescription(
1500                            methodDescription,
1501                            checkedExceptionTypes.toArray(new String[checkedExceptionTypes.size()])));
1502                }
1503            }
1504        }
1505        return map;
1506    }
1507
1508    final private static class MethodSignature implements Comparable<MethodSignature> {
1509        final private Method method;
1510        final private String name;
1511        final private Class<?>[] parameterTypes;
1512
1513        public MethodSignature(Method method) {
1514            this.method = method;
1515            this.name = method.getName();
1516            this.parameterTypes = method.getParameterTypes();
1517        }
1518
1519        @Override
1520        public int hashCode() {
1521            final int prime = 31;
1522            int result = 1;
1523            result = prime * result + Arrays.hashCode(parameterTypes);
1524            result = prime * result + ((name == null) ? 0 : name.hashCode());
1525            return result;
1526        }
1527
1528        @Override
1529        public boolean equals(Object obj) {
1530            if (this == obj) return true;
1531            if (obj == null) return false;
1532            if (getClass() != obj.getClass()) return false;
1533
1534            MethodSignature other = (MethodSignature) obj;
1535            if (!Arrays.equals(parameterTypes, other.parameterTypes)) return false;
1536
1537            return name == null ? other.name == null : name.equals(other.name);
1538        }
1539
1540        @Override
1541        public int compareTo(MethodSignature o) {
1542            return method.getName().compareTo(o.method.getName());
1543        }
1544    }
1545
1546    @Override
1547    public PlasticClass addToString(final String toStringValue)
1548    {
1549        check();
1550
1551        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
1552        {
1553            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
1554            {
1555                @Override
1556                public void doBuild(InstructionBuilder builder)
1557                {
1558                    builder.loadConstant(toStringValue).returnResult();
1559                }
1560            });
1561        }
1562
1563        return this;
1564    }
1565
1566    @Override
1567    public boolean isMethodImplemented(MethodDescription description)
1568    {
1569        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
1570    }
1571
1572    @Override
1573    public boolean isInterfaceImplemented(Class interfaceType)
1574    {
1575        assert interfaceType != null;
1576        assert interfaceType.isInterface();
1577
1578        String interfaceName = nameCache.toInternalName(interfaceType);
1579
1580        return inheritanceData.isInterfaceImplemented(interfaceName);
1581    }
1582
1583    @Override
1584    public String getSuperClassName()
1585    {
1586        return superClassName;
1587    }
1588
1589    @Override
1590    public PlasticClass onConstruct(ConstructorCallback callback)
1591    {
1592        check();
1593
1594        assert callback != null;
1595
1596        constructorCallbacks.add(callback);
1597
1598        return this;
1599    }
1600
1601    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
1602    {
1603        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1604
1605        fieldInstrumentations.write.put(fieldName, fi);
1606
1607        if (!proxy)
1608        {
1609            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
1610        }
1611    }
1612
1613    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
1614    {
1615        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1616
1617        fieldInstrumentations.read.put(fieldName, fi);
1618
1619        if (!proxy)
1620        {
1621            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
1622        }
1623    }
1624    
1625    final private MethodComparator METHOD_COMPARATOR = new MethodComparator();
1626    
1627    final private class MethodComparator implements Comparator<Method> 
1628    {
1629
1630        @Override
1631        public int compare(Method o1, Method o2) 
1632        {
1633            
1634            int comparison = comparison = o1.getName().compareTo(o2.getName());
1635            
1636            if (comparison == 0) 
1637            {
1638                comparison = o1.getParameterTypes().length - o2.getParameterTypes().length;
1639            }
1640            
1641            if (comparison == 0) 
1642            {
1643                final int count = o1.getParameterTypes().length;
1644                for (int i = 0; i < count; i++) 
1645                {
1646                    Class p1 = o1.getParameterTypes()[i];
1647                    Class p2 = o1.getParameterTypes()[i];
1648                    if (!p1.equals(p2)) 
1649                    {
1650                        comparison = p1.getName().compareTo(p2.getName());
1651                        break;
1652                    }
1653                }
1654            }
1655            
1656            if (comparison == 0)
1657            {
1658                // Trying to get methods lower in the interface hierarchy sorted before the same methods
1659                // higher in the hierarchy
1660                Class<?> declaringClass1 = o1.getDeclaringClass();
1661                Class<?> declaringClass2 = o2.getDeclaringClass();
1662                if (declaringClass1.isInterface() && declaringClass2.isInterface() && 
1663                        !declaringClass1.equals(declaringClass2)) {
1664                    if (declaringClass1.isAssignableFrom(declaringClass2))
1665                    {
1666                        comparison = -1;
1667                    }
1668                    else if (declaringClass1.isAssignableFrom(declaringClass2))
1669                    {
1670                        comparison = 1;
1671                    }
1672                }
1673            }
1674                    
1675            return comparison;
1676        }
1677    }
1678    
1679    private List<Method> getUniqueMethods(Class interfaceType) 
1680    {
1681        final List<Method> unique = new ArrayList<>(Arrays.asList(interfaceType.getMethods()));
1682        Collections.sort(unique, METHOD_COMPARATOR);
1683        Method last = null;
1684        Iterator<Method> iterator = unique.iterator();
1685        while (iterator.hasNext()) 
1686        {
1687            Method m = iterator.next();
1688            if (last != null && METHOD_COMPARATOR.compare(m, last) == 0)
1689            {
1690                last = m;
1691                iterator.remove();
1692            }
1693        }
1694        return unique;
1695    }
1696
1697    @Override
1698    public String toString()
1699    {
1700        return String.format("PlasticClassImpl[%s]", className);
1701    }
1702}