You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2010/01/22 17:29:22 UTC

svn commit: r902144 - in /tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5: internal/services/ internal/transform/ services/

Author: hlship
Date: Fri Jan 22 16:29:21 2010
New Revision: 902144

URL: http://svn.apache.org/viewvc?rev=902144&view=rev
Log:
Introduce a new and more efficient mechanism for injecting behavior into components

Added:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentValueProvider.java   (with props)
Modified:
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/AbstractIncludeAssetWorker.java
    tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ClassTransformation.java

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java?rev=902144&r1=902143&r2=902144&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/InternalClassTransformationImpl.java Fri Jan 22 16:29:21 2010
@@ -4,7 +4,7 @@
 // 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
+// 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,
@@ -41,7 +41,8 @@
 import java.util.*;
 
 /**
- * Implementation of the {@link org.apache.tapestry5.internal.services.InternalClassTransformation} interface.
+ * Implementation of the {@link org.apache.tapestry5.internal.services.InternalClassTransformation}
+ * interface.
  */
 public final class InternalClassTransformationImpl implements InternalClassTransformation
 {
@@ -60,7 +61,8 @@
     private final IdAllocator idAllocator;
 
     /**
-     * Map, keyed on InjectKey, of field name.  Injections are always added as protected (not private) fields to support
+     * Map, keyed on InjectKey, of field name. Injections are always added as protected (not
+     * private) fields to support
      * sharing of injections between a base class and a sub class.
      */
     private final Map<InjectionKey, String> injectionCache = CollectionFactory.newMap();
@@ -89,7 +91,8 @@
 
     private Map<CtMethod, TransformMethodSignature> methodSignatures = CollectionFactory.newMap();
 
-    private Map<TransformMethodSignature, ComponentMethodInvocationBuilder> methodToInvocationBuilder = CollectionFactory.newMap();
+    private Map<TransformMethodSignature, ComponentMethodInvocationBuilder> methodToInvocationBuilder = CollectionFactory
+            .newMap();
 
     // Key is field name, value is expression used to replace read access
 
@@ -124,17 +127,16 @@
     /**
      * Signature for newInstance() method of Instantiator.
      */
-    private static final MethodSignature NEW_INSTANCE_SIGNATURE = new MethodSignature(Component.class, "newInstance",
-                                                                                      new Class[] {
-                                                                                              InternalComponentResources.class },
-                                                                                      null);
+    private static final MethodSignature NEW_INSTANCE_SIGNATURE = new MethodSignature(
+            Component.class, "newInstance", new Class[]
+            { InternalComponentResources.class }, null);
 
     /**
      * This is a constructor for a base class.
      */
     public InternalClassTransformationImpl(ClassFactory classFactory, CtClass ctClass,
-                                           ComponentClassCache componentClassCache,
-                                           ComponentModel componentModel, CtClassSource classSource)
+            ComponentClassCache componentClassCache, ComponentModel componentModel,
+            CtClassSource classSource)
     {
         this.ctClass = ctClass;
         this.componentClassCache = componentClassCache;
@@ -155,24 +157,25 @@
 
         addImplementedInterface(Component.class);
 
-        resourcesFieldName = addInjectedFieldUncached(InternalComponentResources.class, "resources", null);
+        resourcesFieldName = addInjectedFieldUncached(InternalComponentResources.class,
+                "resources", null);
 
-        TransformMethodSignature sig = new TransformMethodSignature(Modifier.PUBLIC | Modifier.FINAL,
-                                                                    ComponentResources.class.getName(),
-                                                                    "getComponentResources", null, null);
+        TransformMethodSignature sig = new TransformMethodSignature(Modifier.PUBLIC
+                | Modifier.FINAL, ComponentResources.class.getName(), "getComponentResources",
+                null, null);
 
         addMethod(sig, "return " + resourcesFieldName + ";");
 
-        // The "}" will be added later, inside  finish().
+        // The "}" will be added later, inside finish().
     }
 
     /**
      * Constructor for a component sub-class.
      */
-    private InternalClassTransformationImpl(CtClass ctClass, InternalClassTransformation parentTransformation,
-                                            ClassFactory classFactory, CtClassSource classSource,
-                                            ComponentClassCache componentClassCache,
-                                            ComponentModel componentModel)
+    private InternalClassTransformationImpl(CtClass ctClass,
+            InternalClassTransformation parentTransformation, ClassFactory classFactory,
+            CtClassSource classSource, ComponentClassCache componentClassCache,
+            ComponentModel componentModel)
     {
         this.ctClass = ctClass;
         this.componentClassCache = componentClassCache;
@@ -199,7 +202,8 @@
 
         for (int i = 1; i <= count; i++)
         {
-            if (i > 1) constructor.append(", ");
+            if (i > 1)
+                constructor.append(", ");
 
             // $0 is implicitly self, so the 0-index ConstructorArg will be Javassisst
             // pseudeo-variable $1, and so forth.
@@ -210,13 +214,14 @@
 
         constructor.append(");\n");
 
-        // The "}" will be added later, inside  finish().
+        // The "}" will be added later, inside finish().
     }
 
-    public InternalClassTransformation createChildTransformation(CtClass childClass, MutableComponentModel childModel)
+    public InternalClassTransformation createChildTransformation(CtClass childClass,
+            MutableComponentModel childModel)
     {
-        return new InternalClassTransformationImpl(childClass, this, classFactory, classSource, componentClassCache,
-                                                   childModel);
+        return new InternalClassTransformationImpl(childClass, this, classFactory, classSource,
+                componentClassCache, childModel);
     }
 
     private void freeze()
@@ -259,7 +264,10 @@
     }
 
     /**
-     * Invoked during instance construction to check that all fields are either: <ul> <li>private</li> <li>static</li>
+     * Invoked during instance construction to check that all fields are either:
+     * <ul>
+     * <li>private</li>
+     * <li>static</li>
      * <li>groovy.lang.MetaClass (for Groovy compatiblility)</li> </li>
      */
     void verifyFields()
