Author: hlship
Date: Mon Jul 25 18:03:11 2011
New Revision: 1150815
URL: http://svn.apache.org/viewvc?rev=1150815&view=rev
Log:
Add PlasticClass.onConstruct() to provide callbacks inside the instance
constructor
Added:
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ConstructorCallback.java
tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ConstructorCallbackTests.groovy
Modified:
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClass.java
Modified:
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java?rev=1150815&r1=1150814&r2=1150815&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
(original)
+++
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
Mon Jul 25 18:03:11 2011
@@ -42,8 +42,6 @@ public class PlasticClassImpl extends Lo
private static final String ABSTRACT_METHOD_INVOCATION_INTERNAL_NAME =
PlasticInternalUtils
.toInternalName(AbstractMethodInvocation.class.getName());
- private static final String OBJECT_INTERNAL_NAME =
Type.getInternalName(Object.class);
-
private static final String HANDLE_SHIM_BASE_CLASS_INTERNAL_NAME = Type
.getInternalName(PlasticClassHandleShim.class);
@@ -60,6 +58,9 @@ public class PlasticClassImpl extends Lo
private static final Method COMPUTED_VALUE_GET_METHOD =
toMethod(ComputedValue.class, "get", InstanceContext.class);
+ private static final Method CONSTRUCTOR_CALLBACK_METHOD =
toMethod(ConstructorCallback.class, "onConstruct",
+ Object.class, InstanceContext.class);
+
private static String toDesc(String internalName)
{
return "L" + internalName + ";";
@@ -1449,6 +1450,8 @@ public class PlasticClassImpl extends Lo
private final Set<String> methodNames = new HashSet<String>();
+ private final List<ConstructorCallback> constructorCallbacks = new
ArrayList<ConstructorCallback>();
+
// All non-introduced instance fields
private final List<PlasticFieldImpl> fields;
@@ -1726,33 +1729,64 @@ public class PlasticClassImpl extends Lo
{
if (originalConstructor != null)
{
- // Convert the original constructor into a private method invoked
from the
- // generated constructor.
+ convertOriginalConstructorToMethod();
+ }
- String initializerName = makeUnique(methodNames,
"initializeInstance");
+ invokeCallbacks();
- int originalAccess = originalConstructor.access;
+ constructorBuilder.returnResult();
- originalConstructor.access = ACC_PRIVATE;
- originalConstructor.name = initializerName;
+ classNode.methods.add(newConstructor);
+ }
- stripOutSuperConstructorCall(originalConstructor);
+ private void invokeCallbacks()
+ {
+ for (ConstructorCallback callback : constructorCallbacks)
+ {
+ invokeCallback(callback);
+ }
+ }
- constructorBuilder.loadThis().invokeVirtual(className, "void",
initializerName);
+ private void invokeCallback(ConstructorCallback callback)
+ {
+ int index = staticContext.store(callback);
- // And replace it with a constructor that throws an exception
+ // First, load the callback
- MethodNode replacementConstructor = new MethodNode(originalAccess,
CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
- null);
+
constructorBuilder.loadArgument(0).loadConstant(index).invoke(STATIC_CONTEXT_GET_METHOD).castOrUnbox(ConstructorCallback.class.getName());
-
newBuilder(replacementConstructor).throwException(IllegalStateException.class,
invalidConstructorMessage());
+ // Load this and the InstanceContext
+ constructorBuilder.loadThis().loadArgument(1);
- classNode.methods.add(replacementConstructor);
- }
+ constructorBuilder.invoke(CONSTRUCTOR_CALLBACK_METHOD);
+ }
- constructorBuilder.returnResult();
- classNode.methods.add(newConstructor);
+ /**
+ * Convert the original constructor into a private method invoked from the
+ * generated constructor.
+ */
+ private void convertOriginalConstructorToMethod()
+ {
+ String initializerName = makeUnique(methodNames, "initializeInstance");
+
+ int originalAccess = originalConstructor.access;
+
+ originalConstructor.access = ACC_PRIVATE;
+ originalConstructor.name = initializerName;
+
+ stripOutSuperConstructorCall(originalConstructor);
+
+ constructorBuilder.loadThis().invokeVirtual(className, "void",
initializerName);
+
+ // And replace it with a constructor that throws an exception
+
+ MethodNode replacementConstructor = new MethodNode(originalAccess,
CONSTRUCTOR_NAME, NOTHING_TO_VOID, null,
+ null);
+
+
newBuilder(replacementConstructor).throwException(IllegalStateException.class,
invalidConstructorMessage());
+
+ classNode.methods.add(replacementConstructor);
}
private void stripOutSuperConstructorCall(MethodNode cons)
@@ -2456,4 +2490,15 @@ public class PlasticClassImpl extends Lo
return superClassName;
}
+ public PlasticClass onConstruct(ConstructorCallback callback)
+ {
+ check();
+
+ assert callback != null;
+
+ constructorCallbacks.add(callback);
+
+ return this;
+ }
+
}
Added:
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ConstructorCallback.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ConstructorCallback.java?rev=1150815&view=auto
==============================================================================
---
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ConstructorCallback.java
(added)
+++
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ConstructorCallback.java
Mon Jul 25 18:03:11 2011
@@ -0,0 +1,29 @@
+// 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.plastic;
+
+/**
+ * Supplies construction-time logic for the class' constructor.
+ */
+public interface ConstructorCallback
+{
+ /**
+ * Invoked at the end of the class constructor to perform any additional
initializations.
+ *
+ * @param instance newly constructed instance
+ * @param context instance context for class
+ */
+ void onConstruct(Object instance, InstanceContext context);
+}
Modified:
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClass.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClass.java?rev=1150815&r1=1150814&r2=1150815&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClass.java
(original)
+++
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClass.java
Mon Jul 25 18:03:11 2011
@@ -24,40 +24,42 @@ import java.util.Set;
* for an imperative style of development: the PlasticClass is provided to
other objects; they can query it
* for relevant fields or methods, and invoke methods that modify the class in
various ways. Ultimately, the
* end result is a {@link ClassInstantiator} used to create instances of the
fully instrumented and transformed class.
- * <p>
+ * <p/>
* The terminology is that a class that is being transformed is "plastic", but
the end result is a normal concrete class
* (albeit in a different class loader).
- * <p>
+ * <p/>
* Implements {@link AnnotationAccess} to provide access to annotations on the
type itself.
- * <p>
+ * <p/>
* This class is expressly <em>not thread safe</em>; only a single thread
should be responsible for operating on a
* PlasticClass.
- * <p>
+ * <p/>
* TODO: what about annotation inheritance?
*/
@SuppressWarnings("rawtypes")
public interface PlasticClass extends AnnotationAccess
{
- /** Returns the fully qualified class name of the class being transformed.
*/
+ /**
+ * Returns the fully qualified class name of the class being transformed.
+ */
String getClassName();
/**
* Matches all fields (claimed or not) that have the given annotation.
Returns the fields in sorted order.
- *
+ *
* @return Unmodifiable List of fields.
*/
<T extends Annotation> List<PlasticField> getFieldsWithAnnotation(Class<T>
annotationType);
/**
* Returns all non-introduced fields, in sorted order by name.
- *
+ *
* @return Unmodifiable list of fields.
*/
List<PlasticField> getAllFields();
/**
* Returns all unclaimed fields, in sorted order by name. This does not
include introduced fields.
- *
+ *
* @return Unmodifiable list of fields.
* @see PlasticField#claim(Object)
*/
@@ -65,38 +67,34 @@ public interface PlasticClass extends An
/**
* Introduces a new private field into the class.
- *
- * @param typeName
- * the Java class name for the field, or (possibly) a primitive
type name or an array
- * @param suggestedName
- * the suggested name for the field, which may be modified to
ensure that the field name
- * is unique
+ *
+ * @param typeName the Java class name for the field, or (possibly) a
primitive type name or an array
+ * @param suggestedName the suggested name for the field, which may be
modified to ensure that the field name
+ * is unique
* @return PlasticField for the introduced field
*/
PlasticField introduceField(String typeName, String suggestedName);
- /** Convenience method that uses a Java class rather than a type name. */
+ /**
+ * Convenience method that uses a Java class rather than a type name.
+ */
PlasticField introduceField(Class fieldType, String suggestedName);
/**
* Introduces a new private method into the class, ensuring that the
method name is unique.
- *
- * @param typeName
- * return type of method
- * @param suggestedName
- * suggested name for the method; the actual method name may be
modified to ensure uniqueness
- * @param argumentTypes
- * types of any arguments (may be null)
- * @param exceptionTypes
- * type of any checked exceptions (may be null)
+ *
+ * @param typeName return type of method
+ * @param suggestedName suggested name for the method; the actual method
name may be modified to ensure uniqueness
+ * @param argumentTypes types of any arguments (may be null)
+ * @param exceptionTypes type of any checked exceptions (may be null)
* @return new method, with default implementation
*/
PlasticMethod introducePrivateMethod(String typeName, String
suggestedName, String[] argumentTypes,
- String[] exceptionTypes);
+ String[] exceptionTypes);
/**
* Matches methods with the given annotation.
- *
+ *
* @return Unmodifiable list of methods, in sorted order.
*/
<T extends Annotation> List<PlasticMethod>
getMethodsWithAnnotation(Class<T> annotationType);
@@ -104,7 +102,7 @@ public interface PlasticClass extends An
/**
* Returns all methods of the class, in sorted order. This does not
include static methods,
* or any {@linkplain #introduceMethod(MethodDescription) introduced
methods}.
- *
+ *
* @return Unmodifiable list of methods.
*/
List<PlasticMethod> getMethods();
@@ -115,44 +113,38 @@ public interface PlasticClass extends An
* implemented in a <em>transformed</em> super class, the the default
behavior is to invoke that method and return
* its value. Otherwise, the default behavior is to ignore parameters and
return 0, false, or null. Void methods
* will invoke the super-class implementation (if it exists) and return no
value.
- * <p>
+ * <p/>
* It is allowed for the method description to indicate an abstract
method; however the abstract flag will be
* removed, and a non-abstract method will be created.
- *
- * @param description
- * describes the method name, visibility, return value, etc.
+ *
+ * @param description describes the method name, visibility, return value,
etc.
* @return a new (or previously created) PlasticMethod for the method
- * @throws IllegalArgumentException
- * if the method is abstract or static
+ * @throws IllegalArgumentException if the method is abstract or static
*/
PlasticMethod introduceMethod(MethodDescription description);
/**
* Returns an existing method declared in this class, or introduces a new
method into this class.
* The method is created with default behavior.
- * <p>
+ * <p/>
* It is allowed for the method description to indicate an abstract
method; however the abstract flag will be
* removed, and a non-abstract method will be created.
- *
- * @param description
- * describes the method name, visibility, return value, etc.
- * @param callback
- * used to create the implementation of the method
+ *
+ * @param description describes the method name, visibility, return value,
etc.
+ * @param callback used to create the implementation of the method
* @return a new (or previously created) PlasticMethod for the method
- * @throws IllegalArgumentException
- * if the method is abstract or static
+ * @throws IllegalArgumentException if the method is abstract or static
*/
PlasticMethod introduceMethod(MethodDescription description,
InstructionBuilderCallback callback);
/**
* A convenience that creates a {@link MethodDescription} from the Method
and introduces that. This is often
* invoked when walking the methods of an interface and introducing each
of those methods.
- * <p>
+ * <p/>
* Introduced methods are always concrete, not abstract. The abstract flag
on the method modifiers will always be
* stripped off, which is handy when {@linkplain
#introduceInterface(Class) introducing methods from an interface}.
- *
- * @param method
- * to introduce
+ *
+ * @param method to introduce
* @return new (or previously created) PlasticMethod
*/
PlasticMethod introduceMethod(Method method);
@@ -167,11 +159,9 @@ public interface PlasticClass extends An
/**
* Introduces the interface, and then invokes {@link
PlasticMethod#delegateTo(PlasticField)} on each method
* defined by the interface.
- *
- * @param interfaceType
- * defines the interface to proxy
- * @param field
- * field containing an object to delegate to
+ *
+ * @param interfaceType defines the interface to proxy
+ * @param field field containing an object to delegate to
* @return this plastic class, for further configuration
*/
PlasticClass proxyInterface(Class interfaceType, PlasticField field);
@@ -179,9 +169,8 @@ public interface PlasticClass extends An
/**
* Conditionally adds an implementation of <code>toString()</code> to the
class, but only if it is not already
* present in the class, or in a (transformed) super-class.
- *
- * @param toStringValue
- * the fixed value to be returned from invoking toString()
+ *
+ * @param toStringValue the fixed value to be returned from invoking
toString()
* @return this plastic class, for further configuration
*/
PlasticClass addToString(String toStringValue);
@@ -196,4 +185,11 @@ public interface PlasticClass extends An
* Returns the name of the super-class of the class being transformed.
*/
String getSuperClassName();
+
+ /**
+ * Adds the callback for execution when an instance of the class is
instantiated.
+ *
+ * @param callback to execute at instance construction time
+ */
+ PlasticClass onConstruct(ConstructorCallback callback);
}
Added:
tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ConstructorCallbackTests.groovy
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ConstructorCallbackTests.groovy?rev=1150815&view=auto
==============================================================================
---
tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ConstructorCallbackTests.groovy
(added)
+++
tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ConstructorCallbackTests.groovy
Mon Jul 25 18:03:11 2011
@@ -0,0 +1,44 @@
+// 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.plastic
+
+class ConstructorCallbackTests extends AbstractPlasticSpecification
+{
+ def "constructor callback invoked after field injection"()
+ {
+ String injectedValue = "value to inject into new field"
+ String observedValue
+ FieldHandle fieldHandle
+
+ def mgr = createMgr({ PlasticClass pc ->
+
+ fieldHandle = pc.introduceField(String.class,
"newField").inject(injectedValue).handle
+
+ pc.onConstruct({ instance, context ->
+
+ observedValue = fieldHandle.get(instance)
+
+ } as ConstructorCallback)
+ } as PlasticClassTransformer)
+
+ when:
+
+ mgr.getClassInstantiator("testsubjects.Empty").newInstance()
+
+ then:
+
+ observedValue == injectedValue
+ }
+}