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