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