tapestry-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From thiag...@apache.org
Subject [27/35] tapestry-5 git commit: First pass creating the BeanModel and Commons packages. Lots of stuff moved around, but no actual code changes so far
Date Sun, 21 Dec 2014 19:56:53 GMT
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/beanmodel/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
----------------------------------------------------------------------
diff --git a/beanmodel/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java b/beanmodel/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
new file mode 100644
index 0000000..701420f
--- /dev/null
+++ b/beanmodel/src/main/java/org/apache/tapestry5/internal/services/PropertyConduitSourceImpl.java
@@ -0,0 +1,1563 @@
+// Copyright 2007-2013 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.services;
+
+import org.antlr.runtime.ANTLRInputStream;
+import org.antlr.runtime.CommonTokenStream;
+import org.antlr.runtime.tree.Tree;
+import org.apache.tapestry5.PropertyConduit;
+import org.apache.tapestry5.PropertyConduit2;
+import org.apache.tapestry5.internal.InternalPropertyConduit;
+import org.apache.tapestry5.internal.antlr.PropertyExpressionLexer;
+import org.apache.tapestry5.internal.antlr.PropertyExpressionParser;
+import org.apache.tapestry5.internal.util.IntegerRange;
+import org.apache.tapestry5.internal.util.MultiKey;
+import org.apache.tapestry5.ioc.AnnotationProvider;
+import org.apache.tapestry5.ioc.annotations.PostInjection;
+import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.GenericsUtils;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+import org.apache.tapestry5.ioc.services.*;
+import org.apache.tapestry5.ioc.util.AvailableValues;
+import org.apache.tapestry5.ioc.util.ExceptionUtils;
+import org.apache.tapestry5.ioc.util.UnknownValueException;
+import org.apache.tapestry5.plastic.*;
+import org.apache.tapestry5.services.ComponentClasses;
+import org.apache.tapestry5.services.ComponentLayer;
+import org.apache.tapestry5.services.InvalidationEventHub;
+import org.apache.tapestry5.services.PropertyConduitSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.tapestry5.internal.antlr.PropertyExpressionParser.*;
+
+public class PropertyConduitSourceImpl implements PropertyConduitSource
+{
+    static class ConduitMethods
+    {
+        private static final MethodDescription GET = getMethodDescription(PropertyConduit.class, "get", Object.class);
+
+        private static final MethodDescription SET = getMethodDescription(PropertyConduit.class, "set", Object.class,
+                Object.class);
+
+        private static final MethodDescription GET_PROPERTY_TYPE = getMethodDescription(PropertyConduit.class,
+                "getPropertyType");
+
+        private static final MethodDescription GET_PROPERTY_GENERIC_TYPE = getMethodDescription(PropertyConduit2.class,
+                "getPropertyGenericType");
+        
+        private static final MethodDescription GET_PROPERTY_NAME = getMethodDescription(InternalPropertyConduit.class,
+                "getPropertyName");
+        
+        private static final MethodDescription GET_ANNOTATION = getMethodDescription(AnnotationProvider.class,
+                "getAnnotation", Class.class);
+
+    }
+
+    static class DelegateMethods
+    {
+        static final Method INVERT = getMethod(PropertyConduitDelegate.class, "invert", Object.class);
+
+        static final Method RANGE = getMethod(PropertyConduitDelegate.class, "range", int.class, int.class);
+
+        static final Method COERCE = getMethod(PropertyConduitDelegate.class, "coerce", Object.class, Class.class);
+    }
+
+    static class ArrayListMethods
+    {
+        static final Method ADD = getMethod(ArrayList.class, "add", Object.class);
+    }
+
+    static class HashMapMethods
+    {
+        static final Method PUT = getMethod(HashMap.class, "put", Object.class, Object.class);
+    }
+
+    private static InstructionBuilderCallback RETURN_NULL = new InstructionBuilderCallback()
+    {
+        public void doBuild(InstructionBuilder builder)
+        {
+            builder.loadNull().returnResult();
+        }
+    };
+
+    private static final String[] SINGLE_OBJECT_ARGUMENT = new String[]
+            {Object.class.getName()};
+
+    @SuppressWarnings("unchecked")
+    private static Method getMethod(Class containingClass, String name, Class... parameterTypes)
+    {
+        try
+        {
+            return containingClass.getMethod(name, parameterTypes);
+        } catch (NoSuchMethodException ex)
+        {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    private static MethodDescription getMethodDescription(Class containingClass, String name, Class... parameterTypes)
+    {
+        return new MethodDescription(getMethod(containingClass, name, parameterTypes));
+    }
+
+    private final AnnotationProvider nullAnnotationProvider = new NullAnnotationProvider();
+
+    /**
+     * How are null values in intermdiate terms to be handled?
+     */
+    private enum NullHandling
+    {
+        /**
+         * Add code to check for null and throw exception if null.
+         */
+        FORBID,
+
+        /**
+         * Add code to check for null and short-circuit (i.e., the "?."
+         * safe-dereference operator)
+         */
+        ALLOW
+    }
+
+    /**
+     * One term in an expression. Expressions start with some root type and each term advances
+     * to a new type.
+     */
+    private class Term
+    {
+        /**
+         * The generic type of the term.
+         */
+        final Type type;
+
+        final Class genericType;
+
+        /**
+         * Describes the term, for use in error messages.
+         */
+        final String description;
+
+        final AnnotationProvider annotationProvider;
+
+        /**
+         * Callback that will implement the term.
+         */
+        final InstructionBuilderCallback callback;
+
+        Term(Type type, Class genericType, String description, AnnotationProvider annotationProvider,
+             InstructionBuilderCallback callback)
+        {
+            this.type = type;
+            this.genericType = genericType;
+            this.description = description;
+            this.annotationProvider = annotationProvider;
+            this.callback = callback;
+        }
+
+        Term(Type type, String description, AnnotationProvider annotationProvider, InstructionBuilderCallback callback)
+        {
+            this(type, GenericsUtils.asClass(type), description, annotationProvider, callback);
+        }
+
+        Term(Type type, String description, InstructionBuilderCallback callback)
+        {
+            this(type, description, null, callback);
+        }
+
+        /**
+         * Returns a clone of this Term with a new callback.
+         */
+        Term withCallback(InstructionBuilderCallback newCallback)
+        {
+            return new Term(type, genericType, description, annotationProvider, newCallback);
+        }
+    }
+
+    private final PropertyAccess access;
+
+    private final PlasticProxyFactory proxyFactory;
+
+    private final TypeCoercer typeCoercer;
+
+    private final StringInterner interner;
+
+    /**
+     * Keyed on combination of root class and expression.
+     */
+    private final Map<MultiKey, PropertyConduit> cache = CollectionFactory.newConcurrentMap();
+
+    private final Invariant invariantAnnotation = new Invariant()
+    {
+        public Class<? extends Annotation> annotationType()
+        {
+            return Invariant.class;
+        }
+    };
+
+    private final AnnotationProvider invariantAnnotationProvider = new AnnotationProvider()
+    {
+        public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+        {
+            if (annotationClass == Invariant.class)
+                return annotationClass.cast(invariantAnnotation);
+
+            return null;
+        }
+    };
+
+    private final PropertyConduit literalTrue;
+
+    private final PropertyConduit literalFalse;
+
+    private final PropertyConduit literalNull;
+
+    private final PropertyConduitDelegate sharedDelegate;
+
+    /**
+     * Encapsulates the process of building a PropertyConduit instance from an
+     * expression, as an {@link PlasticClassTransformer}.
+     */
+    class PropertyConduitBuilder implements PlasticClassTransformer
+    {
+        private final Class rootType;
+
+        private final String expression;
+
+        private final Tree tree;
+
+        private Class conduitPropertyType;
+
+        private Type conduitPropertyGenericType;
+
+        private String conduitPropertyName;
+
+        private AnnotationProvider annotationProvider = nullAnnotationProvider;
+
+        private PlasticField delegateField;
+
+        private PlasticClass plasticClass;
+
+        private PlasticMethod getRootMethod, navMethod;
+
+        PropertyConduitBuilder(Class rootType, String expression, Tree tree)
+        {
+            this.rootType = rootType;
+            this.expression = expression;
+            this.tree = tree;
+        }
+
+        public void transform(PlasticClass plasticClass)
+        {
+            this.plasticClass = plasticClass;
+
+            // Create the various methods; also determine the conduit's property type, property name and identify
+            // the annotation provider.
+
+            implementNavMethodAndAccessors();
+
+            implementOtherMethods();
+
+            plasticClass.addToString(String.format("PropertyConduit[%s %s]", rootType.getName(), expression));
+        }
+
+        private void implementOtherMethods()
+        {
+            PlasticField annotationProviderField = plasticClass.introduceField(AnnotationProvider.class,
+                    "annotationProvider").inject(annotationProvider);
+
+            plasticClass.introduceMethod(ConduitMethods.GET_ANNOTATION).delegateTo(annotationProviderField);
+
+            plasticClass.introduceMethod(ConduitMethods.GET_PROPERTY_NAME, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.loadConstant(conduitPropertyName).returnResult();
+                }
+            });
+
+            final PlasticField propertyTypeField = plasticClass.introduceField(Class.class, "propertyType").inject(
+                    conduitPropertyType);
+
+            plasticClass.introduceMethod(ConduitMethods.GET_PROPERTY_TYPE, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.loadThis().getField(propertyTypeField).returnResult();
+                }
+            });
+
+            final PlasticField propertyGenericTypeField = plasticClass.introduceField(Type.class, "propertyGenericType").inject(
+                    conduitPropertyGenericType);
+
+            plasticClass.introduceMethod(ConduitMethods.GET_PROPERTY_GENERIC_TYPE, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.loadThis().getField(propertyGenericTypeField).returnResult();
+                }
+            });
+        }
+
+        /**
+         * Creates a method that does a conversion from Object to the expected root type, with
+         * a null check.
+         */
+        private void implementGetRoot()
+        {
+            getRootMethod = plasticClass.introducePrivateMethod(PlasticUtils.toTypeName(rootType), "getRoot",
+                    SINGLE_OBJECT_ARGUMENT, null);
+
+            getRootMethod.changeImplementation(new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.loadArgument(0).dupe().when(Condition.NULL, new InstructionBuilderCallback()
+                    {
+                        public void doBuild(InstructionBuilder builder)
+                        {
+                            builder.throwException(NullPointerException.class,
+                                    String.format("Root object of property expression '%s' is null.", expression));
+                        }
+                    });
+
+                    builder.checkcast(rootType).returnResult();
+                }
+            });
+        }
+
+        private boolean isLeaf(Tree node)
+        {
+            int type = node.getType();
+
+            return type != DEREF && type != SAFEDEREF;
+        }
+
+        private void implementNavMethodAndAccessors()
+        {
+            implementGetRoot();
+
+            // First, create the navigate method.
+
+            final List<InstructionBuilderCallback> callbacks = CollectionFactory.newList();
+
+            Type activeType = rootType;
+
+            Tree node = tree;
+
+            while (!isLeaf(node))
+            {
+                Term term = analyzeDerefNode(activeType, node);
+
+                callbacks.add(term.callback);
+
+                activeType = term.type;
+
+                // Second term is the continuation, possibly another chained
+                // DEREF, etc.
+                node = node.getChild(1);
+            }
+
+            Class activeClass = GenericsUtils.asClass(activeType);
+
+            if (callbacks.isEmpty())
+            {
+                navMethod = getRootMethod;
+            } else
+            {
+                navMethod = plasticClass.introducePrivateMethod(PlasticUtils.toTypeName(activeClass), "navigate",
+                        SINGLE_OBJECT_ARGUMENT, null);
+
+                navMethod.changeImplementation(new InstructionBuilderCallback()
+                {
+                    public void doBuild(InstructionBuilder builder)
+                    {
+                        builder.loadThis().loadArgument(0).invokeVirtual(getRootMethod);
+
+                        for (InstructionBuilderCallback callback : callbacks)
+                        {
+                            callback.doBuild(builder);
+                        }
+
+                        builder.returnResult();
+                    }
+                });
+            }
+
+            implementAccessors(activeType, node);
+        }
+
+        private void implementAccessors(Type activeType, Tree node)
+        {
+            switch (node.getType())
+            {
+                case IDENTIFIER:
+
+                    implementPropertyAccessors(activeType, node);
+
+                    return;
+
+                case INVOKE:
+
+                    // So, at this point, we have the navigation method written
+                    // and it covers all but the terminal
+                    // de-reference. node is an IDENTIFIER or INVOKE. We're
+                    // ready to use the navigation
+                    // method to implement get() and set().
+
+                    implementMethodAccessors(activeType, node);
+
+                    return;
+
+                case RANGEOP:
+
+                    // As currently implemented, RANGEOP can only appear as the
+                    // top level, which
+                    // means we didn't need the navigate method after all.
+
+                    implementRangeOpGetter(node);
+                    implementNoOpSetter();
+
+                    conduitPropertyType = IntegerRange.class;
+                    conduitPropertyGenericType = IntegerRange.class;
+
+                    return;
+
+                case LIST:
+
+                    implementListGetter(node);
+                    implementNoOpSetter();
+
+                    conduitPropertyType = List.class;
+                    conduitPropertyGenericType = List.class;
+                    
+                    return;
+
+                case MAP:
+                    implementMapGetter(node);
+                    implementNoOpSetter();
+
+                    conduitPropertyType = Map.class;
+                    conduitPropertyGenericType = Map.class;
+
+                    return;
+
+
+                case NOT:
+                    implementNotOpGetter(node);
+                    implementNoOpSetter();
+
+                    conduitPropertyType = boolean.class;
+                    conduitPropertyGenericType = boolean.class;
+
+                    return;
+
+                default:
+                    throw unexpectedNodeType(node, IDENTIFIER, INVOKE, RANGEOP, LIST, NOT);
+            }
+        }
+
+        public void implementMethodAccessors(final Type activeType, final Tree invokeNode)
+        {
+            final Term term = buildInvokeTerm(activeType, invokeNode);
+
+            implementNoOpSetter();
+
+            conduitPropertyName = term.description;
+            conduitPropertyType = term.genericType;
+            conduitPropertyGenericType = term.genericType;
+            annotationProvider = term.annotationProvider;
+
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeNavigateMethod(builder);
+
+                    term.callback.doBuild(builder);
+
+                    boxIfPrimitive(builder, conduitPropertyType);
+
+                    builder.returnResult();
+                }
+            });
+
+            implementNoOpSetter();
+        }
+
+        public void implementPropertyAccessors(Type activeType, Tree identifierNode)
+        {
+            String propertyName = identifierNode.getText();
+
+            PropertyAdapter adapter = findPropertyAdapter(activeType, propertyName);
+
+            conduitPropertyName = propertyName;
+            conduitPropertyType = adapter.getType();
+            conduitPropertyGenericType = getGenericType(adapter);
+            annotationProvider = adapter;
+
+            implementGetter(adapter);
+            implementSetter(adapter);
+        }
+
+        private Type getGenericType(PropertyAdapter adapter)
+        {
+        	Type genericType = null;
+        	if (adapter.getField() != null)
+        	{
+        		genericType = adapter.getField().getGenericType();
+        	}
+        	else if (adapter.getReadMethod() != null)
+        	{
+        		genericType = adapter.getReadMethod().getGenericReturnType(); 
+        	}
+        	else if (adapter.getWriteMethod() != null)
+        	{
+        		genericType = adapter.getWriteMethod().getGenericParameterTypes()[0];
+        	}
+        	else
+        	{
+        		throw new RuntimeException("Could not find accessor for property " + adapter.getName());
+        	}
+        	
+        	return genericType == null ? adapter.getType() : genericType;
+		}
+
+		private void implementSetter(PropertyAdapter adapter)
+        {
+            if (adapter.getWriteMethod() != null)
+            {
+                implementSetter(adapter.getWriteMethod());
+                return;
+            }
+
+            if (adapter.getField() != null && adapter.isUpdate())
+            {
+                implementSetter(adapter.getField());
+                return;
+            }
+
+            implementNoOpMethod(ConduitMethods.SET, "Expression '%s' for class %s is read-only.", expression,
+                    rootType.getName());
+        }
+
+        private boolean isStatic(Member member)
+        {
+            return Modifier.isStatic(member.getModifiers());
+        }
+
+        private void implementSetter(final Field field)
+        {
+            if (isStatic(field))
+            {
+                plasticClass.introduceMethod(ConduitMethods.SET, new InstructionBuilderCallback()
+                {
+                    public void doBuild(InstructionBuilder builder)
+                    {
+                        builder.loadArgument(1).castOrUnbox(PlasticUtils.toTypeName(field.getType()));
+
+                        builder.putStaticField(field.getDeclaringClass().getName(), field.getName(), field.getType());
+
+                        builder.returnResult();
+                    }
+                });
+
+                return;
+            }
+
+            plasticClass.introduceMethod(ConduitMethods.SET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeNavigateMethod(builder);
+
+                    builder.loadArgument(1).castOrUnbox(PlasticUtils.toTypeName(field.getType()));
+
+                    builder.putField(field.getDeclaringClass().getName(), field.getName(), field.getType());
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        private void implementSetter(final Method writeMethod)
+        {
+            plasticClass.introduceMethod(ConduitMethods.SET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeNavigateMethod(builder);
+
+                    Class propertyType = writeMethod.getParameterTypes()[0];
+                    String propertyTypeName = PlasticUtils.toTypeName(propertyType);
+
+                    builder.loadArgument(1).castOrUnbox(propertyTypeName);
+
+                    builder.invoke(writeMethod);
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        private void implementGetter(PropertyAdapter adapter)
+        {
+            if (adapter.getReadMethod() != null)
+            {
+                implementGetter(adapter.getReadMethod());
+                return;
+            }
+
+            if (adapter.getField() != null)
+            {
+                implementGetter(adapter.getField());
+                return;
+            }
+
+            implementNoOpMethod(ConduitMethods.GET, "Expression '%s' for class %s is write-only.", expression,
+                    rootType.getName());
+        }
+
+        private void implementGetter(final Field field)
+        {
+            if (isStatic(field))
+            {
+                plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+                {
+                    public void doBuild(InstructionBuilder builder)
+                    {
+                        builder.getStaticField(field.getDeclaringClass().getName(), field.getName(), field.getType());
+
+                        // Cast not necessary here since the return type of get() is Object
+
+                        boxIfPrimitive(builder, field.getType());
+
+                        builder.returnResult();
+                    }
+                });
+
+                return;
+            }
+
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeNavigateMethod(builder);
+
+                    builder.getField(field.getDeclaringClass().getName(), field.getName(), field.getType());
+
+                    // Cast not necessary here since the return type of get() is Object
+
+                    boxIfPrimitive(builder, field.getType());
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        private void implementGetter(final Method readMethod)
+        {
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeNavigateMethod(builder);
+
+                    invokeMethod(builder, readMethod, null, 0);
+
+                    boxIfPrimitive(builder, conduitPropertyType);
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        private void implementRangeOpGetter(final Tree rangeNode)
+        {
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    // Put the delegate on top of the stack
+
+                    builder.loadThis().getField(getDelegateField());
+
+                    invokeMethod(builder, DelegateMethods.RANGE, rangeNode, 0);
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        /**
+         * @param node
+         *         subexpression to invert
+         */
+        private void implementNotOpGetter(final Tree node)
+        {
+            // Implement get() as navigate, then do a method invocation based on node
+            // then, then pass (wrapped) result to delegate.invert()
+
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    Type expressionType = implementNotExpression(builder, node);
+
+                    // Yes, we know this will always be the case, for now.
+
+                    boxIfPrimitive(builder, expressionType);
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        /**
+         * The first part of any implementation of get() or set(): invoke the navigation method
+         * and if the result is null, return immediately.
+         */
+        private void invokeNavigateMethod(InstructionBuilder builder)
+        {
+            builder.loadThis().loadArgument(0).invokeVirtual(navMethod);
+
+            builder.dupe().when(Condition.NULL, RETURN_NULL);
+        }
+
+        /**
+         * Uses the builder to add instructions for a subexpression.
+         *
+         * @param builder
+         *         used to add instructions
+         * @param activeType
+         *         type of value on top of the stack when this code will execute, or null if no value on stack
+         * @param node
+         *         defines the expression
+         * @return the expression type
+         */
+        private Type implementSubexpression(InstructionBuilder builder, Type activeType, Tree node)
+        {
+            Term term;
+
+            while (true)
+            {
+                switch (node.getType())
+                {
+                    case IDENTIFIER:
+                    case INVOKE:
+
+                        if (activeType == null)
+                        {
+                            invokeGetRootMethod(builder);
+
+                            activeType = rootType;
+                        }
+
+                        term = buildTerm(activeType, node);
+
+                        term.callback.doBuild(builder);
+
+                        return term.type;
+
+                    case INTEGER:
+
+                        builder.loadConstant(new Long(node.getText()));
+
+                        return long.class;
+
+                    case DECIMAL:
+
+                        builder.loadConstant(new Double(node.getText()));
+
+                        return double.class;
+
+                    case STRING:
+
+                        builder.loadConstant(node.getText());
+
+                        return String.class;
+
+                    case DEREF:
+                    case SAFEDEREF:
+
+                        if (activeType == null)
+                        {
+                            invokeGetRootMethod(builder);
+
+                            activeType = rootType;
+                        }
+
+                        term = analyzeDerefNode(activeType, node);
+
+                        term.callback.doBuild(builder);
+
+                        activeType = GenericsUtils.asClass(term.type);
+
+                        node = node.getChild(1);
+
+                        break;
+
+                    case TRUE:
+                    case FALSE:
+
+                        builder.loadConstant(node.getType() == TRUE ? 1 : 0);
+
+                        return boolean.class;
+
+                    case LIST:
+
+                        return implementListConstructor(builder, node);
+
+                    case MAP:
+                        return implementMapConstructor(builder, node);
+
+                    case NOT:
+
+                        return implementNotExpression(builder, node);
+
+                    case THIS:
+
+                        invokeGetRootMethod(builder);
+
+                        return rootType;
+
+                    case NULL:
+
+                        builder.loadNull();
+
+                        return Void.class;
+
+                    default:
+                        throw unexpectedNodeType(node, TRUE, FALSE, INTEGER, DECIMAL, STRING, DEREF, SAFEDEREF,
+                                IDENTIFIER, INVOKE, LIST, NOT, THIS, NULL);
+                }
+            }
+        }
+
+        public void invokeGetRootMethod(InstructionBuilder builder)
+        {
+            builder.loadThis().loadArgument(0).invokeVirtual(getRootMethod);
+        }
+
+        private void implementListGetter(final Tree listNode)
+        {
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    implementListConstructor(builder, listNode);
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        private Type implementListConstructor(InstructionBuilder builder, Tree listNode)
+        {
+            // First, create an empty instance of ArrayList
+
+            int count = listNode.getChildCount();
+
+            builder.newInstance(ArrayList.class);
+            builder.dupe().loadConstant(count).invokeConstructor(ArrayList.class, int.class);
+
+            for (int i = 0; i < count; i++)
+            {
+                builder.dupe(); // the ArrayList
+
+                Type expressionType = implementSubexpression(builder, null, listNode.getChild(i));
+
+                boxIfPrimitive(builder, GenericsUtils.asClass(expressionType));
+
+                // Add the value to the array, then pop off the returned boolean
+                builder.invoke(ArrayListMethods.ADD).pop();
+            }
+
+            return ArrayList.class;
+        }
+
+        private void implementMapGetter(final Tree mapNode)
+        {
+            plasticClass.introduceMethod(ConduitMethods.GET, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    implementMapConstructor(builder, mapNode);
+
+                    builder.returnResult();
+                }
+            });
+        }
+
+        private Type implementMapConstructor(InstructionBuilder builder, Tree mapNode)
+        {
+            int count = mapNode.getChildCount();
+            builder.newInstance(HashMap.class);
+            builder.dupe().loadConstant(count).invokeConstructor(HashMap.class, int.class);
+
+            for (int i = 0; i < count; i += 2)
+            {
+                builder.dupe();
+
+                //build the key:
+                Type keyType = implementSubexpression(builder, null, mapNode.getChild(i));
+                boxIfPrimitive(builder, GenericsUtils.asClass(keyType));
+
+                //and the value:
+                Type valueType = implementSubexpression(builder, null, mapNode.getChild(i + 1));
+                boxIfPrimitive(builder, GenericsUtils.asClass(valueType));
+
+                //put the value into the array, then pop off the returned object.
+                builder.invoke(HashMapMethods.PUT).pop();
+
+            }
+
+            return HashMap.class;
+        }
+
+
+        private void implementNoOpSetter()
+        {
+            implementNoOpMethod(ConduitMethods.SET, "Expression '%s' for class %s is read-only.", expression,
+                    rootType.getName());
+        }
+
+        public void implementNoOpMethod(MethodDescription method, String format, Object... arguments)
+        {
+            final String message = String.format(format, arguments);
+
+            plasticClass.introduceMethod(method).changeImplementation(new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    builder.throwException(RuntimeException.class, message);
+                }
+            });
+        }
+
+        /**
+         * Invokes a method that may take parameters. The children of the invokeNode are subexpressions
+         * to be evaluated, and potentially coerced, so that they may be passed to the method.
+         *
+         * @param builder
+         *         constructs code
+         * @param method
+         *         method to invoke
+         * @param node
+         *         INVOKE or RANGEOP node
+         * @param childOffset
+         *         offset within the node to the first child expression (1 in an INVOKE node because the
+         *         first child is the method name, 0 in a RANGEOP node)
+         */
+        private void invokeMethod(InstructionBuilder builder, Method method, Tree node, int childOffset)
+        {
+            // We start with the target object for the method on top of the stack.
+            // Next, we have to push each method parameter, which may include boxing/deboxing
+            // and coercion. Once the code is in good shape, there's a lot of room to optimize
+            // the bytecode (a bit too much boxing/deboxing occurs, as well as some unnecessary
+            // trips through TypeCoercer). We might also want to have a local variable to store
+            // the root object (result of getRoot()).
+
+            Class[] parameterTypes = method.getParameterTypes();
+
+            for (int i = 0; i < parameterTypes.length; i++)
+            {
+                Type expressionType = implementSubexpression(builder, null, node.getChild(i + childOffset));
+
+                // The value left on the stack is not primitive, and expressionType represents
+                // its real type.
+
+                Class parameterType = parameterTypes[i];
+
+                if (!parameterType.isAssignableFrom(GenericsUtils.asClass(expressionType)))
+                {
+                    boxIfPrimitive(builder, expressionType);
+
+                    builder.loadThis().getField(getDelegateField());
+                    builder.swap().loadTypeConstant(PlasticUtils.toWrapperType(parameterType));
+                    builder.invoke(DelegateMethods.COERCE);
+
+                    if (parameterType.isPrimitive())
+                    {
+                        builder.castOrUnbox(parameterType.getName());
+                    } else
+                    {
+                        builder.checkcast(parameterType);
+                    }
+                }
+
+                // And that should leave an object of the correct type on the stack,
+                // ready for the method invocation.
+            }
+
+            // Now the target object and all parameters are in place.
+
+            builder.invoke(method.getDeclaringClass(), method.getReturnType(), method.getName(),
+                    method.getParameterTypes());
+        }
+
+        /**
+         * Analyzes a DEREF or SAFEDEREF node, proving back a term that identifies its type and provides a callback to
+         * peform the dereference.
+         *
+         * @return a term indicating the type of the expression to this point, and a {@link InstructionBuilderCallback}
+         *         to advance the evaluation of the expression form the previous value to the current
+         */
+        private Term analyzeDerefNode(Type activeType, Tree node)
+        {
+            // The first child is the term.
+
+            Tree term = node.getChild(0);
+
+            boolean allowNull = node.getType() == SAFEDEREF;
+
+            return buildTerm(activeType, term, allowNull ? NullHandling.ALLOW : NullHandling.FORBID);
+        }
+
+        private Term buildTerm(Type activeType, Tree term, final NullHandling nullHandling)
+        {
+            assertNodeType(term, IDENTIFIER, INVOKE);
+
+            final Term simpleTerm = buildTerm(activeType, term);
+
+            if (simpleTerm.genericType.isPrimitive())
+                return simpleTerm;
+
+            return simpleTerm.withCallback(new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    simpleTerm.callback.doBuild(builder);
+
+                    builder.dupe().when(Condition.NULL, new InstructionBuilderCallback()
+                    {
+                        public void doBuild(InstructionBuilder builder)
+                        {
+                            switch (nullHandling)
+                            {
+                                // It is necessary to load a null onto the stack (even if there's already one
+                                // there) because of the verifier. It sees the return when the stack contains an
+                                // intermediate value (along the navigation chain) and thinks the method is
+                                // returning a value of the wrong type.
+
+                                case ALLOW:
+                                    builder.loadNull().returnResult();
+
+                                case FORBID:
+
+                                    builder.loadConstant(simpleTerm.description);
+                                    builder.loadConstant(expression);
+                                    builder.loadArgument(0);
+
+                                    builder.invokeStatic(PropertyConduitSourceImpl.class, NullPointerException.class,
+                                            "nullTerm", String.class, String.class, Object.class);
+                                    builder.throwException();
+
+                                    break;
+
+                            }
+                        }
+                    });
+                }
+            });
+        }
+
+        private void assertNodeType(Tree node, int... expected)
+        {
+            int type = node.getType();
+
+            for (int e : expected)
+            {
+                if (type == e)
+                    return;
+            }
+
+            throw unexpectedNodeType(node, expected);
+        }
+
+        private RuntimeException unexpectedNodeType(Tree node, int... expected)
+        {
+            List<String> tokenNames = CollectionFactory.newList();
+
+            for (int i = 0; i < expected.length; i++)
+                tokenNames.add(PropertyExpressionParser.tokenNames[expected[i]]);
+
+            String message = String.format("Node %s was type %s, but was expected to be (one of) %s.",
+                    node.toStringTree(), PropertyExpressionParser.tokenNames[node.getType()],
+                    InternalUtils.joinSorted(tokenNames));
+
+            return new RuntimeException(message);
+        }
+
+        private Term buildTerm(Type activeType, Tree termNode)
+        {
+            switch (termNode.getType())
+            {
+                case INVOKE:
+
+                    return buildInvokeTerm(activeType, termNode);
+
+                case IDENTIFIER:
+
+                    return buildPropertyAccessTerm(activeType, termNode);
+
+                default:
+                    throw unexpectedNodeType(termNode, INVOKE, IDENTIFIER);
+            }
+        }
+
+        private Term buildPropertyAccessTerm(Type activeType, Tree termNode)
+        {
+            String propertyName = termNode.getText();
+
+            PropertyAdapter adapter = findPropertyAdapter(activeType, propertyName);
+
+            // Prefer the accessor over the field
+
+            if (adapter.getReadMethod() != null)
+            {
+                return buildGetterMethodAccessTerm(activeType, propertyName,
+                        adapter.getReadMethod());
+            }
+
+            if (adapter.getField() != null)
+            {
+                return buildPublicFieldAccessTerm(activeType, propertyName,
+                        adapter.getField());
+            }
+
+            throw new RuntimeException(String.format(
+                    "Property '%s' of class %s is not readable (it has no read accessor method).", adapter.getName(),
+                    adapter.getBeanType().getName()));
+        }
+
+        public PropertyAdapter findPropertyAdapter(Type activeType, String propertyName)
+        {
+            Class activeClass = GenericsUtils.asClass(activeType);
+
+            ClassPropertyAdapter classAdapter = access.getAdapter(activeClass);
+            PropertyAdapter adapter = classAdapter.getPropertyAdapter(propertyName);
+
+            if (adapter == null)
+            {
+                final List<String> names = classAdapter.getPropertyNames();
+                final String className = activeClass.getName();
+                throw new UnknownValueException(String.format(
+                        "Class %s does not contain a property (or public field) named '%s'.", className, propertyName),
+                        new AvailableValues("Properties (and public fields)", names));
+            }
+            return adapter;
+        }
+
+        private Term buildGetterMethodAccessTerm(final Type activeType, String propertyName, final Method readMethod)
+        {
+            Type returnType = GenericsUtils.extractActualType(activeType, readMethod);
+
+            return new Term(returnType, propertyName, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeMethod(builder, readMethod, null, 0);
+
+                    Type genericType = GenericsUtils.extractActualType(activeType, readMethod);
+
+                    castToGenericType(builder, readMethod.getReturnType(), genericType);
+                }
+            });
+        }
+
+        private Term buildPublicFieldAccessTerm(Type activeType, String propertyName, final Field field)
+        {
+            final Type fieldType = GenericsUtils.extractActualType(activeType, field);
+
+            return new Term(fieldType, propertyName, new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    Class rawFieldType = field.getType();
+
+                    String rawTypeName = PlasticUtils.toTypeName(rawFieldType);
+                    String containingClassName = field.getDeclaringClass().getName();
+                    String fieldName = field.getName();
+
+                    if (isStatic(field))
+                    {
+                        // We've gone to the trouble of loading the root object, or navigated to some other object,
+                        // but we don't need or want the instance, since it's a static field we're accessing.
+                        // Ideally, we would optimize this, and only generate and invoke the getRoot() and nav() methods as needed, but
+                        // access to public fields is relatively rare, and the cost is just the unused bytecode.
+
+                        builder.pop();
+
+                        builder.getStaticField(containingClassName, fieldName, rawTypeName);
+
+                    } else
+                    {
+                        builder.getField(containingClassName, fieldName, rawTypeName);
+                    }
+
+                    castToGenericType(builder, rawFieldType, fieldType);
+                }
+
+            });
+        }
+
+        /**
+         * Casts the results of a field read or method invocation based on generic information.
+         *
+         * @param builder
+         *         used to add instructions
+         * @param rawType
+         *         the simple type (often Object) of the field (or method return type)
+         * @param genericType
+         *         the generic Type, from which parameterizations can be determined
+         */
+        private void castToGenericType(InstructionBuilder builder, Class rawType, final Type genericType)
+        {
+            if (!genericType.equals(rawType))
+            {
+                Class castType = GenericsUtils.asClass(genericType);
+                builder.checkcast(castType);
+            }
+        }
+
+        private Term buildInvokeTerm(final Type activeType, final Tree invokeNode)
+        {
+            String methodName = invokeNode.getChild(0).getText();
+
+            int parameterCount = invokeNode.getChildCount() - 1;
+
+            Class activeClass = GenericsUtils.asClass(activeType);
+
+            final Method method = findMethod(activeClass, methodName, parameterCount);
+
+            if (method.getReturnType().equals(void.class))
+                throw new RuntimeException(String.format("Method %s.%s() returns void.", activeClass.getName(),
+                        methodName));
+
+            Type returnType = GenericsUtils.extractActualType(activeType, method);
+
+            return new Term(returnType, toUniqueId(method), InternalUtils.toAnnotationProvider(method), new InstructionBuilderCallback()
+            {
+                public void doBuild(InstructionBuilder builder)
+                {
+                    invokeMethod(builder, method, invokeNode, 1);
+
+                    Type genericType = GenericsUtils.extractActualType(activeType, method);
+
+                    castToGenericType(builder, method.getReturnType(), genericType);
+                }
+            }
+            );
+        }
+
+        private Method findMethod(Class activeType, String methodName, int parameterCount)
+        {
+            Class searchType = activeType;
+
+            while (true)
+            {
+
+                for (Method method : searchType.getMethods())
+                {
+                    if (method.getParameterTypes().length == parameterCount
+                            && method.getName().equalsIgnoreCase(methodName))
+                        return method;
+                }
+
+                // TAP5-330
+                if (searchType != Object.class)
+                {
+                    searchType = Object.class;
+                } else
+                {
+                    throw new RuntimeException(String.format("Class %s does not contain a public method named '%s()'.",
+                            activeType.getName(), methodName));
+                }
+            }
+        }
+
+        public void boxIfPrimitive(InstructionBuilder builder, Type termType)
+        {
+            boxIfPrimitive(builder, GenericsUtils.asClass(termType));
+        }
+
+        public void boxIfPrimitive(InstructionBuilder builder, Class termType)
+        {
+            if (termType.isPrimitive())
+                builder.boxPrimitive(termType.getName());
+        }
+
+        public Class implementNotExpression(InstructionBuilder builder, final Tree notNode)
+        {
+            Type expressionType = implementSubexpression(builder, null, notNode.getChild(0));
+
+            boxIfPrimitive(builder, expressionType);
+
+            // Now invoke the delegate invert() method
+
+            builder.loadThis().getField(getDelegateField());
+
+            builder.swap().invoke(DelegateMethods.INVERT);
+
+            return boolean.class;
+        }
+
+        /**
+         * Defer creation of the delegate field unless actually needed.
+         */
+        private PlasticField getDelegateField()
+        {
+            if (delegateField == null)
+                delegateField = plasticClass.introduceField(PropertyConduitDelegate.class, "delegate").inject(
+                        sharedDelegate);
+
+            return delegateField;
+        }
+    }
+
+    public PropertyConduitSourceImpl(PropertyAccess access, @ComponentLayer
+    PlasticProxyFactory proxyFactory, TypeCoercer typeCoercer, StringInterner interner)
+    {
+        this.access = access;
+        this.proxyFactory = proxyFactory;
+        this.typeCoercer = typeCoercer;
+        this.interner = interner;
+
+        literalTrue = createLiteralConduit(Boolean.class, true);
+        literalFalse = createLiteralConduit(Boolean.class, false);
+        literalNull = createLiteralConduit(Void.class, null);
+
+        sharedDelegate = new PropertyConduitDelegate(typeCoercer);
+    }
+
+    @PostInjection
+    public void listenForInvalidations(@ComponentClasses InvalidationEventHub hub)
+    {
+        hub.clearOnInvalidation(cache);
+    }
+
+
+    public PropertyConduit create(Class rootClass, String expression)
+    {
+        assert rootClass != null;
+        assert InternalUtils.isNonBlank(expression);
+
+        MultiKey key = new MultiKey(rootClass, expression);
+
+        PropertyConduit result = cache.get(key);
+
+        if (result == null)
+        {
+            result = build(rootClass, expression);
+            cache.put(key, result);
+        }
+
+        return result;
+    }
+
+    /**
+     * Builds a subclass of {@link PropertyConduitDelegate} that implements the
+     * get() and set() methods and overrides the
+     * constructor. In a worst-case race condition, we may build two (or more)
+     * conduits for the same
+     * rootClass/expression, and it will get sorted out when the conduit is
+     * stored into the cache.
+     *
+     * @param rootClass
+     *         class of root object for expression evaluation
+     * @param expression
+     *         expression to be evaluated
+     * @return the conduit
+     */
+    private PropertyConduit build(final Class rootClass, String expression)
+    {
+        Tree tree = parse(expression);
+
+        try
+        {
+            switch (tree.getType())
+            {
+                case TRUE:
+
+                    return literalTrue;
+
+                case FALSE:
+
+                    return literalFalse;
+
+                case NULL:
+
+                    return literalNull;
+
+                case INTEGER:
+
+                    // Leading '+' may screw this up.
+                    // TODO: Singleton instance for "0", maybe "1"?
+
+                    return createLiteralConduit(Long.class, new Long(tree.getText()));
+
+                case DECIMAL:
+
+                    // Leading '+' may screw this up.
+                    // TODO: Singleton instance for "0.0"?
+
+                    return createLiteralConduit(Double.class, new Double(tree.getText()));
+
+                case STRING:
+
+                    return createLiteralConduit(String.class, tree.getText());
+
+                case RANGEOP:
+
+                    Tree fromNode = tree.getChild(0);
+                    Tree toNode = tree.getChild(1);
+
+                    // If the range is defined as integers (not properties, etc.)
+                    // then it is possible to calculate the value here, once, and not
+                    // build a new class.
+
+                    if (fromNode.getType() != INTEGER || toNode.getType() != INTEGER)
+                        break;
+
+                    int from = Integer.parseInt(fromNode.getText());
+                    int to = Integer.parseInt(toNode.getText());
+
+                    IntegerRange ir = new IntegerRange(from, to);
+
+                    return createLiteralConduit(IntegerRange.class, ir);
+
+                case THIS:
+
+                    return createLiteralThisPropertyConduit(rootClass);
+
+                default:
+                    break;
+            }
+
+            return proxyFactory.createProxy(InternalPropertyConduit.class,
+                    new PropertyConduitBuilder(rootClass, expression, tree)).newInstance();
+        } catch (Exception ex)
+        {
+            throw new PropertyExpressionException(String.format("Exception generating conduit for expression '%s': %s",
+                    expression, ExceptionUtils.toMessage(ex)), expression, ex);
+        }
+    }
+
+    private PropertyConduit createLiteralThisPropertyConduit(final Class rootClass)
+    {
+        return new PropertyConduit()
+        {
+            public Object get(Object instance)
+            {
+                return instance;
+            }
+
+            public void set(Object instance, Object value)
+            {
+                throw new RuntimeException("Literal values are not updateable.");
+            }
+
+            public Class getPropertyType()
+            {
+                return rootClass;
+            }
+            
+            public Type getPropertyGenericType()
+            {
+            	return rootClass;
+            }
+
+            public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+            {
+                return invariantAnnotationProvider.getAnnotation(annotationClass);
+            }
+        };
+    }
+
+    private <T> PropertyConduit createLiteralConduit(Class<T> type, T value)
+    {
+        return new LiteralPropertyConduit(typeCoercer, type, invariantAnnotationProvider, interner.format(
+                "LiteralPropertyConduit[%s]", value), value);
+    }
+
+    private Tree parse(String expression)
+    {
+        InputStream is = new ByteArrayInputStream(expression.getBytes());
+
+        ANTLRInputStream ais;
+
+        try
+        {
+            ais = new ANTLRInputStream(is);
+        } catch (IOException ex)
+        {
+            throw new RuntimeException(ex);
+        }
+
+        PropertyExpressionLexer lexer = new PropertyExpressionLexer(ais);
+
+        CommonTokenStream tokens = new CommonTokenStream(lexer);
+
+        PropertyExpressionParser parser = new PropertyExpressionParser(tokens);
+
+        try
+        {
+            return (Tree) parser.start().getTree();
+        } catch (Exception ex)
+        {
+            throw new RuntimeException(String.format("Error parsing property expression '%s': %s.", expression,
+                    ex.getMessage()), ex);
+        }
+    }
+
+    /**
+     * May be invoked from fabricated PropertyConduit instances.
+     */
+    @SuppressWarnings("unused")
+    public static NullPointerException nullTerm(String term, String expression, Object root)
+    {
+        String message = String.format("Property '%s' (within property expression '%s', of %s) is null.", term,
+                expression, root);
+
+        return new NullPointerException(message);
+    }
+
+    private static String toUniqueId(Method method)
+    {
+        StringBuilder builder = new StringBuilder(method.getName()).append("(");
+        String sep = "";
+
+        for (Class parameterType : method.getParameterTypes())
+        {
+            builder.append(sep);
+            builder.append(PlasticUtils.toTypeName(parameterType));
+
+            sep = ",";
+        }
+
+        return builder.append(")").toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/beanmodel/src/main/java/org/apache/tapestry5/services/BeanModelSource.java
----------------------------------------------------------------------
diff --git a/beanmodel/src/main/java/org/apache/tapestry5/services/BeanModelSource.java b/beanmodel/src/main/java/org/apache/tapestry5/services/BeanModelSource.java
new file mode 100644
index 0000000..16b4fca
--- /dev/null
+++ b/beanmodel/src/main/java/org/apache/tapestry5/services/BeanModelSource.java
@@ -0,0 +1,70 @@
+// Copyright 2007, 2008 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.beaneditor.BeanModel;
+import org.apache.tapestry5.ioc.Messages;
+
+/**
+ * Used by a component to create a default {@link org.apache.tapestry5.beaneditor.BeanModel} for a particular bean
+ * class. Also provides support to the model by generating validation information for individual fields.
+ * <p/>
+ * BeanModels are the basis for the {@link org.apache.tapestry5.corelib.components.BeanEditor} and {@link
+ * org.apache.tapestry5.corelib.components.Grid} comopnents.
+ *
+ * @see org.apache.tapestry5.services.PropertyConduitSource
+ */
+public interface BeanModelSource
+{
+    /**
+     * Creates a new model used for editing the indicated bean class. The model will represent all read/write properties
+     * of the bean. The order of properties is determined from the order of the getter methods in the code, and can be
+     * overridden with the {@link org.apache.tapestry5.beaneditor.ReorderProperties} annotation. The labels for the
+     * properties are derived from the property names, but if the component's message catalog has keys of the form
+     * <code>propertyName-label</code>, then those will be used instead.
+     * <p/>
+     * Models are <em>mutable</em>, so they are not cached, a fresh instance is created each time.
+     *
+     * @param beanClass                class of object to be edited
+     * @param filterReadOnlyProperties if true, then properties that are read-only will be skipped (leaving only
+     *                                 read-write properties, appropriate for {@link org.apache.tapestry5.corelib.components.BeanEditForm},
+     *                                 etc.). If false, then both read-only and read-write properties will be included
+     *                                 (appropriate for {@link org.apache.tapestry5.corelib.components.Grid} or {@link
+     *                                 org.apache.tapestry5.corelib.components.BeanDisplay}).
+     * @param messages                 Used to find explicit overrides of
+     * @return a model
+     * @deprecated use {@link #createDisplayModel(Class, org.apache.tapestry5.ioc.Messages)} or {@link
+     *             #createEditModel(Class, org.apache.tapestry5.ioc.Messages)}
+     */
+    <T> BeanModel<T> create(Class<T> beanClass, boolean filterReadOnlyProperties, Messages messages);
+
+    /**
+     * Creates a model for display purposes; this may include properties which are read-only.
+     *
+     * @param beanClass class of object to be edited
+     * @param messages
+     * @return a model containing properties that can be presented to the user
+     */
+    <T> BeanModel<T> createDisplayModel(Class<T> beanClass, Messages messages);
+
+    /**
+     * Creates a model for edit and update purposes, only properties that are fully read-write are included.
+     *
+     * @param beanClass class of object to be edited
+     * @param messages
+     * @return a model containing properties that can be presented to the user
+     */
+    <T> BeanModel<T> createEditModel(Class<T> beanClass, Messages messages);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/beanmodel/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java
----------------------------------------------------------------------
diff --git a/beanmodel/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java b/beanmodel/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java
new file mode 100644
index 0000000..312cd60
--- /dev/null
+++ b/beanmodel/src/main/java/org/apache/tapestry5/services/PropertyConduitSource.java
@@ -0,0 +1,41 @@
+// Copyright 2007, 2008, 2009 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.PropertyConduit;
+
+/**
+ * A source for {@link org.apache.tapestry5.PropertyConduit}s, which can be thought of as a compiled property path
+ * expression. PropertyConduits are the basis of the "prop:" binding factory, thus this service defines the expression
+ * format used by the {@link org.apache.tapestry5.internal.bindings.PropBindingFactory}.
+ */
+public interface PropertyConduitSource
+{
+    /**
+     * Returns a property conduit instance for the given expression. PropertyConduitSource caches the conduits it
+     * returns, so despite the name, this method does not always create a <em>new</em> conduit. The cache is cleared if
+     * a change to component classes is observed.
+     * <p/>
+     * Callers of this method should observe notifications from the {@link org.apache.tapestry5.services.InvalidationEventHub}
+     * for {@link org.apache.tapestry5.services.ComponentClasses} and discard any aquired conduits; failure to do so
+     * will create memory leaks whenever component classes change (the conduits will keep references to the old classes
+     * and classloaders).
+     *
+     * @param rootType   the type of the root object to which the expression is applied
+     * @param expression expression to be evaluated on instances of the root class
+     * @return RuntimeException if the expression is invalid (poorly formed, references non-existent properties, etc.)
+     */
+    PropertyConduit create(Class rootType, String expression);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/build.gradle
----------------------------------------------------------------------
diff --git a/commons/build.gradle b/commons/build.gradle
new file mode 100644
index 0000000..76850ef
--- /dev/null
+++ b/commons/build.gradle
@@ -0,0 +1,18 @@
+import org.gradle.plugins.ide.idea.model.*
+import t5build.*
+
+description = "Project including common classes for tapestry-core, tapestry-ioc and beanmodel."
+
+//apply plugin: JavaPlugin
+
+buildDir = 'target/gradle-build'
+       
+dependencies {
+	compile project(":plastic")
+	compile project(":tapestry5-annotations")
+}
+
+jar {	
+	manifest {	
+	}
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/internal/util/IntegerRange.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/internal/util/IntegerRange.java b/commons/src/main/java/org/apache/tapestry5/internal/util/IntegerRange.java
new file mode 100644
index 0000000..7b2b7ab
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/internal/util/IntegerRange.java
@@ -0,0 +1,125 @@
+// Copyright 2006 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.util;
+
+import java.util.Iterator;
+
+/**
+ * Represents a sequence of integer values, either ascending or descending. The sequence is always inclusive (of the
+ * finish value).
+ */
+public final class IntegerRange implements Iterable<Integer>
+{
+    private final int start;
+
+    private final int finish;
+
+    private class RangeIterator implements Iterator<Integer>
+    {
+        private final int increment;
+
+        private int value = start;
+
+        private boolean hasNext = true;
+
+        RangeIterator()
+        {
+            increment = start < finish ? +1 : -1;
+        }
+
+        public boolean hasNext()
+        {
+            return hasNext;
+        }
+
+        public Integer next()
+        {
+            if (!hasNext) throw new IllegalStateException();
+
+            int result = value;
+
+            hasNext = value != finish;
+
+            value += increment;
+
+            return result;
+        }
+
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+    }
+
+    public IntegerRange(final int start, final int finish)
+    {
+        this.start = start;
+        this.finish = finish;
+    }
+
+    public int getFinish()
+    {
+        return finish;
+    }
+
+    public int getStart()
+    {
+        return start;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%d..%d", start, finish);
+    }
+
+    /**
+     * The main puprose of a range object is to produce an Iterator. Since IntegerRange is iterable, it is useful with
+     * the Tapestry Loop component, but also with the Java for loop!
+     */
+    public Iterator<Integer> iterator()
+    {
+        return new RangeIterator();
+    }
+
+    @Override
+    public int hashCode()
+    {
+        final int PRIME = 31;
+
+        int result = PRIME + finish;
+
+        result = PRIME * result + start;
+
+        return result;
+    }
+
+    /**
+     * Returns true if the other object is an IntegerRange with the same start and finish values.
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj) return true;
+        if (obj == null) return false;
+        if (getClass() != obj.getClass()) return false;
+        final IntegerRange other = (IntegerRange) obj;
+        if (finish != other.finish) return false;
+
+        return start == other.start;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/internal/util/MultiKey.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/internal/util/MultiKey.java b/commons/src/main/java/org/apache/tapestry5/internal/util/MultiKey.java
new file mode 100644
index 0000000..503bb1f
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/internal/util/MultiKey.java
@@ -0,0 +1,86 @@
+// Copyright 2006 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.util;
+
+import java.util.Arrays;
+
+/**
+ * Combines multiple values to form a single composite key. MultiKey can often be used as an alternative to nested
+ * maps.
+ */
+public final class MultiKey
+{
+    private static final int PRIME = 31;
+
+    private final Object[] values;
+
+    private final int hashCode;
+
+    /**
+     * Creates a new instance from the provided values. It is assumed that the values provided are good map keys
+     * themselves -- immutable, with proper implementations of equals() and hashCode().
+     *
+     * @param values
+     */
+    public MultiKey(Object... values)
+    {
+        this.values = values;
+
+        hashCode = PRIME * Arrays.hashCode(this.values);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        final MultiKey other = (MultiKey) obj;
+
+        return Arrays.equals(values, other.values);
+    }
+
+    @Override
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder("MultiKey[");
+
+        boolean first = true;
+
+        for (Object o : values)
+        {
+            if (!first)
+                builder.append(", ");
+
+            builder.append(o);
+
+            first = false;
+        }
+
+        builder.append("]");
+
+        return builder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/ioc/AnnotationProvider.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/AnnotationProvider.java b/commons/src/main/java/org/apache/tapestry5/ioc/AnnotationProvider.java
new file mode 100644
index 0000000..1f0e744
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/AnnotationProvider.java
@@ -0,0 +1,33 @@
+// Copyright 2007, 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.ioc;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * A source of annotations. This interface is used to mask where the annotations come from (for example, from a Method,
+ * a Class, or some other source).
+ */
+public interface AnnotationProvider
+{
+    /**
+     * Searches for the specified annotation, returning the matching annotation instance.
+     *
+     * @param <T>
+     * @param annotationClass used to select the annotation to return
+     * @return the annotation, or null if not found
+     */
+    <T extends Annotation> T getAnnotation(Class<T> annotationClass);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/ioc/Locatable.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/Locatable.java b/commons/src/main/java/org/apache/tapestry5/ioc/Locatable.java
new file mode 100644
index 0000000..37b6551
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/Locatable.java
@@ -0,0 +1,27 @@
+// Copyright 2006, 2008 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.ioc;
+
+/**
+ * Interface implemented by objects which carry a location tag. Defines a readable property, location.
+ */
+@SuppressWarnings({"JavaDoc"})
+public interface Locatable
+{
+    /**
+     * Returns the location associated with this object for error reporting purposes.
+     */
+    Location getLocation();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/ioc/Location.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/Location.java b/commons/src/main/java/org/apache/tapestry5/ioc/Location.java
new file mode 100644
index 0000000..e6688c2
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/Location.java
@@ -0,0 +1,38 @@
+// Copyright 2006 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.ioc;
+
+/**
+ * A kind of tag applied to other objects to identify where they came from, in terms of a file (the resource), a line
+ * number, and a column number. This is part of "line precise exception reporting", whereby errors at runtime can be
+ * tracked backwards to the files from which they were parsed or otherwise constructed.
+ */
+public interface Location
+{
+    /**
+     * The resource from which the object tagged with a location was derived.
+     */
+    Resource getResource();
+
+    /**
+     * The line number within the resource, if known, or -1 otherwise.
+     */
+    int getLine();
+
+    /**
+     * The column number within the line if known, or -1 otherwise.
+     */
+    int getColumn();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/ioc/MessageFormatter.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/MessageFormatter.java b/commons/src/main/java/org/apache/tapestry5/ioc/MessageFormatter.java
new file mode 100644
index 0000000..e90eb65
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/MessageFormatter.java
@@ -0,0 +1,32 @@
+// Copyright 2006 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.ioc;
+
+/**
+ * Obtained from a {@link org.apache.tapestry5.ioc.Messages}, used to format messages for a specific localized message
+ * key.
+ */
+public interface MessageFormatter
+{
+    /**
+     * Formats the message. The arguments are passed to {@link java.util.Formatter} as is with one exception: Object of
+     * type {@link Throwable} are converted to their {@link Throwable#getMessage()} (or, if that is null, to the name of
+     * the class).
+     *
+     * @param args
+     * @return formatted string
+     */
+    String format(Object... args);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/ioc/Messages.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/Messages.java b/commons/src/main/java/org/apache/tapestry5/ioc/Messages.java
new file mode 100644
index 0000000..ba7452c
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/Messages.java
@@ -0,0 +1,61 @@
+// Copyright 2006, 2007, 2012 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.ioc;
+
+import java.util.Set;
+
+/**
+ * Provides access to a messages catalog, a set of properties files that provide localized messages for a particular
+ * locale. The message catalog consists of keys and values and follows the semantics of a Java {@link
+ * java.util.ResourceBundle} with some changes.
+ */
+public interface Messages
+{
+    /**
+     * Returns true if the bundle contains the named key.
+     */
+    boolean contains(String key);
+
+    /**
+     * Returns the localized message for the given key. If catalog does not contain such a key, then a modified version
+     * of the key is returned (converted to upper case and enclosed in brackets).
+     *
+     * @param key
+     * @return localized message for key, or placeholder
+     */
+    String get(String key);
+
+    /**
+     * Returns a formatter for the message, which can be used to substitute arguments (as per {@link
+     * java.util.Formatter}).
+     *
+     * @param key
+     * @return formattable object
+     */
+    MessageFormatter getFormatter(String key);
+
+    /**
+     * Convenience for accessing a formatter and formatting a localized message with arguments.
+     */
+    String format(String key, Object... args);
+
+    /**
+     * Returns a set of all the keys for which this instance may provide a value.
+     *
+     * @return set of keys
+     * @since 5.4
+     */
+    Set<String> getKeys();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/3d4de7e1/commons/src/main/java/org/apache/tapestry5/ioc/ObjectLocator.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/ObjectLocator.java b/commons/src/main/java/org/apache/tapestry5/ioc/ObjectLocator.java
new file mode 100644
index 0000000..81d1f77
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/ObjectLocator.java
@@ -0,0 +1,143 @@
+// Copyright 2006, 2007, 2010, 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.ioc;
+
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.services.MasterObjectProvider;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Defines an object which can provide access to services defined within a {@link org.apache.tapestry5.ioc.Registry}, or
+ * to objects or object instances available by other means. Services are accessed via service id, or
+ * (when appropriate)
+ * by just service interface. The Registry itself implements this interface, as does
+ * {@link org.apache.tapestry5.ioc.ServiceResources}.
+ */
+public interface ObjectLocator
+{
+    /**
+     * Obtains a service via its unique service id. Returns the service's proxy. The service proxy
+     * implements the same
+     * interface as the actual service, and is used to instantiate the actual service only as needed
+     * (this is
+     * transparent to the application).
+     *
+     * @param <T>
+     * @param serviceId        unique Service id used to locate the service object (may contain <em>symbols</em>,
+     *                         which
+     *                         will be expanded), case is ignored
+     * @param serviceInterface the interface implemented by the service (or an interface extended by the service
+     *                         interface)
+     * @return the service instance
+     * @throws RuntimeException if the service is not defined, or if an error occurs instantiating it
+     */
+    <T> T getService(String serviceId, Class<T> serviceInterface);
+
+    /**
+     * Locates a service given a service interface and (optionally) some marker annotation types. A single service must implement the service
+     * interface (which                                                   * can be hard to guarantee) and by marked by all the marker types. The search takes into account inheritance of the service interface
+     * (not the service <em>implementation</em>), which may result in a failure due to extra
+     * matches.
+     *
+     * @param serviceInterface the interface the service implements
+     * @return the service's proxy
+     * @throws RuntimeException if the service does not exist (this is considered programmer error), or multiple
+     *                          services directly implement, or extend from, the service interface
+     * @see org.apache.tapestry5.ioc.annotations.Marker
+     */
+    <T> T getService(Class<T> serviceInterface);
+
+    /**
+     * Locates a service given a service interface and (optionally) some marker annotation types. A single service must implement the service
+     * interface (which                                                   * can be hard to guarantee) and by marked by all the marker types. The search takes into account inheritance of the service interface
+     * (not the service <em>implementation</em>), which may result in a failure due to extra
+     * matches.        The ability to specify marker annotation types was added in 5.3
+     *
+     * @param serviceInterface the interface the service implements
+     * @param markerTypes      Markers used to select a specific service that implements the interface
+     * @return the service's proxy
+     * @throws RuntimeException if the service does not exist (this is considered programmer error), or multiple
+     *                          services directly implement, or extend from, the service interface
+     * @see org.apache.tapestry5.ioc.annotations.Marker
+     * @since 5.3
+     */
+    <T> T getService(Class<T> serviceInterface, Class<? extends Annotation>... markerTypes);
+
+    /**
+     * Obtains an object indirectly, using the {@link org.apache.tapestry5.ioc.services.MasterObjectProvider} service.
+     *
+     * @param objectType         the type of object to be returned
+     * @param annotationProvider provides access to annotations on the field or parameter for which a value is to
+     *                           be
+     *                           obtained, which may be utilized in selecting an appropriate object, use
+     *                           <strong>null</strong> when annotations are not available (in which case, selection
+     *                           will
+     *                           be based only on the object type)
+     * @param <T>
+     * @return the requested object
+     * @see ObjectProvider
+     */
+    <T> T getObject(Class<T> objectType, AnnotationProvider annotationProvider);
+
+    /**
+     * Autobuilds a class by finding the public constructor with the most parameters. Services and other resources or
+     * dependencies will be injected into the parameters of the constructor and into private fields marked with the
+     * {@link Inject} annotation. There are two cases: constructing a service implementation, and constructing
+     * an arbitrary object. In the former case, many <em>service resources</em> are also available for injection, not
+     * just dependencies or objects provided via
+     * {@link MasterObjectProvider#provide(Class, AnnotationProvider, ObjectLocator, boolean)}.
+     *
+     * @param <T>
+     * @param clazz the type of object to instantiate
+     * @return the instantiated instance
+     * @throws RuntimeException if the autobuild fails
+     * @see MasterObjectProvider
+     */
+    <T> T autobuild(Class<T> clazz);
+
+    /**
+     * Preferred version of {@link #autobuild(Class)} that tracks the operation using
+     * {@link OperationTracker#invoke(String, Invokable)}.
+     *
+     * @param <T>
+     * @param description description used with {@link OperationTracker}
+     * @param clazz       the type of object to instantiate
+     * @return the instantiated instance
+     * @throws RuntimeException if the autobuild fails
+     * @see MasterObjectProvider
+     * @since 5.2.0
+     */
+    <T> T autobuild(String description, Class<T> clazz);
+
+    /**
+     * Creates a proxy. The proxy will defer invocation of {@link #autobuild(Class)} until
+     * just-in-time (that is, first method invocation). In a limited number of cases, it is necessary to use such a
+     * proxy to prevent service construction cycles, particularly when contributing (directly or indirectly) to the
+     * {@link org.apache.tapestry5.ioc.services.MasterObjectProvider} (which is itself at the heart
+     * of autobuilding).
+     * <p/>
+     * If the class file for the class is a file on the file system (not a file packaged in a JAR), then the proxy will
+     * <em>autoreload</em>: changing the class file will result in the new class being reloaded and re-instantiated
+     * (with dependencies).
+     *
+     * @param <T>
+     * @param interfaceClass      the interface implemented by the proxy
+     * @param implementationClass a concrete class that implements the interface
+     * @return a proxy
+     * @see #autobuild(Class)
+     */
+    <T> T proxy(Class<T> interfaceClass, Class<? extends T> implementationClass);
+}


Mime
View raw message