@@ -274,9 +282,10 @@
 
             // Fields must be either static or private.
 
-            if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) continue;
+            if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers))
+                continue;
 
-            // Groovy injects a public field named metaClass.  We ignore it, and add it as a claimed
+            // Groovy injects a public field named metaClass. We ignore it, and add it as a claimed
             // field to prevent any of the workers from seeing it.
 
             if (name.equals("metaClass") && getFieldType(name).equals("groovy.lang.MetaClass"))
@@ -310,13 +319,16 @@
         return findAnnotationInList(annotationClass, annotations);
     }
 
-    public <T extends Annotation> T getMethodAnnotation(TransformMethodSignature signature, Class<T> annotationClass)
+    public <T extends Annotation> T getMethodAnnotation(TransformMethodSignature signature,
+            Class<T> annotationClass)
     {
         failIfFrozen();
 
         CtMethod method = findMethod(signature);
 
-        if (method == null) throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(ctClass, signature));
+        if (method == null)
+            throw new IllegalArgumentException(ServicesMessages
+                    .noDeclaredMethod(ctClass, signature));
 
         List<Annotation> annotations = findMethodAnnotations(method);
 
@@ -324,19 +336,24 @@
     }
 
     /**
-     * Searches an array of objects (that are really annotations instances) to find one that is of the correct type,
+     * Searches an array of objects (that are really annotations instances) to find one that is of
+     * the correct type,
      * which is returned.
-     *
+     * 
      * @param <T>
-     * @param annotationClass the annotation to search for
-     * @param annotations     the available annotations
+     * @param annotationClass
+     *            the annotation to search for
+     * @param annotations
+     *            the available annotations
      * @return the matching annotation instance, or null if not found
      */
-    private <T extends Annotation> T findAnnotationInList(Class<T> annotationClass, List<Annotation> annotations)
+    private <T extends Annotation> T findAnnotationInList(Class<T> annotationClass,
+            List<Annotation> annotations)
     {
         for (Object annotation : annotations)
         {
-            if (annotationClass.isInstance(annotation)) return annotationClass.cast(annotation);
+            if (annotationClass.isInstance(annotation))
+                return annotationClass.cast(annotation);
         }
 
         return null;
@@ -397,7 +414,8 @@
         }
     }
 
-    private void addAnnotationsToList(List<Annotation> list, Object[] annotations, boolean filterNonInherited)
+    private void addAnnotationsToList(List<Annotation> list, Object[] annotations,
+            boolean filterNonInherited)
     {
         for (Object o : annotations)
         {
@@ -412,7 +430,8 @@
 
                 Inherited inherited = annotationType.getAnnotation(Inherited.class);
 
-                if (inherited == null) continue;
+                if (inherited == null)
+                    continue;
             }
 
             list.add(a);
@@ -427,7 +446,8 @@
         }
         catch (NotFoundException ex)
         {
-            throw new RuntimeException(ServicesMessages.missingDeclaredField(ctClass, fieldName), ex);
+            throw new RuntimeException(ServicesMessages.missingDeclaredField(ctClass, fieldName),
+                    ex);
         }
     }
 
@@ -435,7 +455,8 @@
     {
         failIfFrozen();
 
-        String memberName = InternalUtils.createMemberName(Defense.notBlank(suggested, "suggested"));
+        String memberName = InternalUtils
+                .createMemberName(Defense.notBlank(suggested, "suggested"));
 
         return idAllocator.allocateId(memberName);
     }
