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
+    }
+}


Reply via email to