You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by th...@apache.org on 2014/12/21 20:56:53 UTC
[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
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);
+}