@@ -449,13 +470,12 @@
     {
         failIfFrozen();
 
-        String interfaceName = interfaceClass.getName();
-
         try
         {
-            CtClass ctInterface = classPool.get(interfaceName);
+            CtClass ctInterface = toCtClass(interfaceClass);
 
-            if (classImplementsInterface(ctInterface)) return;
+            if (classImplementsInterface(ctInterface))
+                return;
 
             implementDefaultMethodsForInterface(ctInterface);
 
@@ -468,11 +488,14 @@
     }
 
     /**
-     * Adds default implementations for the methods defined by the interface (and all of its super-interfaces). The
-     * implementations return null (or 0, or false, as appropriate to to the method type). There are a number of
-     * degenerate cases that are not covered properly: these are related to base interfaces that may be implemented by
+     * Adds default implementations for the methods defined by the interface (and all of its
+     * super-interfaces). The
+     * implementations return null (or 0, or false, as appropriate to to the method type). There are
+     * a number of
+     * degenerate cases that are not covered properly: these are related to base interfaces that may
+     * be implemented by
      * base classes.
-     *
+     * 
      * @param ctInterface
      * @throws NotFoundException
      */
@@ -480,7 +503,8 @@
     {
         // java.lang.Object is the parent interface of interfaces
 
-        if (ctInterface.getName().equals(Object.class.getName())) return;
+        if (ctInterface.getName().equals(Object.class.getName()))
+            return;
 
         for (CtMethod method : ctInterface.getDeclaredMethods())
         {
@@ -499,7 +523,8 @@
         // up as methods of the interface. We skip those and only consider the methods
         // that are abstract.
 
-        if (!Modifier.isAbstract(method.getModifiers())) return;
+        if (!Modifier.isAbstract(method.getModifiers()))
+            return;
 
         try
         {
@@ -524,14 +549,16 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new RuntimeException(ServicesMessages.errorAddingMethod(ctClass, method
-                    .getName(), ex), ex);
+            throw new RuntimeException(ServicesMessages.errorAddingMethod(ctClass,
+                    method.getName(), ex), ex);
         }
     }
 
     /**
-     * Check to see if the target class (or any of its super classes) implements the provided interface. This is geared
-     * for simple interfaces (that don't extend other interfaces), thus if the class (or a base class) implement
+     * Check to see if the target class (or any of its super classes) implements the provided
+     * interface. This is geared
+     * for simple interfaces (that don't extend other interfaces), thus if the class (or a base
+     * class) implement
      * interface Y that extends interface X, we may not return true for interface X.
      */
 
@@ -542,7 +569,8 @@
         {
             for (CtClass anInterface : current.getInterfaces())
             {
-                if (anInterface == ctInterface) return true;
+                if (anInterface == ctInterface)
+                    return true;
             }
         }
 
@@ -560,7 +588,8 @@
 
         if (existing != null)
         {
-            String message = ServicesMessages.fieldAlreadyClaimed(fieldName, ctClass, existing, tag);
+            String message = ServicesMessages
+                    .fieldAlreadyClaimed(fieldName, ctClass, existing, tag);
 
             throw new RuntimeException(message);
         }
@@ -575,7 +604,8 @@
         addOrReplaceMethod(signature, methodBody, true);
     }
 
-    private void addOrReplaceMethod(TransformMethodSignature signature, String methodBody, boolean addAsNew)
+    private void addOrReplaceMethod(TransformMethodSignature signature, String methodBody,
+            boolean addAsNew)
     {
         failIfFrozen();
 
@@ -609,7 +639,8 @@
         try
         {
 
-            CtMethod method = new CtMethod(returnType, signature.getMethodName(), parameters, ctClass);
+            CtMethod method = new CtMethod(returnType, signature.getMethodName(), parameters,
+                    ctClass);
 
             // TODO: Check for duplicate method add
 
@@ -620,12 +651,13 @@
 
             ctClass.addMethod(method);
 
-            if (addAsNew) addedMethods.add(method);
+            if (addAsNew)
+                addedMethods.add(method);
         }
         catch (CannotCompileException ex)
         {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(signature, methodBody, ex), methodBody,
-                                             ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(signature,
+                    methodBody, ex), methodBody, ex);
         }
         catch (NotFoundException ex)
         {
@@ -643,7 +675,6 @@
         CtClass[] parameters = buildCtClassList(signature.getParameterTypes());
         CtClass[] exceptions = buildCtClassList(signature.getExceptionTypes());
 
-
         try
         {
             CtMethod existing = ctClass.getDeclaredMethod(signature.getMethodName(), parameters);
@@ -661,7 +692,8 @@
 
         try
         {
-            CtMethod method = new CtMethod(returnType, signature.getMethodName(), parameters, ctClass);
+            CtMethod method = new CtMethod(returnType, signature.getMethodName(), parameters,
+                    ctClass);
 
             // TODO: Check for duplicate method add
 
@@ -674,8 +706,8 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(signature, methodBody, ex), methodBody,
-                                             ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(signature,
+                    methodBody, ex), methodBody, ex);
         }
         catch (NotFoundException ex)
         {
@@ -719,8 +751,8 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature, methodBody, ex),
-                                             methodBody, ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature,
+                    methodBody, ex), methodBody, ex);
         }
 
         addMethodToDescription("extend", methodSignature, methodBody);
@@ -740,14 +772,15 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature, methodBody, ex),
-                                             methodBody, ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature,
+                    methodBody, ex), methodBody, ex);
         }
 
         addMethodToDescription("extend existing", methodSignature, methodBody);
     }
 
-    public void copyMethod(TransformMethodSignature sourceMethod, int modifiers, String newMethodName)
+    public void copyMethod(TransformMethodSignature sourceMethod, int modifiers,
+            String newMethodName)
     {
         failIfFrozen();
 
@@ -771,8 +804,7 @@
         catch (CannotCompileException ex)
         {
             throw new RuntimeException(String.format("Error copying method %s to new method %s().",
-                                                     sourceMethod,
-                                                     newMethodName), ex);
+                    sourceMethod, newMethodName), ex);
         }
         catch (NotFoundException ex)
         {
@@ -798,8 +830,8 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature, body, ex),
-                                             body, ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature,
+                    body, ex), body, ex);
         }
 
         addMethodToDescription(String.format("catch(%s) in", exceptionType), methodSignature, body);
@@ -817,14 +849,15 @@
         }
         catch (CannotCompileException ex)
         {
-            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature, methodBody, ex),
-                                             methodBody, ex);
+            throw new MethodCompileException(ServicesMessages.methodCompileError(methodSignature,
+                    methodBody, ex), methodBody, ex);
         }
 
         addMethodToDescription("prefix", methodSignature, methodBody);
     }
 
