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