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();
+ }
+
+}