-    private void addMethodToDescription(String operation, TransformMethodSignature methodSignature, String methodBody)
+    private void addMethodToDescription(String operation, TransformMethodSignature methodSignature,
+            String methodBody)
     {
         formatter.format("%s method: %s %s %s(", operation, Modifier.toString(methodSignature
                 .getModifiers()), methodSignature.getReturnType(), methodSignature.getMethodName());
@@ -832,7 +865,8 @@
         String[] parameterTypes = methodSignature.getParameterTypes();
         for (int i = 0; i < parameterTypes.length; i++)
         {
-            if (i > 0) description.append(", ");
+            if (i > 0)
+                description.append(", ");
 
             formatter.format("%s $%d", parameterTypes[i], i + 1);
         }
@@ -842,8 +876,10 @@
         String[] exceptionTypes = methodSignature.getExceptionTypes();
         for (int i = 0; i < exceptionTypes.length; i++)
         {
-            if (i == 0) description.append("\n  throws ");
-            else description.append(", ");
+            if (i == 0)
+                description.append("\n  throws ");
+            else
+                description.append(", ");
 
             description.append(exceptionTypes[i]);
         }
@@ -855,20 +891,24 @@
     {
         CtMethod method = findDeclaredMethod(methodSignature);
 
-        if (method != null) return method;
+        if (method != null)
+            return method;
 
         CtMethod result = addOverrideOfSuperclassMethod(methodSignature);
 
-        if (result != null) return result;
+        if (result != null)
+            return result;
 
-        throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(ctClass, methodSignature));
+        throw new IllegalArgumentException(ServicesMessages.noDeclaredMethod(ctClass,
+                methodSignature));
     }
 
     private CtMethod findDeclaredMethod(TransformMethodSignature methodSignature)
     {
         for (CtMethod method : ctClass.getDeclaredMethods())
         {
-            if (match(method, methodSignature)) return method;
+            if (match(method, methodSignature))
+                return method;
         }
 
         return null;
@@ -911,7 +951,8 @@
 
     private boolean match(CtMethod method, TransformMethodSignature sig)
     {
-        if (!sig.getMethodName().equals(method.getName())) return false;
+        if (!sig.getMethodName().equals(method.getName()))
+            return false;
 
         CtClass[] paramTypes;
 
@@ -928,13 +969,15 @@
 
         int count = sigTypes.length;
 
-        if (paramTypes.length != count) return false;
+        if (paramTypes.length != count)
+            return false;
 
         for (int i = 0; i < count; i++)
         {
             String paramType = paramTypes[i].getName();
 
-            if (!paramType.equals(sigTypes[i])) return false;
+            if (!paramType.equals(sigTypes[i]))
+                return false;
         }
 
         // Ignore exceptions thrown and modifiers.
@@ -956,7 +999,6 @@
         return findFields(filter);
     }
 
-
     public List<String> findFields(FieldFilter filter)
     {
         failIfFrozen();
@@ -967,11 +1009,13 @@
         {
             for (CtField field : ctClass.getDeclaredFields())
             {
-                if (!isInstanceField(field)) continue;
+                if (!isInstanceField(field))
+                    continue;
 
                 String fieldName = field.getName();
 
-                if (filter.accept(fieldName, field.getType().getName())) result.add(fieldName);
+                if (filter.accept(fieldName, field.getType().getName()))
+                    result.add(fieldName);
             }
         }
         catch (NotFoundException ex)
@@ -984,7 +1028,8 @@
         return result;
     }
 
-    public List<TransformMethodSignature> findMethodsWithAnnotation(final Class<? extends Annotation> annotationClass)
+    public List<TransformMethodSignature> findMethodsWithAnnotation(
+            final Class<? extends Annotation> annotationClass)
     {
         failIfFrozen();
 
@@ -1016,7 +1061,8 @@
         {
             TransformMethodSignature sig = getMethodSignature(method);
 
-            if (filter.accept(sig)) result.add(sig);
+            if (filter.accept(sig))
+                result.add(sig);
         }
 
         Collections.sort(result);
@@ -1035,8 +1081,8 @@
                 String[] parameters = toTypeNames(method.getParameterTypes());
                 String[] exceptions = toTypeNames(method.getExceptionTypes());
 
-                result = new TransformMethodSignature(method.getModifiers(), type, method.getName(), parameters,
-                                                      exceptions);
+                result = new TransformMethodSignature(method.getModifiers(), type,
+                        method.getName(), parameters, exceptions);
 
                 methodSignatures.put(method, result);
             }
@@ -1070,15 +1116,18 @@
         skipped.addAll(claimedFields.keySet());
         skipped.addAll(addedFieldNames);
 
-        if (removedFieldNames != null) skipped.addAll(removedFieldNames);
+        if (removedFieldNames != null)
+            skipped.addAll(removedFieldNames);
 
         for (CtField field : ctClass.getDeclaredFields())
         {
-            if (!isInstanceField(field)) continue;
+            if (!isInstanceField(field))
+                continue;
 
             String name = field.getName();
 
-            if (skipped.contains(name)) continue;
+            if (skipped.contains(name))
+                continue;
 
             names.add(name);
         }
@@ -1172,8 +1221,7 @@
             throw new RuntimeException(ex);
         }
 
-        formatter
-                .format("add field: %s %s %s;\n\n", Modifier.toString(modifiers), type, fieldName);
+        formatter.format("add field: %s %s %s;\n\n", Modifier.toString(modifiers), type, fieldName);
 
         addedFieldNames.add(fieldName);
 
@@ -1190,7 +1238,8 @@
 
         String fieldName = searchForPreviousInjection(key);
 
-        if (fieldName != null) return fieldName;
+        if (fieldName != null)
+            return fieldName;
 
         // TODO: Probably doesn't handle arrays and primitives.
 
