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        // TAP5-2582: avoiding adding/delegating the same method more than once
625//        for (Method m : interfaceType.getMethods())
626//        {
627//            introduceMethod(m).delegateTo(field);
628//        }
629        
630        Map<MethodSignature, MethodDescription> map = createMethodSignatureMap(interfaceType);
631        for (MethodSignature methodSignature : map.keySet())
632        {
633            introduceMethod(map.get(methodSignature)).delegateTo(field);
634        }
635        
636        return this;
637    }
638    
639    @Override
640    public PlasticClass proxyInterface(Class interfaceType, PlasticMethod method)
641    {
642        check();
643
644        assert method != null;
645
646        introduceInterface(interfaceType);
647
648        // TAP5-2582: avoiding adding/delegating the same method more than once
649        Map<MethodSignature, MethodDescription> map = createMethodSignatureMap(interfaceType);
650        for (MethodSignature methodSignature : map.keySet())
651        {
652            introduceMethod(map.get(methodSignature)).delegateTo(method);
653        }
654        
655        return this;
656    }
657
658    @Override
659    public ClassInstantiator createInstantiator()
660    {
661        lock();
662
663        addClassAnnotations(implementationClassNode);
664        removeDuplicatedAnnotations(classNode);
665
666        createShimIfNeeded();
667
668        interceptFieldAccess();
669
670        rewriteAdvisedMethods();
671
672        completeConstructor();
673
674        transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
675
676        return createInstantiatorFromClass(transformedClass);
677    }
678
679    private void addClassAnnotations(ClassNode otherClassNode)
680    {
681        // Copy annotations from implementation if available.
682        // Code adapted from ClassNode.accept(), as we just want to copy
683        // the annotations and nothing more.
684        if (otherClassNode != null)
685        {
686
687            int i, n;
688            n = otherClassNode.visibleAnnotations == null ? 0 : otherClassNode.visibleAnnotations.size();
689            for (i = 0; i < n; ++i)
690            {
691                AnnotationNode an = otherClassNode.visibleAnnotations.get(i);
692                an.accept(classNode.visitAnnotation(an.desc, true));
693            }
694            n = otherClassNode.invisibleAnnotations == null ? 0 : otherClassNode.invisibleAnnotations.size();
695            for (i = 0; i < n; ++i)
696            {
697                AnnotationNode an = otherClassNode.invisibleAnnotations.get(i);
698                an.accept(classNode.visitAnnotation(an.desc, false));
699            }
700
701        }
702    }
703
704    private ClassInstantiator createInstantiatorFromClass(Class clazz)
705    {
706        try
707        {
708            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
709
710            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
711        } catch (Exception ex)
712        {
713            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
714                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
715        }
716    }
717
718    private void completeConstructor()
719    {
720        if (originalConstructor != null)
721        {
722            convertOriginalConstructorToMethod();
723        }
724
725        invokeCallbacks();
726
727        constructorBuilder.returnResult();
728
729        classNode.methods.add(newConstructor);
730    }
731
732    private void invokeCallbacks()
733    {
734        for (ConstructorCallback callback : constructorCallbacks)
735        {
736            invokeCallback(callback);
737        }
738    }
739
740    private void invokeCallback(ConstructorCallback callback)
741    {
742        int index = staticContext.store(callback);
743
744        // First, load the callback
745
746        constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
747
748        // Load this and the InstanceContext
749        constructorBuilder.loadThis().loadArgument(1);
750
751        constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
752    }
753
754
755    /**
756     * Convert the original constructor into a private method invoked from the
757     * generated constructor.
758     */
759    private void convertOriginalConstructorToMethod()
760    {
761        String initializerName = makeUnique(methodNames, "initializeInstance");
762
763        int originalAccess = originalConstructor.access;
764
765        originalConstructor.access = ACC_PRIVATE;
766        originalConstructor.name = initializerName;
767
768        stripOutSuperConstructorCall(originalConstructor);
769
770        constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
771
772        // And replace it with a constructor that throws an exception
773
774        MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
775                null);
776
777        newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
778
779        classNode.methods.add(replacementConstructor);
780    }
781
782    private void stripOutSuperConstructorCall(MethodNode cons)
783    {
784        InsnList ins = cons.instructions;
785
786        ListIterator li = ins.iterator();
787
788        // Look for the ALOAD 0 (i.e., push this on the stack)
789        while (li.hasNext())
790        {
791            AbstractInsnNode node = (AbstractInsnNode) li.next();
792
793            if (node.getOpcode() == ALOAD)
794            {
795                VarInsnNode varNode = (VarInsnNode) node;
796
797                assert varNode.var == 0;
798
799                // Remove the ALOAD
800                li.remove();
801                break;
802            }
803        }
804
805        // Look for the call to the super-class, an INVOKESPECIAL
806        while (li.hasNext())
807        {
808            AbstractInsnNode node = (AbstractInsnNode) li.next();
809
810            if (node.getOpcode() == INVOKESPECIAL)
811            {
812                MethodInsnNode mnode = (MethodInsnNode) node;
813
814                assert mnode.owner.equals(classNode.superName);
815                assert mnode.name.equals(CONSTRUCTOR_NAME);
816                assert mnode.desc.equals(cons.desc);
817
818                li.remove();
819                return;
820            }
821        }
822
823        throw new AssertionError("Could not convert constructor to simple method.");
824    }
825
826    @Override
827    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
828    {
829        check();
830
831        List<PlasticField> result = getAllFields();
832
833        Iterator<PlasticField> iterator = result.iterator();
834
835        while (iterator.hasNext())
836        {
837            PlasticField plasticField = iterator.next();
838
839            if (!plasticField.hasAnnotation(annotationType))
840                iterator.remove();
841        }
842
843        return result;
844    }
845
846    @Override
847    public List<PlasticField> getAllFields()
848    {
849        check();
850
851        return new ArrayList<PlasticField>(fields);
852    }
853
854    @Override
855    public List<PlasticField> getUnclaimedFields()
856    {
857        check();
858
859        // Initially null, and set back to null by PlasticField.claim().
860
861        if (unclaimedFields == null)
862        {
863            unclaimedFields = new ArrayList<PlasticField>(fields.size());
864
865            for (PlasticField f : fields)
866            {
867                if (!f.isClaimed())
868                    unclaimedFields.add(f);
869            }
870        }
871
872        return unclaimedFields;
873    }
874
875    @Override
876    public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
877                                                String[] exceptionTypes)
878    {
879        check();
880
881        assert PlasticInternalUtils.isNonBlank(typeName);
882        assert PlasticInternalUtils.isNonBlank(suggestedName);
883
884        String name = makeUnique(methodNames, suggestedName);
885
886        MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
887                exceptionTypes);
888
889        return introduceMethod(description);
890    }
891
892    @Override
893    public PlasticField introduceField(String className, String suggestedName)
894    {
895        check();
896
897        assert PlasticInternalUtils.isNonBlank(className);
898        assert PlasticInternalUtils.isNonBlank(suggestedName);
899
900        String name = makeUnique(fieldNames, suggestedName);
901
902        // No signature and no initial value
903
904        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
905
906        classNode.fields.add(fieldNode);
907
908        fieldNames.add(name);
909
910        PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
911
912        return newField;
913    }
914
915    @Override
916    public PlasticField introduceField(Class fieldType, String suggestedName)
917    {
918        assert fieldType != null;
919
920        return introduceField(nameCache.toTypeName(fieldType), suggestedName);
921    }
922
923    String makeUnique(Set<String> values, String input)
924    {
925        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
926    }
927
928    @Override
929    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
930    {
931        check();
932
933        List<PlasticMethod> result = getMethods();
934        Iterator<PlasticMethod> iterator = result.iterator();
935
936        while (iterator.hasNext())
937        {
938            PlasticMethod method = iterator.next();
939
940            if (!method.hasAnnotation(annotationType))
941                iterator.remove();
942        }
943
944        return result;
945    }
946
947    @Override
948    public List<PlasticMethod> getMethods()
949    {
950        check();
951
952        return new ArrayList<PlasticMethod>(methods);
953    }
954
955    @Override
956    public PlasticMethod introduceMethod(MethodDescription description)
957    {
958        check();
959
960        if (Modifier.isAbstract(description.modifiers))
961        {
962            description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
963        }
964
965        PlasticMethod result = description2method.get(description);
966
967        if (result == null)
968        {
969            result = createNewMethod(description);
970
971            description2method.put(description, result);
972        }
973
974        methodNames.add(description.methodName);
975
976        // Note that is it not necessary to add the new MethodNode to
977        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
978        // ever access instance fields) ... unless the caller invokes changeImplementation().
979
980        return result;
981    }
982
983    @Override
984    public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
985    {
986        check();
987
988        // TODO: optimize this so that a default implementation is not created.
989
990        return introduceMethod(description).changeImplementation(callback);
991    }
992
993    @Override
994    public PlasticMethod introduceMethod(Method method)
995    {
996        check();
997
998        return introduceMethod(new MethodDescription(method));
999    }
1000
1001    void addMethod(MethodNode methodNode)
1002    {
1003        classNode.methods.add(methodNode);
1004
1005        methodNames.add(methodNode.name);
1006
1007        if (isInheritableMethod(methodNode))
1008        {
1009            inheritanceData.addMethod(methodNode.name, methodNode.desc, methodNode.access == 0);
1010        }
1011    }
1012
1013    private PlasticMethod createNewMethod(MethodDescription description)
1014    {
1015        if (Modifier.isStatic(description.modifiers))
1016            throw new IllegalArgumentException(String.format(
1017                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
1018                    description, className));
1019
1020        String desc = nameCache.toDesc(description);
1021
1022        String[] exceptions = new String[description.checkedExceptionTypes.length];
1023        for (int i = 0; i < exceptions.length; i++)
1024        {
1025            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
1026        }
1027
1028        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
1029                description.genericSignature, exceptions);
1030        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
1031
1032        if (!isOverride)
1033        {
1034            addMethodAndParameterAnnotationsFromExistingClass(methodNode, implementationClassNode);
1035            addMethodAndParameterAnnotationsFromExistingClass(methodNode, interfaceClassNode);
1036            removeDuplicatedAnnotations(methodNode);
1037        }
1038
1039        if (isOverride)
1040            createOverrideOfBaseClassImpl(description, methodNode);
1041        else
1042            createNewMethodImpl(description, methodNode);
1043
1044        addMethod(methodNode);
1045
1046        return new PlasticMethodImpl(this, methodNode);
1047    }
1048
1049    private boolean isDefaultMethod(Method method)
1050    {
1051        return method.getDeclaringClass().isInterface() &&
1052                (method.getModifiers() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_ABSTRACT)) == Opcodes.ACC_PUBLIC;
1053    }
1054
1055    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
1056    {
1057        newBuilder(methodDescription, methodNode).returnDefaultValue();
1058    }
1059
1060    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
1061    {
1062        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
1063
1064        builder.loadThis();
1065        builder.loadArguments();
1066        builder.invokeSpecial(superClassName, methodDescription);
1067        builder.returnResult();
1068    }
1069
1070    /**
1071     * Iterates over all non-introduced methods, including the original constructor. For each
1072     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
1073     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
1074     * for the class has been created, as the shim may create methods that contain references to fields that may be
1075     * subject to field access interception.
1076     */
1077    private void interceptFieldAccess()
1078    {
1079        for (MethodNode node : fieldTransformMethods)
1080        {
1081            // Intercept field access inside the method, tracking which access methods
1082            // are actually used by removing them from accessMethods
1083
1084            interceptFieldAccess(node);
1085        }
1086    }
1087
1088    /**
1089     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
1090     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
1091     */
1092    private void createShimIfNeeded()
1093    {
1094        if (shimFields.isEmpty() && shimMethods.isEmpty())
1095            return;
1096
1097        PlasticClassHandleShim shim = createShimInstance();
1098
1099        installShim(shim);
1100    }
1101
1102    public void installShim(PlasticClassHandleShim shim)
1103    {
1104        for (PlasticFieldImpl f : shimFields)
1105        {
1106            f.installShim(shim);
1107        }
1108
1109        for (PlasticMethodImpl m : shimMethods)
1110        {
1111            m.installShim(shim);
1112        }
1113    }
1114
1115    public PlasticClassHandleShim createShimInstance()
1116    {
1117        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
1118
1119        ClassNode shimClassNode = new ClassNode();
1120
1121        shimClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
1122                null);
1123
1124        implementConstructor(shimClassNode);
1125
1126        if (!shimFields.isEmpty())
1127        {
1128            implementShimGet(shimClassNode);
1129            implementShimSet(shimClassNode);
1130        }
1131
1132        if (!shimMethods.isEmpty())
1133        {
1134            implementShimInvoke(shimClassNode);
1135        }
1136
1137        return instantiateShim(shimClassNode);
1138    }
1139
1140    private void implementConstructor(ClassNode shimClassNode)
1141    {
1142        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
1143
1144        InstructionBuilder builder = newBuilder(mn);
1145
1146        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
1147
1148        shimClassNode.methods.add(mn);
1149
1150    }
1151
1152    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
1153    {
1154        try
1155        {
1156            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
1157
1158            return (PlasticClassHandleShim) shimClass.newInstance();
1159        } catch (Exception ex)
1160        {
1161            throw new RuntimeException(
1162                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
1163                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
1164                            PlasticInternalUtils.toMessage(ex)), ex);
1165        }
1166    }
1167
1168    private void implementShimGet(ClassNode shimClassNode)
1169    {
1170        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
1171
1172        InstructionBuilder builder = newBuilder(mn);
1173
1174        // Arg 0 is the target instance
1175        // Arg 1 is the index
1176
1177        builder.loadArgument(0).checkcast(className);
1178        builder.loadArgument(1);
1179
1180        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1181        {
1182            @Override
1183            public void doSwitch(SwitchBlock block)
1184            {
1185                for (PlasticFieldImpl f : shimFields)
1186                {
1187                    f.extendShimGet(block);
1188                }
1189            }
1190        });
1191
1192        shimClassNode.methods.add(mn);
1193    }
1194
1195    private void implementShimSet(ClassNode shimClassNode)
1196    {
1197        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
1198
1199        InstructionBuilder builder = newBuilder(mn);
1200
1201        // Arg 0 is the target instance
1202        // Arg 1 is the index
1203        // Arg 2 is the new value
1204
1205        builder.loadArgument(0).checkcast(className);
1206        builder.loadArgument(2);
1207
1208        builder.loadArgument(1);
1209
1210        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
1211        {
1212            @Override
1213            public void doSwitch(SwitchBlock block)
1214            {
1215                for (PlasticFieldImpl f : shimFields)
1216                {
1217                    f.extendShimSet(block);
1218                }
1219            }
1220        });
1221
1222        builder.returnResult();
1223
1224        shimClassNode.methods.add(mn);
1225    }
1226
1227    private void implementShimInvoke(ClassNode shimClassNode)
1228    {
1229        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
1230                null);
1231
1232        InstructionBuilder builder = newBuilder(mn);
1233
1234        // Arg 0 is the target instance
1235        // Arg 1 is the index
1236        // Arg 2 is the object array of parameters
1237
1238        builder.loadArgument(0).checkcast(className);
1239
1240        builder.loadArgument(1);
1241
1242        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
1243        {
1244            @Override
1245            public void doSwitch(SwitchBlock block)
1246            {
1247                for (PlasticMethodImpl m : shimMethods)
1248                {
1249                    m.extendShimInvoke(block);
1250                }
1251            }
1252        });
1253
1254        shimClassNode.methods.add(mn);
1255    }
1256
1257    private void rewriteAdvisedMethods()
1258    {
1259        for (PlasticMethodImpl method : advisedMethods)
1260        {
1261            method.rewriteMethodForAdvice();
1262        }
1263    }
1264
1265    private void interceptFieldAccess(MethodNode methodNode)
1266    {
1267        InsnList insns = methodNode.instructions;
1268
1269        ListIterator it = insns.iterator();
1270
1271        while (it.hasNext())
1272        {
1273            AbstractInsnNode node = (AbstractInsnNode) it.next();
1274
1275            int opcode = node.getOpcode();
1276
1277            if (opcode != GETFIELD && opcode != PUTFIELD)
1278            {
1279                continue;
1280            }
1281
1282            FieldInsnNode fnode = (FieldInsnNode) node;
1283
1284            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
1285
1286            if (instrumentation == null)
1287            {
1288                continue;
1289            }
1290
1291            // Replace the field access node with the appropriate method invocation.
1292
1293            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription));
1294
1295            it.remove();
1296        }
1297    }
1298
1299    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
1300    {
1301        // First look in the local fieldInstrumentations, which contains private field instrumentations
1302        // (as well as non-private ones).
1303
1304        String searchStart = node.owner;
1305
1306        if (searchStart.equals(classNode.name))
1307        {
1308            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
1309
1310            if (result != null)
1311            {
1312                return result;
1313            }
1314
1315            // Slight optimization: start the search in the super-classes' fields, since we've already
1316            // checked this classes fields.
1317
1318            searchStart = classNode.superName;
1319        }
1320
1321        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
1322    }
1323
1324    String getInstanceContextFieldName()
1325    {
1326        if (instanceContextFieldName == null)
1327        {
1328            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
1329
1330            // TODO: We could use a protected field and only initialize
1331            // it once, in the first base class where it is needed, though that raises the possibilities
1332            // of name conflicts (a subclass might introduce a field with a conflicting name).
1333
1334            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
1335                    null, null);
1336
1337            classNode.fields.add(node);
1338
1339            // Extend the constructor to store the context in a field.
1340
1341            constructorBuilder.loadThis().loadArgument(1)
1342                    .putField(className, instanceContextFieldName, InstanceContext.class);
1343        }
1344
1345        return instanceContextFieldName;
1346    }
1347
1348    /**
1349     * Creates a new private final field and initializes its value (using the StaticContext).
1350     */
1351    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
1352                                                     Object injectedFieldValue)
1353    {
1354        String name = makeUnique(fieldNames, suggestedFieldName);
1355
1356        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
1357
1358        classNode.fields.add(field);
1359
1360        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
1361
1362        return name;
1363    }
1364
1365    /**
1366     * Initializes a field from the static context. The injected value is added to the static
1367     * context and the class constructor updated to assign the value from the context (which includes casting and
1368     * possibly unboxing).
1369     */
1370    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
1371    {
1372        int index = staticContext.store(injectedFieldValue);
1373
1374        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
1375        // on primitive longs and doubles, so its just easier to do the loadThis() first
1376        // so its at the right place on the stack for the putField().
1377
1378        constructorBuilder.loadThis();
1379
1380        constructorBuilder.loadArgument(0).loadConstant(index);
1381        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
1382        constructorBuilder.castOrUnbox(fieldType);
1383
1384        constructorBuilder.putField(className, fieldName, fieldType);
1385    }
1386
1387    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
1388    {
1389        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
1390    }
1391
1392    @Override
1393    public PlasticClass getPlasticClass()
1394    {
1395        return this;
1396    }
1397
1398    @Override
1399    public Class<?> getTransformedClass()
1400    {
1401        if (transformedClass == null)
1402            throw new IllegalStateException(String.format(
1403                    "Transformed class %s is not yet available because the transformation is not yet complete.",
1404                    className));
1405
1406        return transformedClass;
1407    }
1408
1409    private boolean isInheritableMethod(MethodNode node)
1410    {
1411        return !Modifier.isPrivate(node.access);
1412    }
1413
1414    @Override
1415    public String getClassName()
1416    {
1417        return className;
1418    }
1419
1420    InstructionBuilderImpl newBuilder(MethodNode mn)
1421    {
1422        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
1423    }
1424
1425    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
1426    {
1427        return new InstructionBuilderImpl(description, mn, nameCache);
1428    }
1429
1430    @Override
1431    public Set<PlasticMethod> introduceInterface(Class interfaceType)
1432    {
1433        check();
1434
1435        assert interfaceType != null;
1436
1437        if (!interfaceType.isInterface())
1438            throw new IllegalArgumentException(String.format(
1439                    "Class %s is not an interface; only interfaces may be introduced.", interfaceType.getName()));
1440
1441        String interfaceName = nameCache.toInternalName(interfaceType);
1442
1443        try
1444        {
1445            interfaceClassNode = PlasticClassPool.readClassNode(interfaceType.getName(), getClass().getClassLoader());
1446        } catch (IOException e)
1447        {
1448            throw new RuntimeException(e);
1449        }
1450
1451        if (!inheritanceData.isInterfaceImplemented(interfaceName))
1452        {
1453            classNode.interfaces.add(interfaceName);
1454            inheritanceData.addInterface(interfaceName);
1455        }
1456
1457        addClassAnnotations(interfaceClassNode);
1458
1459        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
1460        
1461        Map<MethodSignature, MethodDescription> map = createMethodSignatureMap(interfaceType);
1462        
1463        // for (Method m : interfaceType.getMethods())
1464        for (MethodSignature methodSignature : map.keySet())
1465        {
1466            // MethodDescription description = new MethodDescription(m);
1467            final MethodDescription description = map.get(methodSignature);
1468
1469            if (!isMethodImplemented(description) && !isDefaultMethod(methodSignature.method))
1470            {
1471                // introducedMethods.add(introduceMethod(m));
1472                introducedMethods.add(introduceMethod(description));
1473            }
1474        }
1475
1476        interfaceClassNode = null;
1477
1478        return introducedMethods;
1479    }
1480
1481    private Map<MethodSignature, MethodDescription> createMethodSignatureMap(Class interfaceType) {
1482        // TAP-2582: preprocessing the method list so we don't add duplicated
1483        // methods, something that happens when an interface has superinterfaces
1484        // and they define the same method signature.
1485        // In addition, we collect all the thrown checked exceptions, just in case.
1486        Map<MethodSignature, MethodDescription> map = new HashMap<MethodSignature, MethodDescription>();
1487        for (Method m : interfaceType.getMethods())
1488        {
1489            final MethodSignature methodSignature = new MethodSignature(m);
1490            final MethodDescription newMethodDescription = new MethodDescription(m);
1491            if (!map.containsKey(methodSignature)) 
1492            {
1493                map.put(methodSignature, newMethodDescription);
1494            }
1495            else 
1496            {
1497                if (newMethodDescription.checkedExceptionTypes != null && newMethodDescription.checkedExceptionTypes.length > 0)
1498                {
1499                    final MethodDescription methodDescription = map.get(methodSignature);
1500                    final Set<String> checkedExceptionTypes = new HashSet<String>();
1501                    checkedExceptionTypes.addAll(Arrays.asList(methodDescription.checkedExceptionTypes));
1502                    checkedExceptionTypes.addAll(Arrays.asList(newMethodDescription.checkedExceptionTypes));
1503                    map.put(methodSignature, new MethodDescription(
1504                            methodDescription, 
1505                            checkedExceptionTypes.toArray(new String[checkedExceptionTypes.size()])));
1506                }
1507            }
1508        }
1509        return map;
1510    }
1511    
1512    final private static class MethodSignature implements Comparable<MethodSignature>{
1513        
1514        final private Method method;
1515        final private String name;
1516        final private Class<?>[] parameterTypes;
1517        
1518        public MethodSignature(Method method) {
1519            this.method = method;
1520            this.name = method.getName();
1521            this.parameterTypes = method.getParameterTypes();
1522        }
1523
1524        @Override
1525        public int hashCode() {
1526            final int prime = 31;
1527            int result = 1;
1528            result = prime * result + Arrays.hashCode(parameterTypes);
1529            result = prime * result + ((name == null) ? 0 : name.hashCode());
1530            return result;
1531        }
1532
1533        @Override
1534        public boolean equals(Object obj) {
1535            if (this == obj)
1536                return true;
1537            if (obj == null)
1538                return false;
1539            if (getClass() != obj.getClass())
1540                return false;
1541            MethodSignature other = (MethodSignature) obj;
1542            if (!Arrays.equals(parameterTypes, other.parameterTypes))
1543                return false;
1544            if (name == null) {
1545                if (other.name != null)
1546                    return false;
1547            } else if (!name.equals(other.name))
1548                return false;
1549            return true;
1550        }
1551
1552        @Override
1553        public int compareTo(MethodSignature o) {
1554            return method.getName().compareTo(o.method.getName());
1555        }
1556    }
1557
1558    @Override
1559    public PlasticClass addToString(final String toStringValue)
1560    {
1561        check();
1562
1563        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
1564        {
1565            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
1566            {
1567                @Override
1568                public void doBuild(InstructionBuilder builder)
1569                {
1570                    builder.loadConstant(toStringValue).returnResult();
1571                }
1572            });
1573        }
1574
1575        return this;
1576    }
1577
1578    @Override
1579    public boolean isMethodImplemented(MethodDescription description)
1580    {
1581        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
1582    }
1583
1584    @Override
1585    public boolean isInterfaceImplemented(Class interfaceType)
1586    {
1587        assert interfaceType != null;
1588        assert interfaceType.isInterface();
1589
1590        String interfaceName = nameCache.toInternalName(interfaceType);
1591
1592        return inheritanceData.isInterfaceImplemented(interfaceName);
1593    }
1594
1595    @Override
1596    public String getSuperClassName()
1597    {
1598        return superClassName;
1599    }
1600
1601    @Override
1602    public PlasticClass onConstruct(ConstructorCallback callback)
1603    {
1604        check();
1605
1606        assert callback != null;
1607
1608        constructorCallbacks.add(callback);
1609
1610        return this;
1611    }
1612
1613    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
1614    {
1615        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1616
1617        fieldInstrumentations.write.put(fieldName, fi);
1618
1619        if (!(proxy || privateField))
1620        {
1621            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
1622        }
1623    }
1624
1625    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
1626    {
1627        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1628
1629        fieldInstrumentations.read.put(fieldName, fi);
1630
1631        if (!(proxy || privateField))
1632        {
1633            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
1634        }
1635    }
1636
1637    @Override
1638    public String toString()
1639    {
1640        return String.format("PlasticClassImpl[%s]", className);
1641    }
1642}