001// Copyright 2011, 2012 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.plastic;
016
017import org.apache.tapestry5.internal.plastic.asm.Opcodes;
018import org.apache.tapestry5.internal.plastic.asm.Type;
019import org.apache.tapestry5.internal.plastic.asm.tree.*;
020import org.apache.tapestry5.plastic.*;
021
022import java.lang.annotation.Annotation;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.Method;
025import java.lang.reflect.Modifier;
026import java.util.*;
027
028@SuppressWarnings("all")
029public class PlasticClassImpl extends Lockable implements PlasticClass, InternalPlasticClassTransformation, Opcodes
030{
031    private static final String NOTHING_TO_VOID = "()V";
032
033    static final String CONSTRUCTOR_NAME = "<init>";
034
035    private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;";
036
037    private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V";
038
039    private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format(
040            "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class)));
041
042    static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils
043            .toInternalName(AbstractMethodInvocation.class.getName());
044
045    private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type
046            .getInternalName(PlasticClassHandleShim.class);
047
048    static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class);
049
050    private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class);
051
052    private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME);
053
054    private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME,
055            INSTANCE_CONTEXT_INTERNAL_NAME);
056
057    static final Method STATIC_CONTEXT_GET_METHOD = toMethod(StaticContext.class, "get", int.class);
058
059    static final Method COMPUTED_VALUE_GET_METHOD = toMethod(ComputedValue.class, "get", InstanceContext.class);
060
061    private static final Method CONSTRUCTOR_CALLBACK_METHOD = toMethod(ConstructorCallback.class, "onConstruct",
062            Object.class, InstanceContext.class);
063
064    private static String toDesc(String internalName)
065    {
066        return "L" + internalName + ";";
067    }
068
069    private static Method toMethod(Class declaringClass, String methodName, Class... parameterTypes)
070    {
071        return PlasticUtils.getMethod(declaringClass, methodName, parameterTypes);
072    }
073
074    static <T> T safeArrayDeref(T[] array, int index)
075    {
076        if (array == null)
077            return null;
078
079        return array[index];
080    }
081
082    // Now past the inner classes; these are the instance variables of PlasticClassImpl proper:
083
084    final ClassNode classNode;
085
086    final PlasticClassPool pool;
087
088    private final boolean proxy;
089
090    final String className;
091
092    private final String superClassName;
093
094    private final AnnotationAccess annotationAccess;
095
096    // All the non-introduced (and non-constructor) methods, in sorted order
097
098    private final List<PlasticMethodImpl> methods;
099
100    private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<MethodDescription, PlasticMethod>();
101
102    final Set<String> methodNames = new HashSet<String>();
103
104    private final List<ConstructorCallback> constructorCallbacks = PlasticInternalUtils.newList();
105
106    // All non-introduced instance fields
107
108    private final List<PlasticFieldImpl> fields;
109
110    /**
111     * Methods that require special attention inside {@link #createInstantiator()} because they
112     * have method advice.
113     */
114    final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet();
115
116    final NameCache nameCache = new NameCache();
117
118    // This is generated from fields, as necessary
119    List<PlasticField> unclaimedFields;
120
121    private final Set<String> fieldNames = PlasticInternalUtils.newSet();
122
123    final StaticContext staticContext;
124
125    final InheritanceData parentInheritanceData, inheritanceData;
126
127    // MethodNodes in which field transformations should occur; this is most existing and
128    // introduced methods, outside of special access methods.
129
130    final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet();
131
132    // Tracks any methods that the Shim class uses to gain access to fields; used to ensure that
133    // such methods are not optimized away incorrectly.
134    final Set<MethodNode> shimInvokedMethods = PlasticInternalUtils.newSet();
135
136
137    /**
138     * Tracks instrumentations of fields of this class, including private fields which are not published into the
139     * {@link PlasticClassPool}.
140     */
141    private final FieldInstrumentations fieldInstrumentations;
142
143    /**
144     * This normal no-arguments constructor, or null. By the end of the transformation
145     * this will be converted into an ordinary method.
146     */
147    private MethodNode originalConstructor;
148
149    private final MethodNode newConstructor;
150
151    final InstructionBuilder constructorBuilder;
152
153    private String instanceContextFieldName;
154
155    private Class<?> transformedClass;
156
157    // Indexes used to identify fields or methods in the shim
158    int nextFieldIndex = 0;
159
160    int nextMethodIndex = 0;
161
162    // Set of fields that need to contribute to the shim and gain access to it
163
164    final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet();
165
166    // Set of methods that need to contribute to the shim and gain access to it
167
168    final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet();
169
170    /**
171     * @param classNode
172     * @param pool
173     * @param parentInheritanceData
174     * @param parentStaticContext
175     * @param proxy
176     */
177    public PlasticClassImpl(ClassNode classNode, PlasticClassPool pool, InheritanceData parentInheritanceData,
178                            StaticContext parentStaticContext, boolean proxy)
179    {
180        this.classNode = classNode;
181        this.pool = pool;
182        this.proxy = proxy;
183
184        staticContext = parentStaticContext.dupe();
185
186        className = PlasticInternalUtils.toClassName(classNode.name);
187        superClassName = PlasticInternalUtils.toClassName(classNode.superName);
188
189        fieldInstrumentations = new FieldInstrumentations(classNode.superName);
190
191        annotationAccess = new DelegatingAnnotationAccess(pool.createAnnotationAccess(classNode.visibleAnnotations),
192                pool.createAnnotationAccess(superClassName));
193
194        this.parentInheritanceData = parentInheritanceData;
195        inheritanceData = parentInheritanceData.createChild(className);
196
197        for (String interfaceName : classNode.interfaces)
198        {
199            inheritanceData.addInterface(interfaceName);
200        }
201
202        methods = new ArrayList(classNode.methods.size());
203
204        String invalidConstructorMessage = invalidConstructorMessage();
205
206        for (MethodNode node : classNode.methods)
207        {
208            if (node.name.equals(CONSTRUCTOR_NAME))
209            {
210                if (node.desc.equals(NOTHING_TO_VOID))
211                {
212                    originalConstructor = node;
213                    fieldTransformMethods.add(node);
214                } else
215                {
216                    node.instructions.clear();
217
218                    newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage);
219                }
220
221                continue;
222            }
223
224            /**
225             * Static methods are not visible to the main API methods, but they must still be transformed,
226             * in case they directly access fields. In addition, track their names to avoid collisions.
227             */
228            if (Modifier.isStatic(node.access))
229            {
230                if (!Modifier.isPrivate(node.access))
231                {
232                    inheritanceData.addMethod(node.name, node.desc);
233                }
234
235                methodNames.add(node.name);
236
237                fieldTransformMethods.add(node);
238
239                continue;
240            }
241
242            if (!Modifier.isAbstract(node.access))
243            {
244                fieldTransformMethods.add(node);
245            }
246
247            PlasticMethodImpl pmi = new PlasticMethodImpl(this, node);
248
249            methods.add(pmi);
250            description2method.put(pmi.getDescription(), pmi);
251
252            if (isInheritableMethod(node))
253            {
254                inheritanceData.addMethod(node.name, node.desc);
255            }
256
257            methodNames.add(node.name);
258        }
259
260        methodNames.addAll(parentInheritanceData.methodNames());
261
262        Collections.sort(methods);
263
264        fields = new ArrayList(classNode.fields.size());
265
266        for (FieldNode node : classNode.fields)
267        {
268            fieldNames.add(node.name);
269
270            // Ignore static fields.
271
272            if (Modifier.isStatic(node.access))
273                continue;
274
275            // When we instrument the field such that it must be private, we'll get an exception.
276
277            fields.add(new PlasticFieldImpl(this, node));
278        }
279
280        Collections.sort(fields);
281
282        // TODO: Make the output class's constructor protected, and create a shim class to instantiate it
283        // efficiently (without reflection).
284        newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null);
285        constructorBuilder = newBuilder(newConstructor);
286
287        // Start by calling the super-class no args constructor
288
289        if (parentInheritanceData.isTransformed())
290        {
291            // If the parent is transformed, our first step is always to invoke its constructor.
292
293            constructorBuilder.loadThis().loadArgument(0).loadArgument(1);
294            constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(),
295                    InstanceContext.class.getName());
296        } else
297        {
298            // Assumes the base class includes a visible constructor that takes no arguments.
299            // TODO: Do a proper check for this case and throw a meaningful exception
300            // if not present.
301
302            constructorBuilder.loadThis().invokeConstructor(superClassName);
303        }
304
305        // During the transformation, we'll be adding code to the constructor to pull values
306        // out of the static or instance context and assign them to fields.
307
308        // Later on, we'll add the RETURN opcode
309    }
310
311    private String invalidConstructorMessage()
312    {
313        return String.format("Class %s has been transformed and may not be directly instantiated.", className);
314    }
315
316    public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
317    {
318        check();
319
320        return annotationAccess.hasAnnotation(annotationType);
321    }
322
323    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
324    {
325        check();
326
327        return annotationAccess.getAnnotation(annotationType);
328    }
329
330    public PlasticClass proxyInterface(Class interfaceType, PlasticField field)
331    {
332        check();
333
334        assert field != null;
335
336        introduceInterface(interfaceType);
337
338        for (Method m : interfaceType.getMethods())
339        {
340            introduceMethod(m).delegateTo(field);
341        }
342
343        return this;
344    }
345
346    public ClassInstantiator createInstantiator()
347    {
348        lock();
349
350        createShimIfNeeded();
351
352        interceptFieldAccess();
353
354        rewriteAdvisedMethods();
355
356        completeConstructor();
357
358        transformedClass = pool.realizeTransformedClass(classNode, inheritanceData, staticContext);
359
360        return createInstantiatorFromClass(transformedClass);
361    }
362
363    private ClassInstantiator createInstantiatorFromClass(Class clazz)
364    {
365        try
366        {
367            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
368
369            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
370        } catch (Exception ex)
371        {
372            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
373                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
374        }
375    }
376
377    private void completeConstructor()
378    {
379        if (originalConstructor != null)
380        {
381            convertOriginalConstructorToMethod();
382        }
383
384        invokeCallbacks();
385
386        constructorBuilder.returnResult();
387
388        classNode.methods.add(newConstructor);
389    }
390
391    private void invokeCallbacks()
392    {
393        for (ConstructorCallback callback : constructorCallbacks)
394        {
395            invokeCallback(callback);
396        }
397    }
398
399    private void invokeCallback(ConstructorCallback callback)
400    {
401        int index = staticContext.store(callback);
402
403        // First, load the callback
404
405        constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
406
407        // Load this and the InstanceContext
408        constructorBuilder.loadThis().loadArgument(1);
409
410        constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
411    }
412
413
414    /**
415     * Convert the original constructor into a private method invoked from the
416     * generated constructor.
417     */
418    private void convertOriginalConstructorToMethod()
419    {
420        String initializerName = makeUnique(methodNames, "initializeInstance");
421
422        int originalAccess = originalConstructor.access;
423
424        originalConstructor.access = ACC_PRIVATE;
425        originalConstructor.name = initializerName;
426
427        stripOutSuperConstructorCall(originalConstructor);
428
429        constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
430
431        // And replace it with a constructor that throws an exception
432
433        MethodNode replacementConstructor = new MethodNode(originalAccess, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
434                null);
435
436        newBuilder(replacementConstructor).throwException(IllegalStateException.class, invalidConstructorMessage());
437
438        classNode.methods.add(replacementConstructor);
439    }
440
441    private void stripOutSuperConstructorCall(MethodNode cons)
442    {
443        InsnList ins = cons.instructions;
444
445        ListIterator li = ins.iterator();
446
447        // Look for the ALOAD 0 (i.e., push this on the stack)
448        while (li.hasNext())
449        {
450            AbstractInsnNode node = (AbstractInsnNode) li.next();
451
452            if (node.getOpcode() == ALOAD)
453            {
454                VarInsnNode varNode = (VarInsnNode) node;
455
456                assert varNode.var == 0;
457
458                // Remove the ALOAD
459                li.remove();
460                break;
461            }
462        }
463
464        // Look for the call to the super-class, an INVOKESPECIAL
465        while (li.hasNext())
466        {
467            AbstractInsnNode node = (AbstractInsnNode) li.next();
468
469            if (node.getOpcode() == INVOKESPECIAL)
470            {
471                MethodInsnNode mnode = (MethodInsnNode) node;
472
473                assert mnode.owner.equals(classNode.superName);
474                assert mnode.name.equals(CONSTRUCTOR_NAME);
475                assert mnode.desc.equals(cons.desc);
476
477                li.remove();
478                return;
479            }
480        }
481
482        throw new AssertionError("Could not convert constructor to simple method.");
483    }
484
485    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
486    {
487        check();
488
489        List<PlasticField> result = getAllFields();
490
491        Iterator<PlasticField> iterator = result.iterator();
492
493        while (iterator.hasNext())
494        {
495            PlasticField plasticField = iterator.next();
496
497            if (!plasticField.hasAnnotation(annotationType))
498                iterator.remove();
499        }
500
501        return result;
502    }
503
504    public List<PlasticField> getAllFields()
505    {
506        check();
507
508        return new ArrayList<PlasticField>(fields);
509    }
510
511    public List<PlasticField> getUnclaimedFields()
512    {
513        check();
514
515        // Initially null, and set back to null by PlasticField.claim().
516
517        if (unclaimedFields == null)
518        {
519            unclaimedFields = new ArrayList<PlasticField>(fields.size());
520
521            for (PlasticField f : fields)
522            {
523                if (!f.isClaimed())
524                    unclaimedFields.add(f);
525            }
526        }
527
528        return unclaimedFields;
529    }
530
531    public PlasticMethod introducePrivateMethod(String typeName, String suggestedName, String[] argumentTypes,
532                                                String[] exceptionTypes)
533    {
534        check();
535
536        assert PlasticInternalUtils.isNonBlank(typeName);
537        assert PlasticInternalUtils.isNonBlank(suggestedName);
538
539        String name = makeUnique(methodNames, suggestedName);
540
541        MethodDescription description = new MethodDescription(Modifier.PRIVATE, typeName, name, argumentTypes, null,
542                exceptionTypes);
543
544        return introduceMethod(description);
545    }
546
547    public PlasticField introduceField(String className, String suggestedName)
548    {
549        check();
550
551        assert PlasticInternalUtils.isNonBlank(className);
552        assert PlasticInternalUtils.isNonBlank(suggestedName);
553
554        String name = makeUnique(fieldNames, suggestedName);
555
556        // No signature and no initial value
557
558        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
559
560        classNode.fields.add(fieldNode);
561
562        fieldNames.add(name);
563
564        PlasticFieldImpl newField = new PlasticFieldImpl(this, fieldNode);
565
566        return newField;
567    }
568
569    public PlasticField introduceField(Class fieldType, String suggestedName)
570    {
571        assert fieldType != null;
572
573        return introduceField(nameCache.toTypeName(fieldType), suggestedName);
574    }
575
576    String makeUnique(Set<String> values, String input)
577    {
578        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
579    }
580
581    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
582    {
583        check();
584
585        List<PlasticMethod> result = getMethods();
586        Iterator<PlasticMethod> iterator = result.iterator();
587
588        while (iterator.hasNext())
589        {
590            PlasticMethod method = iterator.next();
591
592            if (!method.hasAnnotation(annotationType))
593                iterator.remove();
594        }
595
596        return result;
597    }
598
599    public List<PlasticMethod> getMethods()
600    {
601        check();
602
603        return new ArrayList<PlasticMethod>(methods);
604    }
605
606    public PlasticMethod introduceMethod(MethodDescription description)
607    {
608        check();
609
610        if (Modifier.isAbstract(description.modifiers))
611        {
612            description = description.withModifiers(description.modifiers & ~ACC_ABSTRACT);
613        }
614
615        PlasticMethod result = description2method.get(description);
616
617        if (result == null)
618        {
619            result = createNewMethod(description);
620
621            description2method.put(description, result);
622        }
623
624        methodNames.add(description.methodName);
625
626        // Note that is it not necessary to add the new MethodNode to
627        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
628        // ever access instance fields) ... unless the caller invokes changeImplementation().
629
630        return result;
631    }
632
633    public PlasticMethod introduceMethod(MethodDescription description, InstructionBuilderCallback callback)
634    {
635        check();
636
637        // TODO: optimize this so that a default implementation is not created.
638
639        return introduceMethod(description).changeImplementation(callback);
640    }
641
642    public PlasticMethod introduceMethod(Method method)
643    {
644        check();
645
646        return introduceMethod(new MethodDescription(method));
647    }
648
649    void addMethod(MethodNode methodNode)
650    {
651        classNode.methods.add(methodNode);
652
653        methodNames.add(methodNode.name);
654
655        if (!Modifier.isPrivate(methodNode.access))
656            inheritanceData.addMethod(methodNode.name, methodNode.desc);
657    }
658
659    private PlasticMethod createNewMethod(MethodDescription description)
660    {
661        if (Modifier.isStatic(description.modifiers))
662            throw new IllegalArgumentException(String.format(
663                    "Unable to introduce method '%s' into class %s: introduced methods may not be static.",
664                    description, className));
665
666        String desc = nameCache.toDesc(description);
667
668        String[] exceptions = new String[description.checkedExceptionTypes.length];
669        for (int i = 0; i < exceptions.length; i++)
670        {
671            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
672        }
673
674        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc,
675                description.genericSignature, exceptions);
676        boolean isOverride = inheritanceData.isImplemented(methodNode.name, desc);
677
678        if (isOverride)
679            createOverrideOfBaseClassImpl(description, methodNode);
680        else
681            createNewMethodImpl(description, methodNode);
682
683        addMethod(methodNode);
684
685        return new PlasticMethodImpl(this, methodNode);
686    }
687
688    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
689    {
690        newBuilder(methodDescription, methodNode).returnDefaultValue();
691    }
692
693    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
694    {
695        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
696
697        builder.loadThis();
698        builder.loadArguments();
699        builder.invokeSpecial(superClassName, methodDescription);
700        builder.returnResult();
701    }
702
703    /**
704     * Iterates over all non-introduced methods, including the original constructor. For each
705     * method, the bytecode is scanned for field reads and writes. When a match is found against an intercepted field,
706     * the operation is replaced with a method invocation. This is invoked only after the {@link PlasticClassHandleShim}
707     * for the class has been created, as the shim may create methods that contain references to fields that may be
708     * subject to field access interception.
709     */
710    private void interceptFieldAccess()
711    {
712        for (MethodNode node : fieldTransformMethods)
713        {
714            // Intercept field access inside the method, tracking which access methods
715            // are actually used by removing them from accessMethods
716
717            interceptFieldAccess(node);
718        }
719    }
720
721    /**
722     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
723     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
724     */
725    private void createShimIfNeeded()
726    {
727        if (shimFields.isEmpty() && shimMethods.isEmpty())
728            return;
729
730        PlasticClassHandleShim shim = createShimInstance();
731
732        installShim(shim);
733    }
734
735    public void installShim(PlasticClassHandleShim shim)
736    {
737        for (PlasticFieldImpl f : shimFields)
738        {
739            f.installShim(shim);
740        }
741
742        for (PlasticMethodImpl m : shimMethods)
743        {
744            m.installShim(shim);
745        }
746    }
747
748    public PlasticClassHandleShim createShimInstance()
749    {
750        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
751
752        ClassNode shimClassNode = new ClassNode();
753
754        shimClassNode.visit(V1_5, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
755                null);
756
757        implementConstructor(shimClassNode);
758
759        if (!shimFields.isEmpty())
760        {
761            implementShimGet(shimClassNode);
762            implementShimSet(shimClassNode);
763        }
764
765        if (!shimMethods.isEmpty())
766        {
767            implementShimInvoke(shimClassNode);
768        }
769
770        return instantiateShim(shimClassNode);
771    }
772
773    private void implementConstructor(ClassNode shimClassNode)
774    {
775        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
776
777        InstructionBuilder builder = newBuilder(mn);
778
779        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
780
781        shimClassNode.methods.add(mn);
782
783    }
784
785    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
786    {
787        try
788        {
789            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
790
791            return (PlasticClassHandleShim) shimClass.newInstance();
792        } catch (Exception ex)
793        {
794            throw new RuntimeException(
795                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
796                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
797                            PlasticInternalUtils.toMessage(ex)), ex);
798        }
799    }
800
801    private void implementShimGet(ClassNode shimClassNode)
802    {
803        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
804
805        InstructionBuilder builder = newBuilder(mn);
806
807        // Arg 0 is the target instance
808        // Arg 1 is the index
809
810        builder.loadArgument(0).checkcast(className);
811        builder.loadArgument(1);
812
813        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
814        {
815            public void doSwitch(SwitchBlock block)
816            {
817                for (PlasticFieldImpl f : shimFields)
818                {
819                    f.extendShimGet(block);
820                }
821            }
822        });
823
824        shimClassNode.methods.add(mn);
825    }
826
827    private void implementShimSet(ClassNode shimClassNode)
828    {
829        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
830
831        InstructionBuilder builder = newBuilder(mn);
832
833        // Arg 0 is the target instance
834        // Arg 1 is the index
835        // Arg 2 is the new value
836
837        builder.loadArgument(0).checkcast(className);
838        builder.loadArgument(2);
839
840        builder.loadArgument(1);
841
842        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
843        {
844            public void doSwitch(SwitchBlock block)
845            {
846                for (PlasticFieldImpl f : shimFields)
847                {
848                    f.extendShimSet(block);
849                }
850            }
851        });
852
853        builder.returnResult();
854
855        shimClassNode.methods.add(mn);
856    }
857
858    private void implementShimInvoke(ClassNode shimClassNode)
859    {
860        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
861                null);
862
863        InstructionBuilder builder = newBuilder(mn);
864
865        // Arg 0 is the target instance
866        // Arg 1 is the index
867        // Arg 2 is the object array of parameters
868
869        builder.loadArgument(0).checkcast(className);
870
871        builder.loadArgument(1);
872
873        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
874        {
875            public void doSwitch(SwitchBlock block)
876            {
877                for (PlasticMethodImpl m : shimMethods)
878                {
879                    m.extendShimInvoke(block);
880                }
881            }
882        });
883
884        shimClassNode.methods.add(mn);
885    }
886
887    private void rewriteAdvisedMethods()
888    {
889        for (PlasticMethodImpl method : advisedMethods)
890        {
891            method.rewriteMethodForAdvice();
892        }
893    }
894
895    private void interceptFieldAccess(MethodNode methodNode)
896    {
897        InsnList insns = methodNode.instructions;
898
899        ListIterator it = insns.iterator();
900
901        while (it.hasNext())
902        {
903            AbstractInsnNode node = (AbstractInsnNode) it.next();
904
905            int opcode = node.getOpcode();
906
907            if (opcode != GETFIELD && opcode != PUTFIELD)
908            {
909                continue;
910            }
911
912            FieldInsnNode fnode = (FieldInsnNode) node;
913
914            FieldInstrumentation instrumentation = findFieldNodeInstrumentation(fnode, opcode == GETFIELD);
915
916            if (instrumentation == null)
917            {
918                continue;
919            }
920
921            // Replace the field access node with the appropriate method invocation.
922
923            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, instrumentation.methodName, instrumentation.methodDescription));
924
925            it.remove();
926        }
927    }
928
929    private FieldInstrumentation findFieldNodeInstrumentation(FieldInsnNode node, boolean forRead)
930    {
931        // First look in the local fieldInstrumentations, which contains private field instrumentations
932        // (as well as non-private ones).
933
934        String searchStart = node.owner;
935
936        if (searchStart.equals(classNode.name))
937        {
938            FieldInstrumentation result = fieldInstrumentations.get(node.name, forRead);
939
940            if (result != null)
941            {
942                return result;
943            }
944
945            // Slight optimization: start the search in the super-classes' fields, since we've already
946            // checked this classes fields.
947
948            searchStart = classNode.superName;
949        }
950
951        return pool.getFieldInstrumentation(searchStart, node.name, forRead);
952    }
953
954    String getInstanceContextFieldName()
955    {
956        if (instanceContextFieldName == null)
957        {
958            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
959
960            // TODO: We could use a protected field and only initialize
961            // it once, in the first base class where it is needed, though that raises the possibilities
962            // of name conflicts (a subclass might introduce a field with a conflicting name).
963
964            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
965                    null, null);
966
967            classNode.fields.add(node);
968
969            // Extend the constructor to store the context in a field.
970
971            constructorBuilder.loadThis().loadArgument(1)
972                    .putField(className, instanceContextFieldName, InstanceContext.class);
973        }
974
975        return instanceContextFieldName;
976    }
977
978    /**
979     * Creates a new private final field and initializes its value (using the StaticContext).
980     */
981    String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
982                                                     Object injectedFieldValue)
983    {
984        String name = makeUnique(fieldNames, suggestedFieldName);
985
986        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
987
988        classNode.fields.add(field);
989
990        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
991
992        return name;
993    }
994
995    /**
996     * Initializes a field from the static context. The injected value is added to the static
997     * context and the class constructor updated to assign the value from the context (which includes casting and
998     * possibly unboxing).
999     */
1000    void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
1001    {
1002        int index = staticContext.store(injectedFieldValue);
1003
1004        // Although it feels nicer to do the loadThis() later and then swap(), that breaks
1005        // on primitive longs and doubles, so its just easier to do the loadThis() first
1006        // so its at the right place on the stack for the putField().
1007
1008        constructorBuilder.loadThis();
1009
1010        constructorBuilder.loadArgument(0).loadConstant(index);
1011        constructorBuilder.invoke(STATIC_CONTEXT_GET_METHOD);
1012        constructorBuilder.castOrUnbox(fieldType);
1013
1014        constructorBuilder.putField(className, fieldName, fieldType);
1015    }
1016
1017    void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
1018    {
1019        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
1020    }
1021
1022    public PlasticClass getPlasticClass()
1023    {
1024        return this;
1025    }
1026
1027    public Class<?> getTransformedClass()
1028    {
1029        if (transformedClass == null)
1030            throw new IllegalStateException(String.format(
1031                    "Transformed class %s is not yet available because the transformation is not yet complete.",
1032                    className));
1033
1034        return transformedClass;
1035    }
1036
1037    private boolean isInheritableMethod(MethodNode node)
1038    {
1039        return (node.access & (ACC_ABSTRACT | ACC_PRIVATE)) == 0;
1040    }
1041
1042    public String getClassName()
1043    {
1044        return className;
1045    }
1046
1047    InstructionBuilderImpl newBuilder(MethodNode mn)
1048    {
1049        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
1050    }
1051
1052    InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
1053    {
1054        return new InstructionBuilderImpl(description, mn, nameCache);
1055    }
1056
1057    public Set<PlasticMethod> introduceInterface(Class interfaceType)
1058    {
1059        check();
1060
1061        assert interfaceType != null;
1062
1063        if (!interfaceType.isInterface())
1064            throw new IllegalArgumentException(String.format(
1065                    "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName()));
1066
1067        String interfaceName = nameCache.toInternalName(interfaceType);
1068
1069        if (!inheritanceData.isInterfaceImplemented(interfaceName))
1070        {
1071            classNode.interfaces.add(interfaceName);
1072            inheritanceData.addInterface(interfaceName);
1073        }
1074
1075        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
1076
1077        for (Method m : interfaceType.getMethods())
1078        {
1079            MethodDescription description = new MethodDescription(m);
1080
1081            if (!isMethodImplemented(description))
1082            {
1083                introducedMethods.add(introduceMethod(m));
1084            }
1085        }
1086
1087        return introducedMethods;
1088    }
1089
1090    public PlasticClass addToString(final String toStringValue)
1091    {
1092        check();
1093
1094        if (!isMethodImplemented(PlasticUtils.TO_STRING_DESCRIPTION))
1095        {
1096            introduceMethod(PlasticUtils.TO_STRING_DESCRIPTION, new InstructionBuilderCallback()
1097            {
1098                public void doBuild(InstructionBuilder builder)
1099                {
1100                    builder.loadConstant(toStringValue).returnResult();
1101                }
1102            });
1103        }
1104
1105        return this;
1106    }
1107
1108    public boolean isMethodImplemented(MethodDescription description)
1109    {
1110        return inheritanceData.isImplemented(description.methodName, nameCache.toDesc(description));
1111    }
1112
1113    public boolean isInterfaceImplemented(Class interfaceType)
1114    {
1115        assert interfaceType != null;
1116        assert interfaceType.isInterface();
1117
1118        String interfaceName = nameCache.toInternalName(interfaceType);
1119
1120        return inheritanceData.isInterfaceImplemented(interfaceName);
1121    }
1122
1123    public String getSuperClassName()
1124    {
1125        return superClassName;
1126    }
1127
1128    public PlasticClass onConstruct(ConstructorCallback callback)
1129    {
1130        check();
1131
1132        assert callback != null;
1133
1134        constructorCallbacks.add(callback);
1135
1136        return this;
1137    }
1138
1139    void redirectFieldWrite(String fieldName, boolean privateField, MethodNode method)
1140    {
1141        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1142
1143        fieldInstrumentations.write.put(fieldName, fi);
1144
1145        if (!(proxy || privateField))
1146        {
1147            pool.setFieldWriteInstrumentation(classNode.name, fieldName, fi);
1148        }
1149    }
1150
1151    void redirectFieldRead(String fieldName, boolean privateField, MethodNode method)
1152    {
1153        FieldInstrumentation fi = new FieldInstrumentation(method.name, method.desc);
1154
1155        fieldInstrumentations.read.put(fieldName, fi);
1156
1157        if (!(proxy || privateField))
1158        {
1159            pool.setFieldReadInstrumentation(classNode.name, fieldName, fi);
1160        }
1161    }
1162}