@@ -1203,25 +1252,57 @@
         return fieldName;
     }
 
-    /**
-     * This is split out from {@link #addInjectedField(Class, String, Object)} to handle a special case for the
-     * InternalComponentResources, which is null when "injected" (during the class transformation) and is only
-     * determined when a component is actually instantiated.
-     */
-    private String addInjectedFieldUncached(Class type, String suggestedName, Object value)
+    public <T> String addIndirectInjectedField(Class<T> type, String suggestedName,
+            ComponentValueProvider<T> provider)
     {
-        CtClass ctType;
+        Defense.notNull(type, "type");
+        Defense.notNull(provider, "provider");
+
+        String fieldName = addField(Modifier.PRIVATE | Modifier.FINAL, type.getName(),
+                suggestedName);
+
+        // TODO: This shouldn't have to be constantly recomputed
+
+        CtClass providerType = toCtClass(ComponentValueProvider.class);
+
+        constructorArgs.add(new ConstructorArg(providerType, provider));
+
+        // Inside the constructor,
+        // pass the resources to the provider's get() method, cast to the
+        // field type and assign. This will likely not work with
+        // primitives and arrays, but that's ok for now.
+
+        extendConstructor(String.format("  %s = (%s) $%d.get(%s);", fieldName, type.getName(),
+                constructorArgs.size(), resourcesFieldName));
 
+        return fieldName;
+    }
+
+    private CtClass toCtClass(Class type)
+    {
         try
         {
-            ctType = classPool.get(type.getName());
+            return classPool.get(type.getName());
         }
         catch (NotFoundException ex)
         {
             throw new RuntimeException(ex);
         }
+    }
+
+    /**
+     * This is split out from {@link #addInjectedField(Class, String, Object)} to handle a special
+     * case for the
+     * InternalComponentResources, which is null when "injected" (during the class transformation)
+     * and is only
+     * determined when a component is actually instantiated.
+     */
+    private String addInjectedFieldUncached(Class type, String suggestedName, Object value)
+    {
+        CtClass ctType = toCtClass(type);
 
-        String fieldName = addField(Modifier.PROTECTED | Modifier.FINAL, type.getName(), suggestedName);
+        String fieldName = addField(Modifier.PROTECTED | Modifier.FINAL, type.getName(),
+                suggestedName);
 
         addInjectToConstructor(fieldName, ctType, value);
 
@@ -1234,9 +1315,11 @@
     {
         String result = injectionCache.get(key);
 
-        if (result != null) return result;
+        if (result != null)
+            return result;
 
-        if (parentTransformation != null) return parentTransformation.searchForPreviousInjection(key);
+        if (parentTransformation != null)
+            return parentTransformation.searchForPreviousInjection(key);
 
         return null;
     }
@@ -1250,7 +1333,8 @@
 
         if (builder == null)
         {
-            builder = new ComponentMethodInvocationBuilder(this, componentClassCache, methodSignature, classSource);
+            builder = new ComponentMethodInvocationBuilder(this, componentClassCache,
+                    methodSignature, classSource);
             methodToInvocationBuilder.put(methodSignature, builder);
         }
 
@@ -1262,13 +1346,15 @@
         Defense.notNull(methodSignature, "methodSignature");
 
         if (!isMethod(methodSignature))
-            throw new IllegalArgumentException(String.format("Method %s is not implemented by transformed class %s.",
-                                                             methodSignature, getClassName()));
+            throw new IllegalArgumentException(String.format(
+                    "Method %s is not implemented by transformed class %s.", methodSignature,
+                    getClassName()));
 
         InternalClassTransformation search = parentTransformation;
         while (search != null)
         {
-            if (search.isMethod(methodSignature)) return true;
+            if (search.isMethod(methodSignature))
+                return true;
 
             search = search.getParentTransformation();
         }
@@ -1291,11 +1377,15 @@
     }
 
     /**
-     * Adds a parameter to the constructor for the class; the parameter is used to initialize the value for a field.
-     *
-     * @param fieldName name of field to inject
-     * @param fieldType Javassist type of the field (and corresponding parameter)
-     * @param value     the value to be injected (which will in unusual cases be null)
+     * Adds a parameter to the constructor for the class; the parameter is used to initialize the
+     * value for a field.
+     * 
+     * @param fieldName
+     *            name of field to inject
+     * @param fieldType
+     *            Javassist type of the field (and corresponding parameter)
+     * @param value
+     *            the value to be injected (which will in unusual cases be null)
      */
     private void addInjectToConstructor(String fieldName, CtClass fieldType, Object value)
     {
@@ -1380,7 +1470,8 @@
 
         for (int i = 0; i < count; i++)
         {
-            if (i > 0) description.append(", ");
+            if (i > 0)
+                description.append(", ");
 
             formatter.format("%s $%d", types[i].getName(), i + 1);
         }
@@ -1400,11 +1491,11 @@
 
             ctClass.addMethod(initializerMethod);
 
-            // Replace the constructor body with one that fails.  This leaves, as an open question,
+            // Replace the constructor body with one that fails. This leaves, as an open question,
             // what to do about any other constructors.
 
-            String body = String.format("throw new RuntimeException(\"%s\");",
-                                        ServicesMessages.forbidInstantiateComponentClass(getClassName()));
+            String body = String.format("throw new RuntimeException(\"%s\");", ServicesMessages
+                    .forbidInstantiateComponentClass(getClassName()));
 
             defaultConstructor.setBody(body);
         }
