You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2011/03/29 19:24:35 UTC

svn commit: r1086644 [2/5] - in /tapestry/tapestry5/trunk: ./ plastic/ plastic/src/ plastic/src/main/ plastic/src/main/java/ plastic/src/main/java/org/ plastic/src/main/java/org/apache/ plastic/src/main/java/org/apache/tapestry5/ plastic/src/main/java/...

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java?rev=1086644&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java Tue Mar 29 17:24:31 2011
@@ -0,0 +1,2186 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.plastic;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.tapestry5.plastic.AnnotationAccess;
+import org.apache.tapestry5.plastic.ClassInstantiator;
+import org.apache.tapestry5.plastic.ComputedValue;
+import org.apache.tapestry5.plastic.FieldConduit;
+import org.apache.tapestry5.plastic.FieldHandle;
+import org.apache.tapestry5.plastic.InstanceContext;
+import org.apache.tapestry5.plastic.InstructionBuilder;
+import org.apache.tapestry5.plastic.InstructionBuilderCallback;
+import org.apache.tapestry5.plastic.MethodAdvice;
+import org.apache.tapestry5.plastic.MethodDescription;
+import org.apache.tapestry5.plastic.MethodHandle;
+import org.apache.tapestry5.plastic.MethodInvocation;
+import org.apache.tapestry5.plastic.MethodInvocationResult;
+import org.apache.tapestry5.plastic.MethodParameter;
+import org.apache.tapestry5.plastic.PlasticClass;
+import org.apache.tapestry5.plastic.PlasticField;
+import org.apache.tapestry5.plastic.PlasticMethod;
+import org.apache.tapestry5.plastic.PlasticUtils;
+import org.apache.tapestry5.plastic.PropertyAccessType;
+import org.apache.tapestry5.plastic.SwitchBlock;
+import org.apache.tapestry5.plastic.SwitchCallback;
+import org.apache.tapestry5.plastic.TryCatchBlock;
+import org.apache.tapestry5.plastic.TryCatchCallback;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldInsnNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.InsnList;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.VarInsnNode;
+
+@SuppressWarnings("all")
+public class PlasticClassImpl extends Lockable implements PlasticClass, PlasticClassTransformation, Opcodes
+{
+    private static final String NOTHING_TO_VOID = "()V";
+
+    private static final String CONSTRUCTOR_NAME = "<init>";
+
+    private static final String OBJECT_INT_TO_OBJECT = "(Ljava/lang/Object;I)Ljava/lang/Object;";
+
+    private static final String OBJECT_INT_OBJECT_TO_VOID = "(Ljava/lang/Object;ILjava/lang/Object;)V";
+
+    private static final String OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT = String.format(
+            "(Ljava/lang/Object;I[Ljava/lang/Object;)%s", toDesc(Type.getInternalName(MethodInvocationResult.class)));
+
+    private static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME = PlasticInternalUtils
+            .toInternalName(AbstractMethodInvocation.class.getName());
+
+    private static final String OBJECT_INTERNAL_NAME = Type.getInternalName(Object.class);
+
+    private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type
+            .getInternalName(PlasticClassHandleShim.class);
+
+    private static final String STATIC_CONTEXT_INTERNAL_NAME = Type.getInternalName(StaticContext.class);
+
+    private static final String INSTANCE_CONTEXT_INTERNAL_NAME = Type.getInternalName(InstanceContext.class);
+
+    private static final String INSTANCE_CONTEXT_DESC = toDesc(INSTANCE_CONTEXT_INTERNAL_NAME);
+
+    private static final String CONSTRUCTOR_DESC = String.format("(L%s;L%s;)V", STATIC_CONTEXT_INTERNAL_NAME,
+            INSTANCE_CONTEXT_INTERNAL_NAME);
+
+    private static final MethodDescription TO_STRING_METHOD_DESCRIPTION = new MethodDescription(String.class.getName(),
+            "toString");
+
+    private static String toDesc(String internalName)
+    {
+        return "L" + internalName + ";";
+    }
+
+    private class PlasticMember implements AnnotationAccess
+    {
+        private final AnnotationAccess annotationAccess;
+
+        PlasticMember(List<AnnotationNode> visibleAnnotations)
+        {
+            annotationAccess = pool.createAnnotationAccess(visibleAnnotations);
+        }
+
+        public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
+        {
+            check();
+
+            return annotationAccess.hasAnnotation(annotationType);
+        }
+
+        public <T extends Annotation> T getAnnotation(Class<T> annotationType)
+        {
+            check();
+
+            return annotationAccess.getAnnotation(annotationType);
+        }
+
+    }
+
+    private class MethodParameterImpl extends PlasticMember implements MethodParameter
+    {
+        private final String type;
+
+        private final int index;
+
+        MethodParameterImpl(List<AnnotationNode> visibleAnnotations, String type, int index)
+        {
+            super(visibleAnnotations);
+
+            this.type = type;
+            this.index = index;
+        }
+
+        public String getType()
+        {
+            check();
+
+            return type;
+        }
+
+        public int getIndex()
+        {
+            check();
+
+            return index;
+        }
+    }
+
+    private class PlasticMethodImpl extends PlasticMember implements PlasticMethod, Comparable<PlasticMethodImpl>
+    {
+        private final MethodNode node;
+
+        private MethodDescription description;
+
+        private MethodHandleImpl handle;
+
+        private MethodAdviceManager adviceManager;
+
+        private List<MethodParameter> parameters;
+
+        private int methodIndex = -1;
+
+        public PlasticMethodImpl(MethodNode node)
+        {
+            super(node.visibleAnnotations);
+
+            this.node = node;
+            this.description = PlasticInternalUtils.toMethodDescription(node);
+        }
+
+        public String toString()
+        {
+            return String.format("PlasticMethod[%s in class %s]", description, className);
+        }
+
+        public MethodDescription getDescription()
+        {
+            check();
+
+            return description;
+        }
+
+        public int compareTo(PlasticMethodImpl o)
+        {
+            return description.compareTo(o.description);
+        }
+
+        public MethodHandle getHandle()
+        {
+            check();
+
+            if (handle == null)
+            {
+                methodIndex = nextMethodIndex++;
+                handle = new MethodHandleImpl(className, description.toString(), methodIndex);
+                shimMethods.add(this);
+            }
+
+            return handle;
+        }
+
+        public PlasticMethod changeImplementation(InstructionBuilderCallback callback)
+        {
+            check();
+
+            // If the method is currently abstract, clear that flag.
+            if (Modifier.isAbstract(node.access))
+            {
+                node.access = node.access & ~ACC_ABSTRACT;
+                description = description.withModifiers(node.access);
+            }
+
+            node.instructions.clear();
+
+            newBuilder(description, node).doCallback(callback);
+
+            // With the implementation changed, it is necessary to intercept field reads/writes.
+            // The node may not already have been in the fieldTransformMethods Set if it was
+            // an introduced method.
+
+            fieldTransformMethods.add(node);
+
+            return this;
+        }
+
+        public PlasticMethod addAdvice(MethodAdvice advice)
+        {
+            check();
+
+            assert advice != null;
+
+            if (adviceManager == null)
+            {
+                adviceManager = new MethodAdviceManager(description, node);
+                advisedMethods.add(this);
+            }
+
+            adviceManager.add(advice);
+
+            return this;
+        }
+
+        public PlasticMethod delegateTo(final PlasticField field)
+        {
+            check();
+
+            assert field != null;
+
+            // TODO: Ensure that the field is a field of this class.
+
+            // TODO: Better handling error case where delegating to a primitive or object array.
+
+            // TODO: Is there a easy way to ensure that the type has the necessary method? I don't
+            // like that errors along those lines may be deferred until execution time.
+
+            changeImplementation(new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    // Load the field
+
+                    String delegateType = field.getTypeName();
+
+                    builder.loadThis().getField(className, field.getName(), delegateType);
+                    builder.loadArguments();
+
+                    invokeDelegateAndReturnResult(builder, delegateType);
+                }
+            });
+
+            return this;
+        }
+
+        public PlasticMethod delegateTo(PlasticMethod delegateProvidingMethod)
+        {
+            check();
+
+            assert delegateProvidingMethod != null;
+
+            // TODO: ensure same class, ensure not primitive/array type
+            final MethodDescription providerDescriptor = delegateProvidingMethod.getDescription();
+            final String delegateType = providerDescriptor.returnType;
+
+            if (delegateType.equals("void") || providerDescriptor.argumentTypes.length > 0)
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Method %s is not usable as a delegate provider; it must be a void method that takes no arguments.",
+                                delegateProvidingMethod));
+
+            changeImplementation(new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    // Load the field
+
+                    builder.loadThis();
+
+                    if (Modifier.isPrivate(providerDescriptor.modifiers))
+                    {
+                        builder.invokeSpecial(className, providerDescriptor);
+                    }
+                    else
+                    {
+                        builder.invokeVirtual(className, delegateType, providerDescriptor.methodName);
+                    }
+
+                    builder.loadArguments();
+
+                    invokeDelegateAndReturnResult(builder, delegateType);
+                }
+            });
+
+            return this;
+        }
+
+        public List<MethodParameter> getParameters()
+        {
+            if (parameters == null)
+            {
+                parameters = PlasticInternalUtils.newList();
+
+                for (int i = 0; i < description.argumentTypes.length; i++)
+                {
+                    parameters.add(new MethodParameterImpl(node.visibleParameterAnnotations[i],
+                            description.argumentTypes[i], i));
+                }
+            }
+
+            return parameters;
+        }
+
+        private void rewriteMethodForAdvice()
+        {
+            adviceManager.rewriteOriginalMethod();
+        }
+
+        private boolean isPrivate()
+        {
+            return Modifier.isPrivate(node.access);
+        }
+
+        /**
+         * If a MethodHandle has been requested and the method itself is private, then create a non-private
+         * access method. Returns the access method name (which is different from the method name itself only
+         * for private methods, where an access method is created).
+         */
+        private String setupMethodHandleAccess()
+        {
+            if (isPrivate())
+                return createAccessMethod();
+            else
+                return node.name;
+        }
+
+        private String createAccessMethod()
+        {
+            String name = String.format("%s$access%s", node.name, PlasticUtils.nextUID());
+
+            // Kind of awkward that exceptions are specified as String[] when what we have handy is List<String>
+            MethodNode mn = new MethodNode(ACC_SYNTHETIC | ACC_FINAL, name, node.desc, node.signature, null);
+            // But it is safe enough for the two nodes to share
+            mn.exceptions = node.exceptions;
+
+            InstructionBuilder builder = newBuilder(mn);
+
+            builder.loadThis();
+            builder.loadArguments();
+            builder.invokeSpecial(className, description);
+            builder.returnResult();
+
+            classNode.methods.add(mn);
+
+            return name;
+        }
+
+        void installShim(PlasticClassHandleShim shim)
+        {
+            handle.shim = shim;
+        }
+
+        /**
+         * The properly cast target instance will be on the stack.
+         */
+        void extendShimInvoke(SwitchBlock block)
+        {
+            final String accessMethodName = setupMethodHandleAccess();
+
+            block.addCase(methodIndex, false, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.startTryCatch(new TryCatchCallback()
+                    {
+                        public void doBlock(TryCatchBlock block)
+                        {
+                            block.addTry(new InstructionBuilderCallback()
+                            {
+                                public void doBuild(InstructionBuilder builder)
+                                {
+                                    // The third argument is an Object array; get each
+                                    for (int i = 0; i < description.argumentTypes.length; i++)
+                                    {
+                                        String argumentType = description.argumentTypes[i];
+
+                                        builder.loadArgument(2);
+                                        builder.loadArrayElement(i, Object.class.getName());
+                                        builder.castOrUnbox(argumentType);
+                                    }
+
+                                    builder.invokeVirtual(className, description.returnType, accessMethodName,
+                                            description.argumentTypes);
+
+                                    // TODO: hate see "void" just there.
+
+                                    if (description.returnType.equals("void"))
+                                        builder.loadNull();
+                                    else
+                                        builder.boxPrimitive(description.returnType);
+
+                                    builder.newInstance(SuccessMethodInvocationResult.class).dupe(1).swap();
+                                    builder.invokeConstructor(SuccessMethodInvocationResult.class, Object.class);
+                                    builder.returnResult();
+                                }
+                            });
+
+                            for (String exceptionType : description.checkedExceptionTypes)
+                            {
+                                block.addCatch(exceptionType, new InstructionBuilderCallback()
+                                {
+                                    public void doBuild(InstructionBuilder builder)
+                                    {
+                                        builder.newInstance(FailureMethodInvocationResult.class).dupe(1).swap();
+                                        builder.invokeConstructor(FailureMethodInvocationResult.class, Throwable.class);
+                                        builder.returnResult();
+                                    }
+                                });
+                            }
+                        }
+                    });
+                }
+            });
+        }
+
+        private void invokeDelegateAndReturnResult(InstructionBuilder builder, String delegateType)
+        {
+            // The trick is that you have to be careful to use the right opcode based on the field type
+            // (interface vs. ordinary object).
+
+            final TypeCategory typeCategory = pool.getTypeCategory(delegateType);
+
+            if (typeCategory == TypeCategory.INTERFACE)
+                builder.invokeInterface(delegateType, description.returnType, description.methodName,
+                        description.argumentTypes);
+            else
+                builder.invokeVirtual(delegateType, description.returnType, description.methodName,
+                        description.argumentTypes);
+
+            builder.returnResult();
+        }
+
+    }
+
+    private class PlasticFieldImpl extends PlasticMember implements PlasticField, Comparable<PlasticFieldImpl>
+    {
+        private final FieldNode node;
+
+        private final String typeName;
+
+        private Object tag;
+
+        private FieldHandleImpl handle;
+
+        // Names of methods to get or set the value of the field, invoked
+        // from the generated FieldAccess object. With a FieldConduit,
+        // these also represent the names of the methods that replace field access
+        // in non-introduced methods
+
+        private String getAccessName, setAccessName;
+
+        private FieldState state = FieldState.INITIAL;
+
+        private int fieldIndex = -1;
+
+        public PlasticFieldImpl(FieldNode node)
+        {
+            super(node.visibleAnnotations);
+
+            this.node = node;
+            this.typeName = Type.getType(node.desc).getClassName();
+        }
+
+        public String toString()
+        {
+            return String.format("PlasticField[%s %s %s (in class %s)]", Modifier.toString(node.access), typeName,
+                    node.name, className);
+        }
+
+        public int compareTo(PlasticFieldImpl o)
+        {
+            return this.node.name.compareTo(o.node.name);
+        }
+
+        public FieldHandle getHandle()
+        {
+            check();
+
+            if (handle == null)
+            {
+                fieldIndex = nextFieldIndex++;
+
+                // The shim gets assigned later
+
+                handle = new FieldHandleImpl(className, node.name, fieldIndex);
+
+                shimFields.add(this);
+            }
+
+            return handle;
+        }
+
+        public PlasticField claim(Object tag)
+        {
+            assert tag != null;
+
+            check();
+
+            if (this.tag != null)
+                throw new IllegalStateException(String.format(
+                        "Field %s of class %s can not be claimed by %s as it is already claimed by %s.", node.name,
+                        className, tag, this.tag));
+
+            this.tag = tag;
+
+            // Force the list of unclaimed fields to be recomputed on next access
+
+            unclaimedFields = null;
+
+            return this;
+        }
+
+        public boolean isClaimed()
+        {
+            check();
+
+            return tag != null;
+        }
+
+        public String getName()
+        {
+            check();
+
+            return node.name;
+        }
+
+        public String getTypeName()
+        {
+            check();
+
+            return typeName;
+        }
+
+        private void verifyInitialState(String operation)
+        {
+            if (state != FieldState.INITIAL)
+                throw new IllegalStateException(String.format("Unable to %s field %s of class %s, as it already %s.",
+                        operation, node.name, className, state.description));
+        }
+
+        public PlasticField inject(Object value)
+        {
+            check();
+
+            verifyInitialState("inject a value into");
+
+            assert value != null;
+
+            initializeFieldFromStaticContext(node.name, typeName, value);
+
+            makeReadOnly();
+
+            state = FieldState.INJECTED;
+
+            return this;
+        }
+
+        public PlasticField injectComputed(ComputedValue<?> computedValue)
+        {
+            check();
+
+            verifyInitialState("inject a computed value into");
+
+            assert computedValue != null;
+
+            initializeComputedField(computedValue);
+
+            makeReadOnly();
+
+            state = FieldState.INJECTED;
+
+            return this;
+        }
+
+        private void initializeComputedField(ComputedValue<?> computedValue)
+        {
+            int index = staticContext.store(computedValue);
+
+            constructorBuilder.loadThis(); // for the putField()
+
+            // Get the ComputedValue out of the StaticContext and onto the stack
+
+            constructorBuilder.loadArgument(0).loadConstant(index);
+            constructorBuilder.invoke(StaticContext.class, Object.class, "get", int.class).checkcast(
+                    ComputedValue.class);
+
+            // Add the InstanceContext to the stack
+
+            constructorBuilder.loadArgument(1);
+            constructorBuilder.invoke(ComputedValue.class, Object.class, "get", InstanceContext.class).castOrUnbox(
+                    typeName);
+
+            constructorBuilder.putField(className, node.name, typeName);
+        }
+
+        public PlasticField setConduit(FieldConduit<?> conduit)
+        {
+            assert conduit != null;
+
+            check();
+
+            verifyInitialState("set the FieldConduit for");
+
+            // First step: define a field to store the conduit and add constructor logic
+            // to initialize it
+
+            String conduitFieldName = createAndInitializeFieldFromStaticContext(node.name + "_FieldConduit",
+                    FieldConduit.class.getName(), conduit);
+
+            replaceFieldReadAccess(conduitFieldName);
+            replaceFieldWriteAccess(conduitFieldName);
+
+            // TODO: Do we keep the field or not? It will now always be null/0/false.
+
+            state = FieldState.CONDUIT;
+
+            return null;
+        }
+
+        public PlasticField createAccessors(PropertyAccessType accessType)
+        {
+            check();
+
+            return createAccessors(accessType, PlasticInternalUtils.toPropertyName(node.name));
+        }
+
+        public PlasticField createAccessors(PropertyAccessType accessType, String propertyName)
+        {
+            check();
+
+            assert accessType != null;
+            assert PlasticInternalUtils.isNonBlank(propertyName);
+
+            String capitalized = PlasticInternalUtils.capitalize(propertyName);
+
+            if (accessType != PropertyAccessType.WRITE_ONLY)
+            {
+                introduceMethod(new MethodDescription(getTypeName(), "get" + capitalized, new String[0]))
+                        .changeImplementation(new InstructionBuilderCallback()
+                        {
+                            public void doBuild(InstructionBuilder builder)
+                            {
+                                builder.loadThis().getField(className, node.name, getTypeName()).returnResult();
+                            }
+                        });
+            }
+
+            if (accessType != PropertyAccessType.READ_ONLY)
+            {
+                introduceMethod(new MethodDescription("void", "set" + capitalized, getTypeName()))
+                        .changeImplementation(new InstructionBuilderCallback()
+                        {
+                            public void doBuild(InstructionBuilder builder)
+                            {
+                                builder.loadThis().loadArgument(0);
+                                builder.putField(className, node.name, getTypeName());
+                                builder.returnResult();
+                            }
+                        });
+            }
+
+            return this;
+        }
+
+        private void replaceFieldWriteAccess(String conduitFieldName)
+        {
+            setAccessName = makeUnique(methodNames, "set_" + node.name);
+
+            MethodNode mn = new MethodNode(ACC_SYNTHETIC | ACC_FINAL, setAccessName, "(" + node.desc + ")V", null, null);
+
+            InstructionBuilder builder = newBuilder(mn);
+
+            pushFieldConduitOntoStack(conduitFieldName, builder);
+            pushInstanceContextFieldOntoStack(builder);
+
+            // Take the value passed to this method and push it onto the stack.
+
+            builder.loadArgument(0);
+            builder.boxPrimitive(typeName);
+
+            builder.invoke(FieldConduit.class, void.class, "set", InstanceContext.class, Object.class);
+
+            builder.returnResult();
+
+            addMethod(mn);
+
+            fieldToWriteMethod.put(node.name, setAccessName);
+        }
+
+        private void replaceFieldReadAccess(String conduitFieldName)
+        {
+            getAccessName = makeUnique(methodNames, "getfieldvalue_" + node.name);
+
+            MethodNode mn = new MethodNode(ACC_SYNTHETIC | ACC_FINAL, getAccessName, "()" + node.desc, null, null);
+
+            InstructionBuilder builder = newBuilder(mn);
+
+            // Get the correct FieldConduit object on the stack
+
+            pushFieldConduitOntoStack(conduitFieldName, builder);
+
+            // Now push the instance context on the stack
+
+            pushInstanceContextFieldOntoStack(builder);
+
+            builder.invoke(FieldConduit.class, Object.class, "get", InstanceContext.class).castOrUnbox(typeName);
+
+            builder.returnResult();
+
+            addMethod(mn);
+
+            fieldToReadMethod.put(node.name, getAccessName);
+        }
+
+        private void pushFieldConduitOntoStack(String conduitFileName, InstructionBuilder builder)
+        {
+            builder.loadThis();
+            builder.getField(className, conduitFileName, FieldConduit.class);
+        }
+
+        private void makeReadOnly()
+        {
+            setAccessName = makeUnique(methodNames, "setfieldvalue_" + node.name);
+
+            MethodNode mn = new MethodNode(ACC_SYNTHETIC | ACC_FINAL, setAccessName, "(" + node.desc + ")V", null, null);
+
+            String message = String.format("Field %s of class %s is read-only.", node.name, className);
+
+            newBuilder(mn).throwException(IllegalStateException.class, message);
+
+            addMethod(mn);
+
+            fieldToWriteMethod.put(node.name, setAccessName);
+
+            node.access |= ACC_FINAL;
+        }
+
+        /**
+         * Adds a static setter method, allowing an external FieldAccess implementation
+         * to directly set the value of the field.
+         */
+        private MethodNode addSetAccessMethod()
+        {
+            String name = makeUnique(methodNames, "directset_" + node.name);
+
+            // Takes two Object parameters (instance, and value) and returns void.
+
+            MethodNode mn = new MethodNode(ACC_SYNTHETIC | ACC_FINAL, name, "(" + node.desc + ")V", null, null);
+
+            InstructionBuilder builder = newBuilder(mn);
+
+            builder.loadThis().loadArgument(0).putField(className, node.name, typeName);
+            builder.returnResult();
+
+            addMethod(mn);
+
+            return mn;
+        }
+
+        private MethodNode addGetAccessMethod()
+        {
+            String name = makeUnique(methodNames, "directget_" + node.name);
+
+            MethodNode mn = new MethodNode(ACC_SYNTHETIC | ACC_FINAL, name, "()" + node.desc, null, null);
+
+            InstructionBuilder builder = newBuilder(mn);
+
+            builder.loadThis().getField(className, node.name, typeName).returnResult();
+
+            addMethod(mn);
+
+            return mn;
+        }
+
+        void installShim(PlasticClassHandleShim shim)
+        {
+            if (handle != null)
+            {
+                handle.shim = shim;
+            }
+        }
+
+        /** Invoked with the object instance on the stack and cast to the right type. */
+        void extendShimGet(SwitchBlock switchBlock)
+        {
+            String accessMethodName = getAccessName;
+
+            if (accessMethodName == null)
+            {
+                MethodNode method = addGetAccessMethod();
+                fieldTransformMethods.add(method);
+                accessMethodName = method.name;
+            }
+
+            final String methodToInvoke = accessMethodName;
+
+            switchBlock.addCase(fieldIndex, false, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.invokeVirtual(className, typeName, methodToInvoke).boxPrimitive(typeName).returnResult();
+                }
+            });
+        }
+
+        /**
+         * Invoked with the object instance on the stack and cast to the right type, then the
+         * new field value (as Object, needing to be cast or unboxed).
+         */
+        void extendShimSet(SwitchBlock switchBlock)
+        {
+            String accessMethodName = setAccessName;
+
+            // If no conduit has yet been specified, then we need a set access method for the shim to invoke.
+
+            if (accessMethodName == null)
+            {
+                MethodNode method = addSetAccessMethod();
+                fieldTransformMethods.add(method);
+                accessMethodName = method.name;
+            }
+
+            final String methodToInvoke = accessMethodName;
+
+            switchBlock.addCase(fieldIndex, true, new InstructionBuilderCallback()
+            {
+
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.castOrUnbox(typeName);
+                    builder.invokeVirtual(className, "void", methodToInvoke, typeName);
+                    // Should not be necessary, as its always a void method, and we can
+                    // drop to the bottom of the method.
+                    // builder.returnResult();
+                }
+            });
+        }
+    }
+
+    /**
+     * Responsible for tracking the advice added to a method, as well as creating the MethodInvocation
+     * class for the method and ultimately rewriting the original method to instnatiate the MethodInvocation
+     * and handle the success or failure result.
+     */
+    class MethodAdviceManager
+    {
+        private final static String RETURN_VALUE = "returnValue";
+
+        private final MethodDescription description;
+
+        /**
+         * The method to which advice is added; it must be converted to instantiate the
+         * MethodInvocation subclass, then unpack the return value and/or re-throw checked exceptions.
+         */
+        private final MethodNode advisedMethodNode;
+
+        private final ClassNode invocationClassNode;
+
+        private final List<MethodAdvice> advice = new ArrayList<MethodAdvice>();
+
+        private final boolean isVoid;
+
+        private final String invocationClassName;
+
+        /** The new method that uses the original instructions from the advisedMethodNode. */
+        private final String newMethodName;
+
+        private final String[] constructorTypes;
+
+        protected MethodAdviceManager(MethodDescription description, MethodNode methodNode)
+        {
+            this.description = description;
+            this.advisedMethodNode = methodNode;
+
+            isVoid = description.returnType.equals("void");
+
+            invocationClassName = String.format("%s$Invocation_%s_%s", className, description.methodName,
+                    PlasticUtils.nextUID());
+
+            invocationClassNode = new ClassNode();
+
+            invocationClassNode.visit(V1_5, ACC_PUBLIC | ACC_FINAL, nameCache.toInternalName(invocationClassName),
+                    null, ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME, new String[]
+                    { nameCache.toInternalName(MethodInvocation.class) });
+
+            constructorTypes = createFieldsAndConstructor();
+
+            createReturnValueAccessors();
+
+            createSetParameter();
+
+            createGetParameter();
+
+            newMethodName = String.format("advised$%s_%s", description.methodName, PlasticUtils.nextUID());
+
+            createNewMethod();
+
+            createProceedToAdvisedMethod();
+        }
+
+        private String[] createFieldsAndConstructor()
+        {
+            if (!isVoid)
+                invocationClassNode.visitField(ACC_PUBLIC, RETURN_VALUE, nameCache.toDesc(description.returnType),
+                        null, null);
+
+            List<String> consTypes = new ArrayList<String>();
+            consTypes.add(Object.class.getName());
+            consTypes.add(InstanceContext.class.getName());
+            consTypes.add(MethodAdvice.class.getName() + "[]");
+
+            for (int i = 0; i < description.argumentTypes.length; i++)
+            {
+                String type = description.argumentTypes[i];
+
+                invocationClassNode.visitField(ACC_PRIVATE, "p" + i, nameCache.toDesc(type), null, null);
+
+                consTypes.add(type);
+            }
+
+            String[] constructorTypes = consTypes.toArray(new String[consTypes.size()]);
+
+            MethodNode cons = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, nameCache.toMethodDescriptor("void",
+                    constructorTypes), null, null);
+
+            InstructionBuilder builder = newBuilder(cons);
+
+            // First three arguments go to the super-class
+
+            builder.loadThis();
+            builder.loadArgument(0);
+            builder.loadArgument(1);
+            builder.loadArgument(2);
+            builder.invokeConstructor(AbstractMethodInvocation.class, Object.class, InstanceContext.class,
+                    MethodAdvice[].class);
+
+            for (int i = 0; i < description.argumentTypes.length; i++)
+            {
+                String name = "p" + i;
+                String type = description.argumentTypes[i];
+
+                builder.loadThis();
+                builder.loadArgument(3 + i);
+                builder.putField(invocationClassName, name, type);
+            }
+
+            builder.returnResult();
+
+            invocationClassNode.methods.add(cons);
+
+            return constructorTypes;
+        }
+
+        public void add(MethodAdvice advice)
+        {
+            this.advice.add(advice);
+        }
+
+        private void createReturnValueAccessors()
+        {
+            addReturnValueSetter();
+            createReturnValueGetter();
+        }
+
+        private InstructionBuilder newMethod(String name, Class returnType, Class... argumentTypes)
+        {
+            MethodNode mn = new MethodNode(ACC_PUBLIC, name, nameCache.toMethodDescriptor(returnType, argumentTypes),
+                    null, null);
+
+            invocationClassNode.methods.add(mn);
+
+            return newBuilder(mn);
+        }
+
+        private void createReturnValueGetter()
+        {
+            InstructionBuilder builder = newMethod("getReturnValue", Object.class);
+
+            if (isVoid)
+            {
+                builder.loadNull().returnResult();
+            }
+            else
+            {
+                builder.loadThis().getField(invocationClassName, RETURN_VALUE, description.returnType)
+                        .boxPrimitive(description.returnType).returnResult();
+            }
+        }
+
+        private void addReturnValueSetter()
+        {
+            InstructionBuilder builder = newMethod("setReturnValue", MethodInvocation.class, Object.class);
+
+            if (isVoid)
+            {
+                builder.throwException(IllegalArgumentException.class, String
+                        .format("Method %s of class %s is void, setting a return value is not allowed.", description,
+                                className));
+            }
+            else
+            {
+                builder.loadThis().loadArgument(0);
+                builder.castOrUnbox(description.returnType);
+                builder.putField(invocationClassName, RETURN_VALUE, description.returnType);
+
+                builder.loadThis().returnResult();
+            }
+        }
+
+        private void createGetParameter()
+        {
+            InstructionBuilder builder = newMethod("getParameter", Object.class, int.class);
+
+            if (description.argumentTypes.length == 0)
+            {
+                indexOutOfRange(builder);
+            }
+            else
+            {
+                builder.loadArgument(0);
+                builder.startSwitch(0, description.argumentTypes.length - 1, new SwitchCallback()
+                {
+
+                    public void doSwitch(SwitchBlock block)
+                    {
+                        for (int i = 0; i < description.argumentTypes.length; i++)
+                        {
+                            final int index = i;
+
+                            block.addCase(i, false, new InstructionBuilderCallback()
+                            {
+
+                                public void doBuild(InstructionBuilder builder)
+                                {
+                                    String type = description.argumentTypes[index];
+
+                                    builder.loadThis();
+                                    builder.getField(invocationClassName, "p" + index, type).boxPrimitive(type)
+                                            .returnResult();
+                                }
+                            });
+                        }
+                    }
+                });
+            }
+        }
+
+        private void indexOutOfRange(InstructionBuilder builder)
+        {
+            builder.throwException(IllegalArgumentException.class, "Parameter index out of range.");
+        }
+
+        private void createSetParameter()
+        {
+            InstructionBuilder builder = newMethod("setParameter", MethodInvocation.class, int.class, Object.class);
+
+            if (description.argumentTypes.length == 0)
+            {
+                indexOutOfRange(builder);
+            }
+            else
+            {
+                builder.loadArgument(0).startSwitch(0, description.argumentTypes.length - 1, new SwitchCallback()
+                {
+
+                    public void doSwitch(SwitchBlock block)
+                    {
+                        for (int i = 0; i < description.argumentTypes.length; i++)
+                        {
+                            final int index = i;
+
+                            block.addCase(i, true, new InstructionBuilderCallback()
+                            {
+
+                                public void doBuild(InstructionBuilder builder)
+                                {
+                                    String type = description.argumentTypes[index];
+
+                                    builder.loadThis();
+                                    builder.loadArgument(1).castOrUnbox(type);
+                                    builder.putField(invocationClassName, "p" + index, type);
+                                }
+                            });
+                        }
+                    }
+                });
+
+                builder.loadThis().returnResult();
+            }
+        }
+
+        private void createNewMethod()
+        {
+            String[] exceptions = (String[]) (advisedMethodNode.exceptions == null ? null
+                    : advisedMethodNode.exceptions.toArray(new String[0]));
+
+            // Remove the private flag, so that the MethodInvocation implementation (in the same package)
+            // can directly access the method without an additional access method.
+
+            MethodNode mn = new MethodNode(advisedMethodNode.access & ~ACC_PRIVATE, newMethodName,
+                    advisedMethodNode.desc, advisedMethodNode.signature, exceptions);
+
+            // Copy everything else about the advisedMethodNode over to the new node
+
+            advisedMethodNode.accept(mn);
+
+            // Add this new method, with the same implementation as the original method, to the
+            // PlasticClass
+
+            classNode.methods.add(mn);
+        }
+
+        /** Invoke the "new" method, and deal with the return value and/or thrown exceptions. */
+        private void createProceedToAdvisedMethod()
+        {
+            InstructionBuilder builder = newMethod("proceedToAdvisedMethod", void.class);
+
+            if (!isVoid)
+                builder.loadThis();
+
+            builder.loadThis().invoke(AbstractMethodInvocation.class, Object.class, "getInstance").checkcast(className);
+
+            // Load up each parameter
+            for (int i = 0; i < description.argumentTypes.length; i++)
+            {
+                String type = description.argumentTypes[i];
+
+                builder.loadThis().getField(invocationClassName, "p" + i, type);
+            }
+
+            builder.startTryCatch(new TryCatchCallback()
+            {
+                public void doBlock(TryCatchBlock block)
+                {
+                    block.addTry(new InstructionBuilderCallback()
+                    {
+
+                        public void doBuild(InstructionBuilder builder)
+                        {
+                            builder.invokeVirtual(className, description.returnType, newMethodName,
+                                    description.argumentTypes);
+
+                            if (!isVoid)
+                                builder.putField(invocationClassName, RETURN_VALUE, description.returnType);
+
+                            builder.returnResult();
+                        }
+                    });
+
+                    for (String exceptionName : description.checkedExceptionTypes)
+                    {
+                        block.addCatch(exceptionName, new InstructionBuilderCallback()
+                        {
+                            public void doBuild(InstructionBuilder builder)
+                            {
+                                builder.loadThis().swap();
+                                builder.invoke(AbstractMethodInvocation.class, MethodInvocation.class,
+                                        "setCheckedException", Exception.class);
+
+                                builder.returnResult();
+                            }
+                        });
+                    }
+                }
+            });
+        }
+
+        private void rewriteOriginalMethod()
+        {
+            pool.realize(invocationClassNode);
+
+            String fieldName = String.format("advice_%s_%s", description.methodName, PlasticUtils.nextUID());
+
+            MethodAdvice[] adviceArray = advice.toArray(new MethodAdvice[advice.size()]);
+
+            classNode.visitField(ACC_PRIVATE | ACC_FINAL, fieldName, nameCache.toDesc(constructorTypes[2]), null, null);
+            initializeFieldFromStaticContext(fieldName, constructorTypes[2], adviceArray);
+
+            // Ok, here's the easy part: replace the method invocation with instantiating the invocation class
+
+            advisedMethodNode.instructions.clear();
+            advisedMethodNode.tryCatchBlocks.clear();
+
+            if (advisedMethodNode.localVariables != null)
+                advisedMethodNode.localVariables.clear();
+
+            InstructionBuilder builder = newBuilder(description, advisedMethodNode);
+
+            builder.newInstance(invocationClassName).dupe(0);
+
+            // Now load up the parameters to the constructor
+
+            builder.loadThis();
+            builder.loadThis().getField(className, getInstanceContextFieldName(), constructorTypes[1]);
+            builder.loadThis().getField(className, fieldName, constructorTypes[2]);
+
+            // Load up the actual method parameters
+
+            builder.loadArguments();
+            builder.invokeConstructor(invocationClassName, constructorTypes);
+
+            // That leaves an instance of the invocation class on the stack. If the method is void
+            // and throws no checked exceptions, then the variable actually isn't used. This code
+            // should be refactored a bit once there are tests for those cases.
+
+            builder.startVariable("invocation", invocationClassName, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.dupe(0).storeVariable("invocation");
+
+                    builder.invoke(AbstractMethodInvocation.class, MethodInvocation.class, "proceed");
+
+                    if (description.checkedExceptionTypes.length > 0)
+                    {
+                        builder.invoke(MethodInvocation.class, boolean.class, "didThrowCheckedException");
+
+                        builder.ifZero(null, new InstructionBuilderCallback()
+                        {
+                            public void doBuild(InstructionBuilder builder)
+                            {
+                                builder.loadVariable("invocation").loadTypeConstant(Exception.class);
+                                builder.invokeVirtual(invocationClassName, Throwable.class.getName(),
+                                        "getCheckedException", Class.class.getName());
+                                builder.throwException();
+                            }
+                        });
+                    }
+
+                    if (!isVoid)
+                        builder.loadVariable("invocation").getField(invocationClassName, RETURN_VALUE,
+                                description.returnType);
+
+                    builder.returnResult();
+                }
+            });
+        }
+    }
+
+    // Now past the inner classes; these are the instance variables of PlasticClassImpl proper:
+
+    private final ClassNode classNode;
+
+    private final PlasticClassPool pool;
+
+    private final String className;
+
+    private final String superClassName;
+
+    private final AnnotationAccess annotationAccess;
+
+    // All the non-introduced (and non-constructor) methods, in sorted order
+
+    private final List<PlasticMethodImpl> methods;
+
+    private final Map<MethodDescription, PlasticMethod> description2method = new HashMap<MethodDescription, PlasticMethod>();
+
+    private final Set<String> methodNames = new HashSet<String>();
+
+    // All non-introduced instance fields
+
+    private final List<PlasticFieldImpl> fields;
+
+    /**
+     * Methods that require special attention inside {@link #createInstantiator()} because they
+     * have method advice.
+     */
+    private final Set<PlasticMethodImpl> advisedMethods = PlasticInternalUtils.newSet();;
+
+    private final NameCache nameCache = new NameCache();
+
+    // This is generated from fields, as necessary
+    private List<PlasticField> unclaimedFields;
+
+    private final Set<String> fieldNames = PlasticInternalUtils.newSet();
+
+    private final StaticContext staticContext;
+
+    private final MethodBundle methodBundle;
+
+    // MethodNodes in which field transformations should occur; this is most existing and
+    // introduced methods, outside of special access methods.
+
+    private final Set<MethodNode> fieldTransformMethods = PlasticInternalUtils.newSet();
+
+    /**
+     * Maps a field name to a replacement method that should be invoked instead of reading the
+     * field.
+     */
+    private final Map<String, String> fieldToReadMethod = new HashMap<String, String>();
+
+    /**
+     * Maps a field name to a replacement method that should be invoked instead of writing the
+     * field.
+     */
+    private final Map<String, String> fieldToWriteMethod = new HashMap<String, String>();
+
+    /**
+     * This normal no-arguments constructor, or null. By the end of the transformation
+     * this will be converted into an ordinary method.
+     */
+    private MethodNode originalConstructor;
+
+    private final MethodNode newConstructor;
+
+    private final InstructionBuilder constructorBuilder;
+
+    private String instanceContextFieldName;
+
+    private Class<?> transformedClass;
+
+    // Indexes used to identify fields or methods in the shim
+    private int nextFieldIndex = 0;
+
+    private int nextMethodIndex = 0;
+
+    // Set of fields that need to contribute to the shim and gain access to it
+
+    private final Set<PlasticFieldImpl> shimFields = PlasticInternalUtils.newSet();
+
+    // Set of methods that need to contribute to the shim and gain access to it
+
+    private final Set<PlasticMethodImpl> shimMethods = PlasticInternalUtils.newSet();
+
+    /**
+     * @param classNode
+     * @param pool
+     * @param parentMethodBundle
+     * @param parentStaticContext
+     */
+    public PlasticClassImpl(ClassNode classNode, PlasticClassPool pool, MethodBundle parentMethodBundle,
+            StaticContext parentStaticContext)
+    {
+        this.classNode = classNode;
+        this.pool = pool;
+
+        staticContext = parentStaticContext.dupe();
+
+        annotationAccess = pool.createAnnotationAccess(classNode.visibleAnnotations);
+
+        className = PlasticInternalUtils.toClassName(classNode.name);
+        superClassName = PlasticInternalUtils.toClassName(classNode.superName);
+
+        methodBundle = parentMethodBundle.createChild(className);
+
+        methods = new ArrayList(classNode.methods.size());
+
+        String invalidConstructorMessage = String.format(
+                "Class %s has been transformed and may not be directly instantiated.", className);
+
+        for (MethodNode node : (List<MethodNode>) classNode.methods)
+        {
+            if (node.name.equals(CONSTRUCTOR_NAME))
+            {
+                if (node.desc.equals(NOTHING_TO_VOID))
+                {
+                    originalConstructor = node;
+                    fieldTransformMethods.add(node);
+                }
+                else
+                {
+                    node.instructions.clear();
+
+                    newBuilder(node).throwException(IllegalStateException.class, invalidConstructorMessage);
+                }
+
+                continue;
+            }
+
+            if (Modifier.isStatic(node.access))
+                continue;
+
+            if (!Modifier.isAbstract(node.access))
+                fieldTransformMethods.add(node);
+
+            PlasticMethodImpl pmi = new PlasticMethodImpl(node);
+
+            methods.add(pmi);
+            description2method.put(pmi.getDescription(), pmi);
+
+            if (isInheritableMethod(node))
+                methodBundle.addMethod(node.name, node.desc);
+
+            methodNames.add(node.name);
+        }
+
+        Collections.sort(methods);
+
+        fields = new ArrayList(classNode.fields.size());
+
+        for (FieldNode node : (List<FieldNode>) classNode.fields)
+        {
+            fieldNames.add(node.name);
+
+            // Ignore static fields.
+
+            if (Modifier.isStatic(node.access))
+                continue;
+
+            // TODO: Perhaps we should defer this private check until it is needed,
+            // i.e., when we do some work on the field that requires it to be private?
+            // However, given class loading issues, public fields are likely to cause
+            // their own problems when passing the ClassLoader boundrary.
+
+            if (!Modifier.isPrivate(node.access))
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Field %s of class %s is not private. Class transformation requires that all instance fields be private.",
+                                node.name, className));
+
+            fields.add(new PlasticFieldImpl(node));
+        }
+
+        Collections.sort(fields);
+
+        // TODO: Make the output class's constructor protected, and create a shim class to instantiate it
+        // efficiently (without reflection).
+        newConstructor = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, CONSTRUCTOR_DESC, null, null);
+        constructorBuilder = newBuilder(newConstructor);
+
+        // Start by calling the super-class no args constructor
+
+        if (parentMethodBundle.isTransformed())
+        {
+            // If the parent is transformed, our first step is always to invoke its constructor.
+
+            constructorBuilder.loadThis().loadArgument(0).loadArgument(1);
+            constructorBuilder.invokeConstructor(superClassName, StaticContext.class.getName(),
+                    InstanceContext.class.getName());
+        }
+        else
+        {
+            // Assumes the base class includes a visible constructor that takes no arguments.
+            // TODO: Do a proper check for this case and throw a meaningful exception
+            // if not present.
+
+            constructorBuilder.loadThis().invokeConstructor(superClassName);
+        }
+
+        // During the transformation, we'll be adding code to the constructor to pull values
+        // out of the static or instance context and assign them to fields.
+
+        // Later on, we'll add the RETURN opcode
+    }
+
+    public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
+    {
+        check();
+
+        return annotationAccess.hasAnnotation(annotationType);
+    }
+
+    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
+    {
+        check();
+
+        return annotationAccess.getAnnotation(annotationType);
+    }
+
+    public PlasticClass proxyInterface(Class interfaceType, PlasticField field)
+    {
+        check();
+
+        assert field != null;
+
+        introduceInterface(interfaceType);
+
+        for (Method m : interfaceType.getMethods())
+        {
+            introduceMethod(m).delegateTo(field);
+        }
+
+        return this;
+    }
+
+    public ClassInstantiator createInstantiator()
+    {
+        lock();
+
+        interceptFieldAccess();
+
+        createShimIfNeeded();
+
+        rewriteAdvisedMethods();
+
+        completeConstructor();
+
+        transformedClass = pool.realizeTransformedClass(classNode, methodBundle, staticContext);
+
+        return createInstantiatorFromClass(transformedClass);
+    }
+
+    private ClassInstantiator createInstantiatorFromClass(Class clazz)
+    {
+        try
+        {
+            Constructor ctor = clazz.getConstructor(StaticContext.class, InstanceContext.class);
+
+            return new ClassInstantiatorImpl(clazz, ctor, staticContext);
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(String.format("Unable to create ClassInstantiator for class %s: %s",
+                    clazz.getName(), PlasticInternalUtils.toMessage(ex)), ex);
+        }
+    }
+
+    private void completeConstructor()
+    {
+        if (originalConstructor != null)
+        {
+            // Convert the original constructor into a private method invoked from the
+            // generated constructor.
+
+            String initializerName = makeUnique(methodNames, "initializeInstance");
+
+            originalConstructor.access = ACC_PRIVATE;
+            originalConstructor.name = initializerName;
+
+            stripOutSuperConstructorCall(originalConstructor);
+
+            constructorBuilder.loadThis().invokeVirtual(className, "void", initializerName);
+        }
+
+        constructorBuilder.returnResult();
+
+        classNode.methods.add(newConstructor);
+    }
+
+    private void stripOutSuperConstructorCall(MethodNode cons)
+    {
+        InsnList ins = cons.instructions;
+
+        ListIterator li = ins.iterator();
+
+        // Look for the ALOAD 0 (i.e., push this on the stack)
+        while (li.hasNext())
+        {
+            AbstractInsnNode node = (AbstractInsnNode) li.next();
+
+            if (node.getOpcode() == ALOAD)
+            {
+                VarInsnNode varNode = (VarInsnNode) node;
+
+                assert varNode.var == 0;
+
+                // Remove the ALOAD
+                li.remove();
+                break;
+            }
+        }
+
+        // Look for the call to the super-class, an INVOKESPECIAL
+        while (li.hasNext())
+        {
+            AbstractInsnNode node = (AbstractInsnNode) li.next();
+
+            if (node.getOpcode() == INVOKESPECIAL)
+            {
+                MethodInsnNode mnode = (MethodInsnNode) node;
+
+                assert mnode.owner.equals(classNode.superName);
+                assert mnode.name.equals(CONSTRUCTOR_NAME);
+                assert mnode.desc.equals(cons.desc);
+
+                li.remove();
+                return;
+            }
+        }
+
+        throw new AssertionError("Could not convert constructor to simple method.");
+    }
+
+    public <T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T> annotationType)
+    {
+        check();
+
+        List<PlasticField> result = getUnclaimedFields();
+
+        Iterator<PlasticField> iterator = result.iterator();
+
+        while (iterator.hasNext())
+        {
+            PlasticField plasticField = iterator.next();
+
+            if (!plasticField.hasAnnotation(annotationType))
+                iterator.remove();
+        }
+
+        return result;
+    }
+
+    public List<PlasticField> getAllFields()
+    {
+        check();
+
+        return new ArrayList<PlasticField>(fields);
+    }
+
+    public List<PlasticField> getUnclaimedFields()
+    {
+        check();
+
+        // Initially null, and set back to null by PlasticField.claim().
+
+        if (unclaimedFields == null)
+        {
+            unclaimedFields = new ArrayList<PlasticField>(fields.size());
+
+            for (PlasticField f : fields)
+            {
+                if (!f.isClaimed())
+                    unclaimedFields.add(f);
+            }
+        }
+
+        return unclaimedFields;
+    }
+
+    public PlasticField introduceField(String className, String suggestedName)
+    {
+        check();
+
+        assert PlasticInternalUtils.isNonBlank(className);
+        assert PlasticInternalUtils.isNonBlank(suggestedName);
+
+        String name = makeUnique(fieldNames, suggestedName);
+
+        // No signature and no initial value
+
+        FieldNode fieldNode = new FieldNode(ACC_PRIVATE, name, PlasticInternalUtils.toDescriptor(className), null, null);
+
+        classNode.fields.add(fieldNode);
+
+        fieldNames.add(name);
+
+        PlasticFieldImpl newField = new PlasticFieldImpl(fieldNode);
+
+        return newField;
+    }
+
+    public PlasticField introduceField(Class fieldType, String suggestedName)
+    {
+        assert fieldType != null;
+
+        return introduceField(fieldType.getName(), suggestedName);
+    }
+
+    private String makeUnique(Set<String> values, String input)
+    {
+        return values.contains(input) ? input + "$" + PlasticUtils.nextUID() : input;
+    }
+
+    public <T extends Annotation> List<PlasticMethod> getMethodsWithAnnotation(Class<T> annotationType)
+    {
+        check();
+
+        List<PlasticMethod> result = getMethods();
+        Iterator<PlasticMethod> iterator = result.iterator();
+
+        while (iterator.hasNext())
+        {
+            PlasticMethod method = iterator.next();
+
+            if (!method.hasAnnotation(annotationType))
+                iterator.remove();
+        }
+
+        return result;
+    }
+
+    public List<PlasticMethod> getMethods()
+    {
+        check();
+
+        return new ArrayList<PlasticMethod>(methods);
+    }
+
+    public PlasticMethod introduceMethod(MethodDescription description)
+    {
+        check();
+
+        PlasticMethod result = description2method.get(description);
+
+        if (result == null)
+        {
+            result = createNewMethod(description);
+
+            description2method.put(description, result);
+        }
+
+        // Note that is it not necessary to add the new MethodNode to
+        // fieldTransformMethods (the default implementations provided by introduceMethod() do not
+        // ever access instance fields) ... unless the caller invokes changeImplementation().
+
+        return result;
+    }
+
+    public PlasticMethod introduceMethod(Method method)
+    {
+        check();
+
+        return introduceMethod(new MethodDescription(method).withModifiers(method.getModifiers() & ~Modifier.ABSTRACT));
+    }
+
+    private void addMethod(MethodNode methodNode)
+    {
+        classNode.methods.add(methodNode);
+
+        methodNames.add(methodNode.name);
+    }
+
+    private PlasticMethod createNewMethod(MethodDescription description)
+    {
+        if (Modifier.isAbstract(description.modifiers) || Modifier.isStatic(description.modifiers))
+            throw new IllegalArgumentException(String.format(
+                    "Unable to introduce method '%s' into class %s: introduced methods may not be abstract or static.",
+                    description, className));
+
+        String desc = nameCache.toDesc(description);
+
+        String[] exceptions = new String[description.checkedExceptionTypes.length];
+        for (int i = 0; i < exceptions.length; i++)
+        {
+            exceptions[i] = PlasticInternalUtils.toInternalName(description.checkedExceptionTypes[i]);
+        }
+
+        MethodNode methodNode = new MethodNode(description.modifiers, description.methodName, desc, null, exceptions);
+        boolean isOverride = methodBundle.isImplemented(methodNode.name, desc);
+
+        if (isOverride)
+            createOverrideOfBaseClassImpl(description, methodNode);
+        else
+            createNewMethodImpl(description, methodNode);
+
+        classNode.methods.add(methodNode);
+
+        if (!Modifier.isPrivate(description.modifiers))
+            methodBundle.addMethod(description.methodName, desc);
+
+        return new PlasticMethodImpl(methodNode);
+    }
+
+    private void createNewMethodImpl(MethodDescription methodDescription, MethodNode methodNode)
+    {
+        newBuilder(methodDescription, methodNode).returnDefaultValue();
+    }
+
+    private void createOverrideOfBaseClassImpl(MethodDescription methodDescription, MethodNode methodNode)
+    {
+        InstructionBuilder builder = newBuilder(methodDescription, methodNode);
+
+        builder.loadThis();
+        builder.loadArguments();
+        builder.invokeSpecial(superClassName, methodDescription);
+        builder.returnResult();
+    }
+
+    /**
+     * Iterates over all non-introduced methods, including the original constructor. For each
+     * method, the bytecode is
+     * scanned for field reads and writes. When a match is found against an intercepted field, the
+     * operation is
+     * replaced with a method invocation.
+     */
+    private void interceptFieldAccess()
+    {
+        for (MethodNode node : fieldTransformMethods)
+        {
+            interceptFieldAccess(node);
+        }
+    }
+
+    /**
+     * Determines if any fields or methods have provided FieldHandles or MethodHandles; if so
+     * a shim class must be created to facilitate read/write access to fields, or invocation of methods.
+     */
+    private void createShimIfNeeded()
+    {
+        if (shimFields.isEmpty() && shimMethods.isEmpty())
+            return;
+
+        PlasticClassHandleShim shim = createShimInstance();
+
+        installShim(shim);
+    }
+
+    public void installShim(PlasticClassHandleShim shim)
+    {
+        for (PlasticFieldImpl f : shimFields)
+        {
+            f.installShim(shim);
+        }
+
+        for (PlasticMethodImpl m : shimMethods)
+        {
+            m.installShim(shim);
+        }
+    }
+
+    public PlasticClassHandleShim createShimInstance()
+    {
+        String shimClassName = String.format("%s$Shim_%s", classNode.name, PlasticUtils.nextUID());
+
+        ClassNode shimClassNode = new ClassNode();
+
+        shimClassNode.visit(V1_5, ACC_PUBLIC | ACC_FINAL, shimClassName, null, HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME,
+                null);
+
+        implementConstructor(shimClassNode);
+
+        if (!shimFields.isEmpty())
+        {
+            implementShimGet(shimClassNode);
+            implementShimSet(shimClassNode);
+        }
+
+        if (!shimMethods.isEmpty())
+        {
+            implementShimInvoke(shimClassNode);
+        }
+
+        return instantiateShim(shimClassNode);
+    }
+
+    private void implementConstructor(ClassNode shimClassNode)
+    {
+        MethodNode mn = new MethodNode(ACC_PUBLIC, CONSTRUCTOR_NAME, NOTHING_TO_VOID, null, null);
+
+        InstructionBuilder builder = newBuilder(mn);
+
+        builder.loadThis().invokeConstructor(PlasticClassHandleShim.class).returnResult();
+
+        shimClassNode.methods.add(mn);
+
+    }
+
+    private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
+    {
+        Class shimClass = pool.realize(shimClassNode);
+
+        try
+        {
+            return (PlasticClassHandleShim) shimClass.newInstance();
+        }
+        catch (Exception ex)
+        {
+            throw new RuntimeException(
+                    String.format("Unable to instantiate shim class %s for plastic class %s: %s",
+                            PlasticInternalUtils.toClassName(shimClassNode.name), className,
+                            PlasticInternalUtils.toMessage(ex)), ex);
+        }
+    }
+
+    private void implementShimGet(ClassNode shimClassNode)
+    {
+        MethodNode mn = new MethodNode(ACC_PUBLIC, "get", OBJECT_INT_TO_OBJECT, null, null);
+
+        InstructionBuilder builder = newBuilder(mn);
+
+        // Arg 0 is the target instance
+        // Arg 1 is the index
+
+        builder.loadArgument(0).checkcast(className);
+        builder.loadArgument(1);
+
+        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
+        {
+            public void doSwitch(SwitchBlock block)
+            {
+                for (PlasticFieldImpl f : shimFields)
+                {
+                    f.extendShimGet(block);
+                }
+            }
+        });
+
+        shimClassNode.methods.add(mn);
+    }
+
+    private void implementShimSet(ClassNode shimClassNode)
+    {
+        MethodNode mn = new MethodNode(ACC_PUBLIC, "set", OBJECT_INT_OBJECT_TO_VOID, null, null);
+
+        InstructionBuilder builder = newBuilder(mn);
+
+        // Arg 0 is the target instance
+        // Arg 1 is the index
+        // Arg 2 is the new value
+
+        builder.loadArgument(0).checkcast(className);
+        builder.loadArgument(2);
+
+        builder.loadArgument(1);
+
+        builder.startSwitch(0, nextFieldIndex - 1, new SwitchCallback()
+        {
+            public void doSwitch(SwitchBlock block)
+            {
+                for (PlasticFieldImpl f : shimFields)
+                {
+                    f.extendShimSet(block);
+                }
+            }
+        });
+
+        builder.returnResult();
+
+        shimClassNode.methods.add(mn);
+    }
+
+    private void implementShimInvoke(ClassNode shimClassNode)
+    {
+        MethodNode mn = new MethodNode(ACC_PUBLIC, "invoke", OBJECT_INT_OBJECT_ARRAY_TO_METHOD_INVOCATION_RESULT, null,
+                null);
+
+        InstructionBuilder builder = newBuilder(mn);
+
+        // Arg 0 is the target instance
+        // Arg 1 is the index
+        // Arg 2 is the object array of parameters
+
+        builder.loadArgument(0).checkcast(className);
+
+        builder.loadArgument(1);
+
+        builder.startSwitch(0, nextMethodIndex - 1, new SwitchCallback()
+        {
+            public void doSwitch(SwitchBlock block)
+            {
+                for (PlasticMethodImpl m : shimMethods)
+                {
+                    m.extendShimInvoke(block);
+                }
+            }
+        });
+
+        shimClassNode.methods.add(mn);
+    }
+
+    private void rewriteAdvisedMethods()
+    {
+        for (PlasticMethodImpl method : advisedMethods)
+        {
+            method.rewriteMethodForAdvice();
+        }
+    }
+
+    private void interceptFieldAccess(MethodNode methodNode)
+    {
+        InsnList insns = methodNode.instructions;
+
+        ListIterator it = insns.iterator();
+
+        while (it.hasNext())
+        {
+            AbstractInsnNode node = (AbstractInsnNode) it.next();
+
+            int opcode = node.getOpcode();
+
+            if (opcode != GETFIELD && opcode != PUTFIELD)
+                continue;
+
+            // Make sure we're talking about access to a field of this class, not some other
+            // visible field of another class.
+
+            FieldInsnNode fnode = (FieldInsnNode) node;
+
+            if (!fnode.owner.equals(classNode.name))
+                continue;
+
+            Map<String, String> fieldToMethod = opcode == GETFIELD ? fieldToReadMethod : fieldToWriteMethod;
+
+            String methodName = fieldToMethod.get(fnode.name);
+
+            if (methodName == null)
+                continue;
+
+            String methodDescription = opcode == GETFIELD ? "()" + fnode.desc : "(" + fnode.desc + ")V";
+
+            // Replace the field access node with the appropriate method invocation.
+
+            insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, fnode.owner, methodName, methodDescription));
+
+            it.remove();
+        }
+    }
+
+    private String getInstanceContextFieldName()
+    {
+        if (instanceContextFieldName == null)
+        {
+            instanceContextFieldName = makeUnique(fieldNames, "instanceContext");
+
+            // TODO: Once we support inheritance, we could use a protected field and only initialize
+            // it once, in the first base class where it is needed.
+
+            FieldNode node = new FieldNode(ACC_PRIVATE | ACC_FINAL, instanceContextFieldName, INSTANCE_CONTEXT_DESC,
+                    null, null);
+
+            classNode.fields.add(node);
+
+            // Extend the constructor to store the context in a field.
+
+            constructorBuilder.loadThis().loadArgument(1)
+                    .putField(className, instanceContextFieldName, InstanceContext.class);
+        }
+
+        return instanceContextFieldName;
+    }
+
+    /** Creates a new private final field and initializes its value (using the StaticContext). */
+    private String createAndInitializeFieldFromStaticContext(String suggestedFieldName, String fieldType,
+            Object injectedFieldValue)
+    {
+        String name = makeUnique(fieldNames, suggestedFieldName);
+
+        FieldNode field = new FieldNode(ACC_PRIVATE | ACC_FINAL, name, nameCache.toDesc(fieldType), null, null);
+
+        classNode.fields.add(field);
+
+        initializeFieldFromStaticContext(name, fieldType, injectedFieldValue);
+
+        return name;
+    }
+
+    /**
+     * Initializes a field from the static context. The injected value is added to the static
+     * context and the class constructor updated to assign the value from the context (which includes casting and
+     * possibly unboxing).
+     */
+    private void initializeFieldFromStaticContext(String fieldName, String fieldType, Object injectedFieldValue)
+    {
+        int index = staticContext.store(injectedFieldValue);
+
+        // Although it feels nicer to do the loadThis() later and then swap() that breaks
+        // on primitive longs and doubles, so its just easier to do the loadThis() first
+        // so its at the right place on the stack for the putField().
+
+        constructorBuilder.loadThis();
+
+        constructorBuilder.loadArgument(0).loadConstant(index);
+        constructorBuilder.invoke(StaticContext.class, Object.class, "get", int.class);
+        constructorBuilder.castOrUnbox(fieldType);
+
+        constructorBuilder.putField(className, fieldName, fieldType);
+    }
+
+    private void pushInstanceContextFieldOntoStack(InstructionBuilder builder)
+    {
+        builder.loadThis().getField(className, getInstanceContextFieldName(), InstanceContext.class);
+    }
+
+    public PlasticClass getPlasticClass()
+    {
+        return this;
+    }
+
+    public Class<?> getTransformedClass()
+    {
+        if (transformedClass == null)
+            throw new IllegalStateException(String.format(
+                    "Transformed class %s is not yet available because the transformation is not yet complete.",
+                    className));
+
+        return transformedClass;
+    }
+
+    private boolean isInheritableMethod(MethodNode node)
+    {
+        return (node.access & (ACC_ABSTRACT | ACC_PRIVATE)) == 0;
+    }
+
+    public String getClassName()
+    {
+        return className;
+    }
+
+    private InstructionBuilderImpl newBuilder(MethodNode mn)
+    {
+        return newBuilder(PlasticInternalUtils.toMethodDescription(mn), mn);
+    }
+
+    private InstructionBuilderImpl newBuilder(MethodDescription description, MethodNode mn)
+    {
+        return new InstructionBuilderImpl(description, mn, nameCache);
+    }
+
+    public Set<PlasticMethod> introduceInterface(Class interfaceType)
+    {
+        check();
+
+        assert interfaceType != null;
+
+        if (!interfaceType.isInterface())
+            throw new IllegalArgumentException(String.format(
+                    "Class %s is not an interface; ony interfaces may be introduced.", interfaceType.getName()));
+
+        String interfaceName = nameCache.toInternalName(interfaceType);
+
+        // I suppose this means that a subclass may restate that it implements an interface from a base class.
+
+        if (!classNode.interfaces.contains(interfaceName))
+            classNode.interfaces.add(interfaceName);
+
+        Set<PlasticMethod> introducedMethods = new HashSet<PlasticMethod>();
+
+        for (Method m : interfaceType.getMethods())
+        {
+            MethodDescription description = new MethodDescription(m);
+
+            if (!isMethodImplemented(description))
+            {
+                introducedMethods.add(introduceMethod(m));
+            }
+        }
+
+        return introducedMethods;
+    }
+
+    public PlasticClass addToString(final String toStringValue)
+    {
+        check();
+
+        if (!isMethodImplemented(TO_STRING_METHOD_DESCRIPTION))
+        {
+            introduceMethod(TO_STRING_METHOD_DESCRIPTION).changeImplementation(new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.loadConstant(toStringValue).returnResult();
+                }
+            });
+        }
+
+        return this;
+    }
+
+    /**
+     * Returns true if this class has an implementation of the indicated method, or a super-class provides
+     * a non-abstract implementation.
+     */
+    private boolean isMethodImplemented(MethodDescription description)
+    {
+        return methodBundle.isImplemented(description.methodName, nameCache.toDesc(description));
+    }
+
+}

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java?rev=1086644&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassLoader.java Tue Mar 29 17:24:31 2011
@@ -0,0 +1,55 @@
+// Copyright 2011 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.internal.plastic;
+
+public class PlasticClassLoader extends ClassLoader
+{
+    private final ClassLoaderDelegate delegate;
+
+    public PlasticClassLoader(ClassLoader parent, ClassLoaderDelegate delegate)
+    {
+        super(parent);
+
+        this.delegate = delegate;
+    }
+
+    @Override
+    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
+    {
+        Class<?> loadedClass = findLoadedClass(name);
+
+        if (loadedClass != null)
+            return loadedClass;
+
+        if (delegate.shouldInterceptClassLoading(name))
+        {
+            Class<?> c = delegate.loadAndTransformClass(name);
+
+            if (resolve)
+                resolveClass(c);
+
+            return c;
+        }
+        else
+        {
+            return super.loadClass(name, resolve);
+        }
+    }
+
+    Class<?> defineClassWithBytecode(String className, byte[] bytecode)
+    {
+        return defineClass(className, bytecode, 0, bytecode.length);
+    }
+}