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/08/10 02:51:16 UTC

svn commit: r1155986 [2/2] - in /tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic: MethodAdviceManager.java MethodParameterImpl.java PlasticClassImpl.java PlasticFieldImpl.java PlasticMember.java PlasticMethodImpl.java

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java?rev=1155986&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java Wed Aug 10 00:51:16 2011
@@ -0,0 +1,562 @@
+// 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 org.apache.tapestry5.internal.plastic.asm.*;
+import org.apache.tapestry5.internal.plastic.asm.Opcodes;
+import org.apache.tapestry5.internal.plastic.asm.tree.FieldNode;
+import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode;
+import org.apache.tapestry5.plastic.*;
+
+import java.lang.reflect.Modifier;
+
+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 MethodNode getAccess, setAccess;
+
+    private FieldState state = FieldState.INITIAL;
+
+    private int fieldIndex = -1;
+
+    public PlasticFieldImpl(PlasticClassImpl plasticClass, FieldNode node)
+    {
+        super(plasticClass, 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, plasticClass.className);
+    }
+
+    public String getGenericSignature()
+    {
+        return node.signature;
+    }
+
+    public int getModifiers()
+    {
+        return node.access;
+    }
+
+    public int compareTo(PlasticFieldImpl o)
+    {
+        return this.node.name.compareTo(o.node.name);
+    }
+
+    public PlasticClass getPlasticClass()
+    {
+        plasticClass.check();
+
+        return plasticClass;
+    }
+
+    public FieldHandle getHandle()
+    {
+        plasticClass.check();
+
+        if (handle == null)
+        {
+            fieldIndex = plasticClass.nextFieldIndex++;
+
+            // The shim gets assigned later
+
+            handle = new FieldHandleImpl(plasticClass.className, node.name, fieldIndex);
+
+            plasticClass.shimFields.add(this);
+        }
+
+        return handle;
+    }
+
+    public PlasticField claim(Object tag)
+    {
+        assert tag != null;
+
+        plasticClass.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,
+                    plasticClass.className, tag, this.tag));
+
+        this.tag = tag;
+
+        // Force the list of unclaimed fields to be recomputed on next access
+
+        plasticClass.unclaimedFields = null;
+
+        return this;
+    }
+
+    public boolean isClaimed()
+    {
+        plasticClass.check();
+
+        return tag != null;
+    }
+
+    public String getName()
+    {
+        plasticClass.check();
+
+        return node.name;
+    }
+
+    public String getTypeName()
+    {
+        plasticClass.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, plasticClass.className, state.description));
+    }
+
+    public PlasticField inject(Object value)
+    {
+        plasticClass.check();
+
+        verifyInitialState("inject a value into");
+
+        assert value != null;
+
+        plasticClass.initializeFieldFromStaticContext(node.name, typeName, value);
+
+        makeReadOnly();
+
+        state = FieldState.INJECTED;
+
+        return this;
+    }
+
+    public PlasticField injectComputed(ComputedValue<?> computedValue)
+    {
+        plasticClass.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 = plasticClass.staticContext.store(computedValue);
+
+        plasticClass.constructorBuilder.loadThis(); // for the putField()
+
+        // Get the ComputedValue out of the StaticContext and onto the stack
+
+        plasticClass.constructorBuilder.loadArgument(0).loadConstant(index);
+        plasticClass.constructorBuilder.invoke(PlasticClassImpl.STATIC_CONTEXT_GET_METHOD).checkcast(ComputedValue.class);
+
+        // Add the InstanceContext to the stack
+
+        plasticClass.constructorBuilder.loadArgument(1);
+        plasticClass.constructorBuilder.invoke(PlasticClassImpl.COMPUTED_VALUE_GET_METHOD).castOrUnbox(typeName);
+
+        plasticClass.constructorBuilder.putField(plasticClass.className, node.name, typeName);
+    }
+
+    public PlasticField injectFromInstanceContext()
+    {
+        plasticClass.check();
+
+        verifyInitialState("inject instance context value into");
+
+        // Easiest to load this, for the putField(), early, in case the field is
+        // wide (long or double primitive)
+
+        plasticClass.constructorBuilder.loadThis();
+
+        // Add the InstanceContext to the stack
+
+        plasticClass.constructorBuilder.loadArgument(1);
+        plasticClass.constructorBuilder.loadConstant(typeName);
+
+        plasticClass.constructorBuilder.invokeStatic(PlasticInternalUtils.class, Object.class, "getFromInstanceContext",
+                InstanceContext.class, String.class).castOrUnbox(typeName);
+
+        plasticClass.constructorBuilder.putField(plasticClass.className, node.name, typeName);
+
+        makeReadOnly();
+
+        state = FieldState.INJECTED;
+
+        return this;
+    }
+
+    public <F> PlasticField setConduit(FieldConduit<F> conduit)
+    {
+        assert conduit != null;
+
+        plasticClass.check();
+
+        verifyInitialState("set the FieldConduit for");
+
+        // First step: define a field to store the conduit and add constructor logic
+        // to initialize it
+
+        String conduitFieldName = plasticClass.createAndInitializeFieldFromStaticContext(node.name + "_FieldConduit",
+                FieldConduit.class.getName(), conduit);
+
+        replaceFieldReadAccess(conduitFieldName);
+        replaceFieldWriteAccess(conduitFieldName);
+
+        state = FieldState.CONDUIT;
+
+        return this;
+    }
+
+    public <F> PlasticField setComputedConduit(ComputedValue<FieldConduit<F>> computedConduit)
+    {
+        assert computedConduit != null;
+
+        plasticClass.check();
+
+        verifyInitialState("set the computed FieldConduit for");
+
+        // First step: define a field to store the conduit and add constructor logic
+        // to initialize it
+
+        PlasticField conduitField = plasticClass.introduceField(FieldConduit.class, node.name + "_FieldConduit").injectComputed(
+                computedConduit);
+
+        replaceFieldReadAccess(conduitField.getName());
+        replaceFieldWriteAccess(conduitField.getName());
+
+        // TODO: Do we keep the field or not? It will now always be null/0/false.
+
+        state = FieldState.CONDUIT;
+
+        return this;
+    }
+
+    public PlasticField createAccessors(PropertyAccessType accessType)
+    {
+        plasticClass.check();
+
+        return createAccessors(accessType, PlasticInternalUtils.toPropertyName(node.name));
+    }
+
+    public PlasticField createAccessors(PropertyAccessType accessType, String propertyName)
+    {
+        plasticClass.check();
+
+        assert accessType != null;
+        assert PlasticInternalUtils.isNonBlank(propertyName);
+
+        String capitalized = PlasticInternalUtils.capitalize(propertyName);
+
+        if (accessType != PropertyAccessType.WRITE_ONLY)
+        {
+            String signature = node.signature == null ? null : "()" + node.signature;
+
+            introduceAccessorMethod(getTypeName(), "get" + capitalized, null, signature,
+                    new InstructionBuilderCallback()
+                    {
+                        public void doBuild(InstructionBuilder builder)
+                        {
+                            builder.loadThis().getField(PlasticFieldImpl.this).returnResult();
+                        }
+                    });
+        }
+
+        if (accessType != PropertyAccessType.READ_ONLY)
+        {
+            String signature = node.signature == null ? null : "(" + node.signature + ")V";
+
+            introduceAccessorMethod("void", "set" + capitalized, new String[]
+                    {getTypeName()}, signature, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.loadThis().loadArgument(0);
+                    builder.putField(plasticClass.className, node.name, getTypeName());
+                    builder.returnResult();
+                }
+            });
+        }
+
+        return this;
+    }
+
+    private void introduceAccessorMethod(String returnType, String name, String[] parameterTypes, String signature,
+                                         InstructionBuilderCallback callback)
+    {
+        MethodDescription description = new MethodDescription(org.apache.tapestry5.internal.plastic.asm.Opcodes.ACC_PUBLIC, returnType, name, parameterTypes,
+                signature, null);
+
+        String desc = plasticClass.nameCache.toDesc(description);
+
+        if (plasticClass.inheritanceData.isImplemented(name, desc))
+            throw new IllegalArgumentException(String.format(
+                    "Unable to create new accessor method %s on class %s as the method is already implemented.",
+                    description.toString(), plasticClass.className));
+
+        plasticClass.introduceMethod(description, callback);
+    }
+
+    private void replaceFieldWriteAccess(String conduitFieldName)
+    {
+        String setAccessName = plasticClass.makeUnique(plasticClass.methodNames, "set_" + node.name);
+
+        setAccess = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, setAccessName, "(" + node.desc + ")V", null, null);
+
+        InstructionBuilder builder = plasticClass.newBuilder(setAccess);
+
+        pushFieldConduitOntoStack(conduitFieldName, builder);
+
+        builder.loadThis();
+
+        plasticClass.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", Object.class, InstanceContext.class, Object.class);
+
+        if (isWriteBehindEnabled())
+        {
+            builder.loadThis().loadArgument(0).putField(plasticClass.className, node.name, typeName);
+        }
+
+        builder.returnResult();
+
+        plasticClass.addMethod(setAccess);
+
+        plasticClass.fieldToWriteMethod.put(node.name, setAccess);
+    }
+
+    private void replaceFieldReadAccess(String conduitFieldName)
+    {
+        boolean writeBehindEnabled = isWriteBehindEnabled();
+
+        String getAccessName = plasticClass.makeUnique(plasticClass.methodNames, "getfieldvalue_" + node.name);
+
+        getAccess = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, getAccessName, "()" + node.desc, null, null);
+
+        InstructionBuilder builder = plasticClass.newBuilder(getAccess);
+
+        // Get the correct FieldConduit object on the stack
+
+        pushFieldConduitOntoStack(conduitFieldName, builder);
+
+        builder.loadThis();
+
+        // Now push the instance context on the stack
+
+        plasticClass.pushInstanceContextFieldOntoStack(builder);
+
+        builder.invoke(FieldConduit.class, Object.class, "get", Object.class, InstanceContext.class).castOrUnbox(
+                typeName);
+
+        if (writeBehindEnabled)
+        {
+            // Dupe the value, then push this, then swap
+
+            if (isWide())
+            {
+                // Dupe this under the wide value, then pop the wide value
+
+                builder.dupeWide().loadThis().dupe(2).pop();
+            } else
+            {
+                builder.dupe().loadThis().swap();
+            }
+
+            // At which point the stack is the result value, this, the result value
+
+            builder.putField(plasticClass.className, node.name, typeName);
+
+            // And now it is just the result value
+        }
+
+        builder.returnResult();
+
+        plasticClass.addMethod(getAccess);
+
+        plasticClass.fieldToReadMethod.put(node.name, getAccess);
+    }
+
+    private boolean isWriteBehindEnabled()
+    {
+        return plasticClass.pool.isEnabled(TransformationOption.FIELD_WRITEBEHIND);
+    }
+
+    private boolean isWide()
+    {
+        PrimitiveType pt = PrimitiveType.getByName(typeName);
+
+        return pt != null && pt.isWide();
+    }
+
+    private void pushFieldConduitOntoStack(String conduitFileName, InstructionBuilder builder)
+    {
+        builder.loadThis();
+        builder.getField(plasticClass.className, conduitFileName, FieldConduit.class);
+    }
+
+    private void makeReadOnly()
+    {
+        String setAccessName = plasticClass.makeUnique(plasticClass.methodNames, "setfieldvalue_" + node.name);
+
+        setAccess = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, setAccessName, "(" + node.desc + ")V", null, null);
+
+        String message = String.format("Field %s of class %s is read-only.", node.name, plasticClass.className);
+
+        plasticClass.newBuilder(setAccess).throwException(IllegalStateException.class, message);
+
+        plasticClass.addMethod(setAccess);
+
+        plasticClass.fieldToWriteMethod.put(node.name, setAccess);
+
+        node.access |= Opcodes.ACC_FINAL;
+    }
+
+    /**
+     * Adds a static setter method, allowing an external FieldAccess implementation
+     * to directly set the value of the field.
+     */
+    private MethodNode addShimSetAccessMethod()
+    {
+        String name = plasticClass.makeUnique(plasticClass.methodNames, "shimset_" + node.name);
+
+        // Takes two Object parameters (instance, and value) and returns void.
+
+        MethodNode mn = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, name, "(" + node.desc + ")V", null, null);
+
+        InstructionBuilder builder = plasticClass.newBuilder(mn);
+
+        builder.loadThis().loadArgument(0).putField(plasticClass.className, node.name, typeName);
+        builder.returnResult();
+
+        plasticClass.addMethod(mn);
+
+        plasticClass.fieldTransformMethods.add(mn);
+
+        return mn;
+    }
+
+    private MethodNode addShimGetAccessMethod()
+    {
+        String name = plasticClass.makeUnique(plasticClass.methodNames, "shimget_" + node.name);
+
+        MethodNode mn = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, name, "()" + node.desc, null, null);
+
+        InstructionBuilder builder = plasticClass.newBuilder(mn);
+
+        builder.loadThis().getField(plasticClass.className, node.name, typeName).returnResult();
+
+        plasticClass.addMethod(mn);
+
+        plasticClass.fieldTransformMethods.add(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)
+    {
+        if (getAccess == null)
+        {
+            getAccess = addShimGetAccessMethod();
+        }
+
+        final String methodToInvoke = getAccess.name;
+
+        plasticClass.shimInvokedMethods.add(getAccess);
+
+        switchBlock.addCase(fieldIndex, false, new InstructionBuilderCallback()
+        {
+            public void doBuild(InstructionBuilder builder)
+            {
+                builder.invokeVirtual(plasticClass.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)
+    {
+        // If no conduit has yet been specified, then we need a set access method for the shim to invoke.
+
+        if (setAccess == null)
+        {
+            setAccess = addShimSetAccessMethod();
+        }
+
+        plasticClass.shimInvokedMethods.add(setAccess);
+
+        final String methodToInvoke = setAccess.name;
+
+        switchBlock.addCase(fieldIndex, true, new InstructionBuilderCallback()
+        {
+
+            public void doBuild(InstructionBuilder builder)
+            {
+                builder.castOrUnbox(typeName);
+                builder.invokeVirtual(plasticClass.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();
+            }
+        });
+    }
+
+}

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMember.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMember.java?rev=1155986&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMember.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMember.java Wed Aug 10 00:51:16 2011
@@ -0,0 +1,49 @@
+// 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 org.apache.tapestry5.internal.plastic.asm.tree.AnnotationNode;
+import org.apache.tapestry5.plastic.AnnotationAccess;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+class PlasticMember implements AnnotationAccess
+{
+    private final AnnotationAccess annotationAccess;
+
+    protected final PlasticClassImpl plasticClass;
+
+    PlasticMember(PlasticClassImpl plasticClass, List visibleAnnotations)
+    {
+        this.plasticClass = plasticClass;
+        annotationAccess = plasticClass.pool.createAnnotationAccess((List<AnnotationNode>) visibleAnnotations);
+    }
+
+    public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
+    {
+        plasticClass.check();
+
+        return annotationAccess.hasAnnotation(annotationType);
+    }
+
+    public <T extends Annotation> T getAnnotation(Class<T> annotationType)
+    {
+        plasticClass.check();
+
+        return annotationAccess.getAnnotation(annotationType);
+    }
+
+}

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMethodImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMethodImpl.java?rev=1155986&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMethodImpl.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticMethodImpl.java Wed Aug 10 00:51:16 2011
@@ -0,0 +1,375 @@
+// 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 org.apache.tapestry5.internal.plastic.asm.Opcodes;
+import org.apache.tapestry5.internal.plastic.asm.tree.MethodNode;
+import org.apache.tapestry5.plastic.*;
+
+import java.lang.reflect.Modifier;
+import java.util.List;
+
+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;
+
+    // Lazily initialized
+    private String methodIdentifier;
+
+    public PlasticMethodImpl(PlasticClassImpl plasticClass, MethodNode node)
+    {
+        super(plasticClass, node.visibleAnnotations);
+
+        this.node = node;
+        this.description = PlasticInternalUtils.toMethodDescription(node);
+    }
+
+    public String toString()
+    {
+        return String.format("PlasticMethod[%s in class %s]", description, plasticClass.className);
+    }
+
+    public PlasticClass getPlasticClass()
+    {
+        plasticClass.check();
+
+        return plasticClass;
+    }
+
+    public MethodDescription getDescription()
+    {
+        plasticClass.check();
+
+        return description;
+    }
+
+    public int compareTo(PlasticMethodImpl o)
+    {
+        plasticClass.check();
+
+        return description.compareTo(o.description);
+    }
+
+    public boolean isOverride()
+    {
+        plasticClass.check();
+
+        return plasticClass.parentInheritanceData.isImplemented(node.name, node.desc);
+    }
+
+    public String getMethodIdentifier()
+    {
+        plasticClass.check();
+
+        if (methodIdentifier == null)
+        {
+            methodIdentifier = String.format("%s.%s",
+                    plasticClass.className,
+                    description.toShortString());
+        }
+
+        return methodIdentifier;
+    }
+
+    public boolean isVoid()
+    {
+        plasticClass.check();
+
+        return description.returnType.equals("void");
+    }
+
+    public MethodHandle getHandle()
+    {
+        plasticClass.check();
+
+        if (handle == null)
+        {
+            methodIndex = plasticClass.nextMethodIndex++;
+            handle = new MethodHandleImpl(plasticClass.className, description.toString(), methodIndex);
+            plasticClass.shimMethods.add(this);
+        }
+
+        return handle;
+    }
+
+    public PlasticMethod changeImplementation(InstructionBuilderCallback callback)
+    {
+        plasticClass.check();
+
+        // If the method is currently abstract, clear that flag.
+        if (Modifier.isAbstract(node.access))
+        {
+            node.access = node.access & ~org.apache.tapestry5.internal.plastic.asm.Opcodes.ACC_ABSTRACT;
+            description = description.withModifiers(node.access);
+        }
+
+        node.instructions.clear();
+
+        plasticClass.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.
+
+        plasticClass.fieldTransformMethods.add(node);
+
+        return this;
+    }
+
+    public PlasticMethod addAdvice(MethodAdvice advice)
+    {
+        plasticClass.check();
+
+        assert advice != null;
+
+        if (adviceManager == null)
+        {
+            adviceManager = new MethodAdviceManager(plasticClass, description, node);
+            plasticClass.advisedMethods.add(this);
+        }
+
+        adviceManager.add(advice);
+
+        return this;
+    }
+
+    public PlasticMethod delegateTo(final PlasticField field)
+    {
+        plasticClass.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
+
+                builder.loadThis().getField(field);
+                builder.loadArguments();
+
+                invokeDelegateAndReturnResult(builder, field.getTypeName());
+            }
+        });
+
+        return this;
+    }
+
+    public PlasticMethod delegateTo(PlasticMethod delegateProvidingMethod)
+    {
+        plasticClass.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(plasticClass.className, providerDescriptor);
+                } else
+                {
+                    builder.invokeVirtual(plasticClass.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(plasticClass,
+                        PlasticClassImpl.safeArrayDeref(node.visibleParameterAnnotations, i),
+                        PlasticClassImpl.safeArrayDeref(description.argumentTypes, i), i));
+            }
+        }
+
+        return parameters;
+    }
+
+    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(Opcodes.ACC_SYNTHETIC | Opcodes.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 = plasticClass.newBuilder(mn);
+
+        builder.loadThis();
+        builder.loadArguments();
+        builder.invokeSpecial(plasticClass.className, description);
+        builder.returnResult();
+
+        plasticClass.addMethod(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(plasticClass.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 = plasticClass.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();
+    }
+
+}