@@ -1420,23 +1511,20 @@
 
     public Instantiator createInstantiator()
     {
-        if (Modifier.isAbstract(ctClass.getModifiers()))
+        if (Modifier.isAbstract(ctClass.getModifiers())) { return new Instantiator()
         {
-            return new Instantiator()
+            public Component newInstance(InternalComponentResources resources)
             {
-                public Component newInstance(InternalComponentResources resources)
-                {
-                    throw new RuntimeException(
-                            String.format("Component class %s is abstract and can not be instantiated.",
-                                          ctClass.getName()));
-                }
+                throw new RuntimeException(String.format(
+                        "Component class %s is abstract and can not be instantiated.", ctClass
+                                .getName()));
+            }
 
-                public ComponentModel getModel()
-                {
-                    return componentModel;
-                }
-            };
-        }
+            public ComponentModel getModel()
+            {
+                return componentModel;
+            }
+        }; }
 
         String componentClassName = ctClass.getName();
 
@@ -1446,9 +1534,12 @@
 
         BodyBuilder constructor = new BodyBuilder();
 
-        // This is realy -1 + 2: The first value in constructorArgs is the InternalComponentResources, which doesn't
-        // count toward's the Instantiator's constructor ... then we add in the Model and String description.
-        // It's tricky because there's the constructor parameters for the Instantiator, most of which are stored
+        // This is realy -1 + 2: The first value in constructorArgs is the
+        // InternalComponentResources, which doesn't
+        // count toward's the Instantiator's constructor ... then we add in the Model and String
+        // description.
+        // It's tricky because there's the constructor parameters for the Instantiator, most of
+        // which are stored
         // in fields and then used as the constructor parameters for the Component.
 
         Class[] constructorParameterTypes = new Class[constructorArgs.size() + 1];
@@ -1498,9 +1589,8 @@
 
             String parameterReference = "$" + (i + 2);
 
-            constructor.addln("%s = %s;",
-                              fieldName,
-                              ClassFabUtils.castReference(parameterReference, fieldType.getName()));
+            constructor.addln("%s = %s;", fieldName, ClassFabUtils.castReference(
+                    parameterReference, fieldType.getName()));
 
             newInstance.add(", %s", fieldName);
         }
@@ -1516,7 +1606,8 @@
 
         try
         {
-            Object instance = instantiatorClass.getConstructors()[0].newInstance(constructorParameterValues);
+            Object instance = instantiatorClass.getConstructors()[0]
+                    .newInstance(constructorParameterValues);
 
             return (Instantiator) instance;
         }
@@ -1528,14 +1619,17 @@
 
     private void failIfFrozen()
     {
-        if (frozen) throw new IllegalStateException(
-                "The ClassTransformation instance (for " + ctClass.getName() + ") has completed all transformations and may not be further modified.");
+        if (frozen)
+            throw new IllegalStateException("The ClassTransformation instance (for "
+                    + ctClass.getName()
+                    + ") has completed all transformations and may not be further modified.");
     }
 
     private void failIfNotFrozen()
     {
-        if (!frozen) throw new IllegalStateException(
-                "The ClassTransformation instance (for " + ctClass.getName() + ") has not yet completed all transformations.");
+        if (!frozen)
+            throw new IllegalStateException("The ClassTransformation instance (for "
+                    + ctClass.getName() + ") has not yet completed all transformations.");
     }
 
     public IdAllocator getIdAllocator()
@@ -1556,7 +1650,8 @@
     {
         failIfFrozen();
 
-        if (classAnnotations == null) assembleClassAnnotations();
+        if (classAnnotations == null)
+            assembleClassAnnotations();
 
         return classAnnotations;
     }
@@ -1597,15 +1692,17 @@
         {
             Formatter formatter = new Formatter(builder);
 
-            formatter.format("%s %s extends %s", Modifier.toString(ctClass.getModifiers()), ctClass.getName(),
-                             ctClass.getSuperclass().getName());
+            formatter.format("%s %s extends %s", Modifier.toString(ctClass.getModifiers()), ctClass
+                    .getName(), ctClass.getSuperclass().getName());
 
             CtClass[] interfaces = ctClass.getInterfaces();
 
             for (int i = 0; i < interfaces.length; i++)
             {
-                if (i == 0) builder.append("\n  implements ");
-                else builder.append(", ");
+                if (i == 0)
+                    builder.append("\n  implements ");
+                else
+                    builder.append(", ");
 
                 builder.append(interfaces[i].getName());
             }
@@ -1628,8 +1725,9 @@
 
         String fieldType = getFieldType(fieldName);
 
-        TransformMethodSignature sig = new TransformMethodSignature(Modifier.PRIVATE, "void", methodName,
-                                                                    new String[] { fieldType }, null);
+        TransformMethodSignature sig = new TransformMethodSignature(Modifier.PRIVATE, "void",
+                methodName, new String[]
+                { fieldType }, null);
 
         String message = ServicesMessages.readOnlyField(ctClass.getName(), fieldName);
 
@@ -1646,7 +1744,8 @@
 
         // TODO: We could check that there's an existing field read and field write transform ...
 
-        if (removedFieldNames == null) removedFieldNames = CollectionFactory.newSet();
+        if (removedFieldNames == null)
+            removedFieldNames = CollectionFactory.newSet();
 
         removedFieldNames.add(fieldName);
     }
@@ -1658,7 +1757,8 @@
 
         String body = String.format("$_ = $0.%s();", methodName);
 
-        if (fieldReadTransforms == null) fieldReadTransforms = CollectionFactory.newMap();
+        if (fieldReadTransforms == null)
+            fieldReadTransforms = CollectionFactory.newMap();
 
         // TODO: Collisions?
 
@@ -1674,7 +1774,8 @@
 
         String body = String.format("$0.%s($1);", methodName);
 
-        if (fieldWriteTransforms == null) fieldWriteTransforms = CollectionFactory.newMap();
+        if (fieldWriteTransforms == null)
+            fieldWriteTransforms = CollectionFactory.newMap();
 
         // TODO: Collisions?
 
@@ -1688,7 +1789,8 @@
         // If no field transformations have been requested, then we can save ourselves some
         // trouble!
 
-        if (fieldReadTransforms != null || fieldWriteTransforms != null) replaceFieldAccess();
+        if (fieldReadTransforms != null || fieldWriteTransforms != null)
+            replaceFieldAccess();
 
         if (removedFieldNames != null)
         {
@@ -1714,9 +1816,11 @@
         // Provide empty maps here, to make the code in the inner class a tad
         // easier.
 
-        if (fieldReadTransforms == null) fieldReadTransforms = CollectionFactory.newMap();
+        if (fieldReadTransforms == null)
+            fieldReadTransforms = CollectionFactory.newMap();
 
-        if (fieldWriteTransforms == null) fieldWriteTransforms = CollectionFactory.newMap();
+        if (fieldWriteTransforms == null)
+            fieldWriteTransforms = CollectionFactory.newMap();
 
         ExprEditor editor = new ExprEditor()
         {
@@ -1725,14 +1829,15 @@
             {
                 CtBehavior where = access.where();
 
-                if (where instanceof CtConstructor) return;
+                if (where instanceof CtConstructor)
+                    return;
 
                 boolean isRead = access.isReader();
                 String fieldName = access.getFieldName();
                 CtMethod method = (CtMethod) where;
 
-                formatter.format("Checking field %s %s in method %s(): ", isRead ? "read" : "write", fieldName,
-                                 method.getName());
+                formatter.format("Checking field %s %s in method %s(): ",
+                        isRead ? "read" : "write", fieldName, method.getName());
 
                 // Ignore any methods to were added as part of the transformation.
                 // If we reference the field there, we really mean the field.
@@ -1743,7 +1848,8 @@
                     return;
                 }
 
-                Map<String, String> transformMap = isRead ? fieldReadTransforms : fieldWriteTransforms;
+                Map<String, String> transformMap = isRead ? fieldReadTransforms
+                        : fieldWriteTransforms;
 
                 String body = transformMap.get(fieldName);
                 if (body == null)

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/AbstractIncludeAssetWorker.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/AbstractIncludeAssetWorker.java?rev=902144&r1=902143&r2=902144&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/AbstractIncludeAssetWorker.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/AbstractIncludeAssetWorker.java Fri Jan 22 16:29:21 2010
@@ -1,10 +1,10 @@
-// Copyright 2007, 2008, 2009 The Apache Software Foundation
+// Copyright 2007, 2008, 2009, 2010 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
+// 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,
@@ -14,10 +14,12 @@
 
 package org.apache.tapestry5.internal.transform;
 
+import java.util.List;
+import java.util.Locale;
+
 import org.apache.tapestry5.Asset;
 import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.annotations.SetupRender;
-import org.apache.tapestry5.internal.services.ComponentResourcesOperation;
 import org.apache.tapestry5.ioc.Resource;
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
 import org.apache.tapestry5.ioc.services.SymbolSource;
@@ -25,14 +27,12 @@
 import org.apache.tapestry5.services.AssetSource;
 import org.apache.tapestry5.services.ClassTransformation;
 import org.apache.tapestry5.services.ComponentClassTransformWorker;
+import org.apache.tapestry5.services.ComponentValueProvider;
 import org.apache.tapestry5.services.TransformConstants;
 
-import java.util.List;
-import java.util.Locale;
-
 /**
- * Base class for workers that automatically inlcude assets in the page (via methods on {@link
- * org.apache.tapestry5.RenderSupport}).
+ * Base class for workers that automatically include assets in the page (via methods on
+ * {@link org.apache.tapestry5.RenderSupport}).
  */
 public abstract class AbstractIncludeAssetWorker implements ComponentClassTransformWorker
 {
@@ -47,15 +47,19 @@
     }
 
     /**
-     * Expands symbols in the path, then adds an operation into the setup render phase of the component. Ultimately,
-     * {@link #handleAsset(org.apache.tapestry5.Asset)} will be invoked for each asset (dervied from assetPaths).
-     *
-     * @param transformation transformation process for component
-     * @param model          component model for component
-     * @param assetPaths     raw paths to be converted to assets
+     * Expands symbols in the path, then adds an operation into the setup render phase of the
+     * component. Ultimately, {@link #handleAsset(org.apache.tapestry5.Asset)} will be invoked for
+     * each asset (dervied from assetPaths).
+     * 
+     * @param transformation
+     *            transformation process for component
+     * @param model
+     *            component model for component
+     * @param assetPaths
+     *            raw paths to be converted to assets
      */
     protected final void addOperationForAssetPaths(ClassTransformation transformation,
-                                                   MutableComponentModel model, String[] assetPaths)
+            MutableComponentModel model, String[] assetPaths)
     {
         final Resource baseResource = model.getBaseResource();
         final List<String> paths = CollectionFactory.newList();
@@ -67,45 +71,57 @@
             paths.add(expanded);
         }
 
-        ComponentResourcesOperation op = new ComponentResourcesOperation()
+        ComponentValueProvider<Runnable> provider = new ComponentValueProvider<Runnable>()
         {
-            // Remember that ONE instances of this op will be injected into EVERY instance
-            // of the component ... that means that we can't do any aggresive caching
-            // inside the operation (the operation must be threadsafe).
-
-            public void perform(ComponentResources resources)
+            @Override
+            public Runnable get(final ComponentResources resources)
             {
-                Locale locale = resources.getLocale();
+                // This code is re-executed for each new component instance. We could 
+                // possibly cache on resources.getCompleteId() + locale, but that's
+                // probably not worth the effort.
+                
+                final Locale locale = resources.getLocale();
+
+                final List<Asset> assets = CollectionFactory.newList();
 
                 for (String assetPath : paths)
                 {
                     Asset asset = assetSource.getAsset(baseResource, assetPath, locale);
 
-                    handleAsset(asset);
+                    assets.add(asset);
                 }
+
+                return new Runnable()
+                {
+                    @Override
+                    public void run()
+                    {
+                        for (Asset asset : assets)
+                        {
+                            handleAsset(asset);
+                        }
+                    }
+                };
             }
         };
 
-        String opFieldName = transformation.addInjectedField(ComponentResourcesOperation.class, "operation", op);
-
-        String resourcesName = transformation.getResourcesFieldName();
-
-        String body = String.format("%s.perform(%s);", opFieldName, resourcesName);
-
-        // This is what I like about this approach; the injected body is tiny.  The downside is that
-        // the object that gets injected is hard to test, hard enough that we'll just concentrate on
-        // the integration test, thank you.
+        String runnableFieldName = transformation.addIndirectInjectedField(Runnable.class,
+                "includeAssets", provider);
 
-        transformation.extendMethod(TransformConstants.SETUP_RENDER_SIGNATURE, body);
+        transformation.extendMethod(TransformConstants.SETUP_RENDER_SIGNATURE, String.format(
+                "%s.run();", runnableFieldName));
 
         model.addRenderPhase(SetupRender.class);
     }
 
     /**
-     * Invoked, from the component's setup render phase, for each asset. This method must be threadsafe.  Most
-     * implementation pass the asset to a particular method of {@link org.apache.tapestry5.RenderSupport}.
-     *
-     * @param asset to be processed
+     * Invoked, from the component's setup render phase, for each asset. This method must be
+     * threadsafe. Most
+     * implementations pass the asset to a particular method of
+     * {@link org.apache.tapestry5.RenderSupport}.
+     * 
+     * @param asset
+     *            to be processed
      */
     protected abstract void handleAsset(Asset asset);
 }

Modified: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ClassTransformation.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ClassTransformation.java?rev=902144&r1=902143&r2=902144&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ClassTransformation.java (original)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ClassTransformation.java Fri Jan 22 16:29:21 2010
@@ -15,6 +15,8 @@
 package org.apache.tapestry5.services;
 
 import javassist.CtBehavior;
+
+import org.apache.tapestry5.ComponentResources;
 import org.apache.tapestry5.ioc.AnnotationProvider;
 import org.slf4j.Logger;
 
@@ -230,6 +232,8 @@
      * and value will return the same field name. Caching extends to the parent transformation, so
      * that a value injected
      * into a parent class will be available (via the protected instance variable) to subclasses.
+     * This is primarily used to inject service dependencies into components, though it has a number
+     * of other uses as well.
      * 
      * @param type
      *            the type of object to inject
@@ -242,7 +246,21 @@
     String addInjectedField(Class type, String suggestedName, Object value);
 
     /**
-     * Converts the field into a read only field whose value is the provided value. This is used
+     * Like {@link #addInjectedField(Class, String, Object)}, but instead of specifying the value,
+     * a provider for the value is specified. In the generated class' constructor, the provider
+     * will be passed the {@link ComponentResources} and will return the final value; thus
+     * each component <em>instance</em> will receive a unique 
+     * @param <T>
+     * @param type type of value to inject
+     * @param suggestedName suggested name for the new field
+     * @param provider injected into the component to provide the value
+     * @return the actual name of the injected field
+     * @since 5.2
+     */
+    <T> String addIndirectInjectedField(Class<T> type, String suggestedName, ComponentValueProvider<T> provider);
+    
+    /**
+     * Converts and <em>existing</em> field into a read only field whose value is the provided value. This is used
      * when converting an
      * existing field into a read-only injected value.
      * 

Added: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentValueProvider.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentValueProvider.java?rev=902144&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentValueProvider.java (added)
+++ tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentValueProvider.java Fri Jan 22 16:29:21 2010
@@ -0,0 +1,40 @@
+// Copyright 2010 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.services;
+
+import org.apache.tapestry5.ComponentResources;
+
+/**
+ * An object used to provide a value of a specific type to a component (represented by an
+ * instance of {@link ComponentResources}). The provider will create and return the value
+ * (some providers may be smart enough to cache a value, but should be implemented in
+ * a thread-safe manner). Often the provider is an inner class of a
+ * {@link ComponentClassTransformWorker}.
+ * 
+ * @param <T>
+ *            type of object provided
+ * @since 5.2.0
+ */
+public interface ComponentValueProvider<T>
+{
+    /**
+     * Provide the object for the indicated component.
+     * 
+     * @param resources
+     *            Identifies the component
+     * @return the object
+     */
+    T get(ComponentResources resources);
+}

Propchange: tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/ComponentValueProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native