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:31 UTC

[05/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/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/Resource.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/Resource.java b/commons/src/main/java/org/apache/tapestry5/ioc/Resource.java
new file mode 100644
index 0000000..b81c1c5
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/Resource.java
@@ -0,0 +1,108 @@
+// 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.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Locale;
+
+/**
+ * Represents a resource on the server that may be used for server side processing, or may be exposed to the client
+ * side. Generally, this represents an abstraction on top of files on the class path and files stored in the web
+ * application context.
+ * <p/>
+ * Resources are often used as map keys; they should be immutable and should implement hashCode() and equals().
+ */
+public interface Resource
+{
+
+    /**
+     * Returns true if the resource exists; if a stream to the content of the file may be opened. A resource exists
+     * if {@link #toURL()} returns a non-null value. Starting in release 5.3.4, the result of this is cached.
+     * <p/>
+     * Starting in 5.4, some "virtual resources", may return true even though {@link #toURL()} returns null.
+     *
+     * @return true if the resource exists, false if it does not
+     */
+    boolean exists();
+
+
+    /**
+     * Returns true if the resource is virtual, meaning this is no underlying file. Many operations are unsupported
+     * on virtual resources, including {@link #toURL()}, {@link #forLocale(java.util.Locale)},
+     * {@link #withExtension(String)}, {@link #getFile()}, {@link #getFolder()}, {@link #getPath()}}; these
+     * operations will throw an {@link java.lang.UnsupportedOperationException}.
+     *
+     * @since 5.4
+     */
+    boolean isVirtual();
+
+    /**
+     * Opens a stream to the content of the resource, or returns null if the resource does not exist. The native
+     * input stream supplied by the resource is wrapped in a {@link java.io.BufferedInputStream}.
+     *
+     * @return an open, buffered stream to the content, if available
+     */
+    InputStream openStream() throws IOException;
+
+    /**
+     * Returns the URL for the resource, or null if it does not exist. This value is lazily computed; starting in 5.3.4, subclasses may cache
+     * the result.  Starting in 5.4, some "virtual resources" may return null.
+     */
+    URL toURL();
+
+    /**
+     * Returns a localized version of the resource. May return null if no such resource exists. Starting in release
+     * 5.3.4, the result of this method is cached internally.
+     */
+    Resource forLocale(Locale locale);
+
+    /**
+     * Returns a Resource based on a relative path, relative to the folder containing the resource. Understands the "."
+     * (current folder) and ".." (parent folder) conventions, and treats multiple sequential slashes as a single slash.
+     * <p/>
+     * Virtual resources (resources fabricated at runtime) return themselves.
+     */
+    Resource forFile(String relativePath);
+
+    /**
+     * Returns a new Resource with the extension changed (or, if the resource does not have an extension, the extension
+     * is added). The new Resource may not exist (that is, {@link #toURL()} may return null.
+     *
+     * @param extension
+     *         to apply to the resource, such as "html" or "properties"
+     * @return the new resource
+     */
+    Resource withExtension(String extension);
+
+    /**
+     * Returns the portion of the path up to the last forward slash; this is the directory or folder portion of the
+     * Resource.
+     */
+    String getFolder();
+
+    /**
+     * Returns the file portion of the Resource path, everything that follows the final forward slash.
+     * <p/>
+     * Starting in 5.4, certain kinds of "virtual resources" may return null here.
+     */
+    String getFile();
+
+    /**
+     * Return the path (the combination of folder and file).
+     * <p/>
+     * Starting in 5.4, certain "virtual resources", may return an arbitrary value here.
+     */
+    String getPath();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/internal/NullAnnotationProvider.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/NullAnnotationProvider.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/NullAnnotationProvider.java
new file mode 100644
index 0000000..716b7c6
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/NullAnnotationProvider.java
@@ -0,0 +1,35 @@
+// Copyright 2007 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.internal;
+
+import org.apache.tapestry5.ioc.AnnotationProvider;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * A null implementation of {@link AnnotationProvider}, used when there is not appropriate source of annotations.
+ */
+public class NullAnnotationProvider implements AnnotationProvider
+{
+    /**
+     * Always returns null.
+     */
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+    {
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/CollectionFactory.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/CollectionFactory.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/CollectionFactory.java
new file mode 100644
index 0000000..6711b1a
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/CollectionFactory.java
@@ -0,0 +1,139 @@
+// 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.internal.util;
+
+import org.apache.tapestry5.ioc.util.CaseInsensitiveMap;
+import org.apache.tapestry5.ioc.util.Stack;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Static factory methods to ease the creation of new collection types (when using generics). Most of these method
+ * leverage the compiler's ability to match generic types by return value. Typical usage (with a static import):
+ * <p/>
+ * <pre>
+ * Map&lt;Foo, Bar&gt; map = newMap();
+ * </pre>
+ * <p/>
+ * <p/>
+ * This is a replacement for:
+ * <p/>
+ * <pre>
+ * Map&lt;Foo, Bar&gt; map = new HashMap&lt;Foo, Bar&gt;();
+ * </pre>
+ */
+public final class CollectionFactory
+{
+    /**
+     * Constructs and returns a generic {@link HashMap} instance.
+     */
+    public static <K, V> Map<K, V> newMap()
+    {
+        return new HashMap<K, V>();
+    }
+
+    /**
+     * Constructs and returns a generic {@link java.util.HashSet} instance.
+     */
+    public static <T> Set<T> newSet()
+    {
+        return new HashSet<T>();
+    }
+
+    /**
+     * Contructs a new {@link HashSet} and initializes it using the provided collection.
+     */
+    public static <T, V extends T> Set<T> newSet(Collection<V> values)
+    {
+        return new HashSet<T>(values);
+    }
+
+    public static <T, V extends T> Set<T> newSet(V... values)
+    {
+        // Was a call to newSet(), but Sun JDK can't handle that. Fucking generics.
+        return new HashSet<T>(Arrays.asList(values));
+    }
+
+    /**
+     * Constructs a new {@link java.util.HashMap} instance by copying an existing Map instance.
+     */
+    public static <K, V> Map<K, V> newMap(Map<? extends K, ? extends V> map)
+    {
+        return new HashMap<K, V>(map);
+    }
+
+    /**
+     * Constructs a new concurrent map, which is safe to access via multiple threads.
+     */
+    public static <K, V> ConcurrentMap<K, V> newConcurrentMap()
+    {
+        return new ConcurrentHashMap<K, V>();
+    }
+
+    /**
+     * Contructs and returns a new generic {@link java.util.ArrayList} instance.
+     */
+    public static <T> List<T> newList()
+    {
+        return new ArrayList<T>();
+    }
+
+    /**
+     * Creates a new, fully modifiable list from an initial set of elements.
+     */
+    public static <T, V extends T> List<T> newList(V... elements)
+    {
+        // Was call to newList(), but Sun JDK can't handle that.
+        return new ArrayList<T>(Arrays.asList(elements));
+    }
+
+    /**
+     * Useful for queues.
+     */
+    public static <T> LinkedList<T> newLinkedList()
+    {
+        return new LinkedList<T>();
+    }
+
+    /**
+     * Constructs and returns a new {@link java.util.ArrayList} as a copy of the provided collection.
+     */
+    public static <T, V extends T> List<T> newList(Collection<V> list)
+    {
+        return new ArrayList<T>(list);
+    }
+
+    /**
+     * Constructs and returns a new {@link java.util.concurrent.CopyOnWriteArrayList}.
+     */
+    public static <T> List<T> newThreadSafeList()
+    {
+        return new CopyOnWriteArrayList<T>();
+    }
+
+    public static <T> Stack<T> newStack()
+    {
+        return new Stack<T>();
+    }
+
+    public static <V> Map<String, V> newCaseInsensitiveMap()
+    {
+        return new CaseInsensitiveMap<V>();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
new file mode 100644
index 0000000..1a6dd80
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java
@@ -0,0 +1,615 @@
+// Copyright 2008, 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc.internal.util;
+
+import java.lang.reflect.*;
+import java.util.LinkedList;
+
+/**
+ * Static methods related to the use of JDK 1.5 generics.
+ */
+@SuppressWarnings("unchecked")
+public class GenericsUtils
+{
+    /**
+     * Analyzes the method in the context of containingClass and returns the Class that is represented by
+     * the method's generic return type. Any parameter information in the generic return type is lost. If you want
+     * to preserve the type parameters of the return type consider using
+     * {@link #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)}.
+     *
+     * @param containingClass class which either contains or inherited the method
+     * @param method          method from which to extract the return type
+     * @return the class represented by the methods generic return type, resolved based on the context .
+     * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Method)
+     * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+     * @see #asClass(java.lang.reflect.Type)
+     */
+    public static Class<?> extractGenericReturnType(Class<?> containingClass, Method method)
+    {
+        return asClass(resolve(method.getGenericReturnType(), containingClass));
+    }
+
+
+    /**
+     * Analyzes the field in the context of containingClass and returns the Class that is represented by
+     * the field's generic type. Any parameter information in the generic type is lost, if you want
+     * to preserve the type parameters of the return type consider using
+     * {@link #getTypeVariableIndex(java.lang.reflect.TypeVariable)}.
+     *
+     * @param containingClass class which either contains or inherited the field
+     * @param field           field from which to extract the type
+     * @return the class represented by the field's generic type, resolved based on the containingClass.
+     * @see #extractActualType(java.lang.reflect.Type, java.lang.reflect.Field)
+     * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+     * @see #asClass(java.lang.reflect.Type)
+     */
+    public static Class extractGenericFieldType(Class containingClass, Field field)
+    {
+        return asClass(resolve(field.getGenericType(), containingClass));
+    }
+
+    /**
+     * Analyzes the method in the context of containingClass and returns the Class that is represented by
+     * the method's generic return type. Any parameter information in the generic return type is lost.
+     *
+     * @param containingType Type which is/represents the class that either contains or inherited the method
+     * @param method         method from which to extract the generic return type
+     * @return the generic type represented by the methods generic return type, resolved based on the containingType.
+     * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+     */
+    public static Type extractActualType(Type containingType, Method method)
+    {
+        return resolve(method.getGenericReturnType(), containingType);
+    }
+
+    /**
+     * Analyzes the method in the context of containingClass and returns the Class that is represented by
+     * the method's generic return type. Any parameter information in the generic return type is lost.
+     *
+     * @param containingType Type which is/represents the class that either contains or inherited the field
+     * @param field          field from which to extract the generic return type
+     * @return the generic type represented by the methods generic return type, resolved based on the containingType.
+     * @see #resolve(java.lang.reflect.Type,java.lang.reflect.Type)
+     */
+    public static Type extractActualType(Type containingType, Field field)
+    {
+        return resolve(field.getGenericType(), containingType);
+    }
+
+    /**
+     * Resolves the type parameter based on the context of the containingType.
+     * <p/>
+     * {@link java.lang.reflect.TypeVariable} will be unwrapped to the type argument resolved form the class
+     * hierarchy. This may be something other than a simple Class if the type argument is a ParameterizedType for
+     * instance (e.g. List&lt;E>; List&lt;Map&lt;Long, String>>, E would be returned as a ParameterizedType with the raw
+     * type Map and type arguments Long and String.
+     * <p/>
+     *
+     * @param type
+     *          the generic type (ParameterizedType, GenericArrayType, WildcardType, TypeVariable) to be resolved
+     * @param containingType
+     *          the type which his
+     * @return
+     *          the type resolved to the best of our ability.
+     * @since 5.2.?
+     */
+    public static Type resolve(final Type type, final Type containingType)
+    {
+        // The type isn't generic. (String, Long, etc)
+        if (type instanceof Class)
+            return type;
+
+        // List<T>, List<String>, List<T extends Number>
+        if (type instanceof ParameterizedType)
+            return resolve((ParameterizedType) type, containingType);
+
+        // T[], List<String>[], List<T>[]
+        if (type instanceof GenericArrayType)
+            return resolve((GenericArrayType) type, containingType);
+
+        // List<? extends T>, List<? extends Object & Comparable & Serializable>
+        if (type instanceof WildcardType)
+            return resolve((WildcardType) type, containingType);
+
+        // T
+        if (type instanceof TypeVariable)
+            return resolve((TypeVariable) type, containingType);
+
+        // I'm leaning towards an exception here.
+        return type;
+    }
+
+
+    /**
+     * Determines if the suspected super type is assignable from the suspected sub type.
+     *
+     * @param suspectedSuperType
+     *          e.g. GenericDAO&lt;Pet, String>
+     * @param suspectedSubType
+     *          e.g. PetDAO extends GenericDAO&lt;Pet,String>
+     * @return
+     *          true if (sourceType)targetClass is a valid cast
+     */
+    public static boolean isAssignableFrom(Type suspectedSuperType, Type suspectedSubType)
+    {
+        final Class suspectedSuperClass = asClass(suspectedSuperType);
+        final Class suspectedSubClass = asClass(suspectedSubType);
+
+        // The raw types need to be compatible.
+        if (!suspectedSuperClass.isAssignableFrom(suspectedSubClass))
+        {
+            return false;
+        }
+
+        // From this point we know that the raw types are assignable.
+        // We need to figure out what the generic parameters in the targetClass are
+        // as they pertain to the sourceType.
+
+        if (suspectedSuperType instanceof WildcardType)
+        {
+            // ? extends Number
+            // needs to match all the bounds (there will only be upper bounds or lower bounds
+            for (Type t : ((WildcardType) suspectedSuperType).getUpperBounds())
+            {
+                if (!isAssignableFrom(t, suspectedSubType)) return false;
+            }
+            for (Type t : ((WildcardType) suspectedSuperType).getLowerBounds())
+            {
+                if (!isAssignableFrom(suspectedSubType, t)) return false;
+            }
+            return true;
+        }
+
+        Type curType = suspectedSubType;
+        Class curClass;
+
+        while (curType != null && !curType.equals(Object.class))
+        {
+            curClass = asClass(curType);
+
+            if (curClass.equals(suspectedSuperClass))
+            {
+                final Type resolved = resolve(curType, suspectedSubType);
+
+                if (suspectedSuperType instanceof Class)
+                {
+                    if ( resolved instanceof Class )
+                        return suspectedSuperType.equals(resolved);
+
+                    // They may represent the same class, but the suspectedSuperType is not parameterized. The parameter
+                    // types default to Object so they must be a match.
+                    // e.g. Pair p = new StringLongPair();
+                    //      Pair p = new Pair<? extends Number, String>
+
+                    return true;
+                }
+
+                if (suspectedSuperType instanceof ParameterizedType)
+                {
+                    if (resolved instanceof ParameterizedType)
+                    {
+                        final Type[] type1Arguments = ((ParameterizedType) suspectedSuperType).getActualTypeArguments();
+                        final Type[] type2Arguments = ((ParameterizedType) resolved).getActualTypeArguments();
+                        if (type1Arguments.length != type2Arguments.length) return false;
+
+                        for (int i = 0; i < type1Arguments.length; ++i)
+                        {
+                            if (!isAssignableFrom(type1Arguments[i], type2Arguments[i])) return false;
+                        }
+                        return true;
+                    }
+                }
+                else if (suspectedSuperType instanceof GenericArrayType)
+                {
+                    if (resolved instanceof GenericArrayType)
+                    {
+                        return isAssignableFrom(
+                                ((GenericArrayType) suspectedSuperType).getGenericComponentType(),
+                                ((GenericArrayType) resolved).getGenericComponentType()
+                        );
+                    }
+                }
+
+                return false;
+            }
+
+            final Type[] types = curClass.getGenericInterfaces();
+            for (Type t : types)
+            {
+                final Type resolved = resolve(t, suspectedSubType);
+                if (isAssignableFrom(suspectedSuperType, resolved))
+                    return true;
+            }
+
+            curType = curClass.getGenericSuperclass();
+        }
+        return false;
+    }
+
+    /**
+     * Get the class represented by the reflected type.
+     * This method is lossy; You cannot recover the type information from the class that is returned.
+     * <p/>
+     * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information
+     * is lost.
+     * <p/>
+     * {@code WildcardType} the first lower bound is returned. If the wildcard is defined with upper bounds
+     * then {@code Object} is returned.
+     *
+     * @param actualType
+     *           a Class, ParameterizedType, GenericArrayType
+     * @return the un-parameterized class associated with the type.
+     */
+    public static Class asClass(Type actualType)
+    {
+        if (actualType instanceof Class) return (Class) actualType;
+
+        if (actualType instanceof ParameterizedType)
+        {
+            final Type rawType = ((ParameterizedType) actualType).getRawType();
+            // The sun implementation returns getRawType as Class<?>, but there is room in the interface for it to be
+            // some other Type. We'll assume it's a Class.
+            // TODO: consider logging or throwing our own exception for that day when "something else" causes some confusion
+            return (Class) rawType;
+        }
+
+        if (actualType instanceof GenericArrayType)
+        {
+            final Type type = ((GenericArrayType) actualType).getGenericComponentType();
+            return Array.newInstance(asClass(type), 0).getClass();
+        }
+
+        if (actualType instanceof TypeVariable)
+        {
+            // Support for List<T extends Number>
+            // There is always at least one bound. If no bound is specified in the source then it will be Object.class
+            return asClass(((TypeVariable) actualType).getBounds()[0]);
+        }
+
+        if (actualType instanceof WildcardType)
+        {
+            final WildcardType wildcardType = (WildcardType) actualType;
+            final Type[] bounds = wildcardType.getLowerBounds();
+            if (bounds != null && bounds.length > 0)
+            {
+                return asClass(bounds[0]);
+            }
+            // If there is no lower bounds then the only thing that makes sense is Object.
+            return Object.class;
+        }
+
+        throw new RuntimeException(String.format("Unable to convert %s to Class.", actualType));
+    }
+
+    /**
+     * Convert the type into a string. The string representation approximates the code that would be used to define the
+     * type.
+     *
+     * @param type - the type.
+     * @return a string representation of the type, similar to how it was declared.
+     */
+    public static String toString(Type type)
+    {
+        if ( type instanceof ParameterizedType ) return toString((ParameterizedType)type);
+        if ( type instanceof WildcardType ) return toString((WildcardType)type);
+        if ( type instanceof GenericArrayType) return toString((GenericArrayType)type);
+        if ( type instanceof Class )
+        {
+            final Class theClass = (Class) type;
+            return (theClass.isArray() ? theClass.getName() + "[]" : theClass.getName());
+        }
+        return type.toString();
+    }
+
+    /**
+     * Method to resolve a TypeVariable to its most
+     * <a href="http://java.sun.com/docs/books/jls/third_edition/html/typesValues.html#112582">reifiable</a> form.
+     * <p/>
+     * <p/>
+     * How to resolve a TypeVariable:<br/>
+     * All of the TypeVariables defined by a generic class will be given a Type by any class that extends it. The Type
+     * given may or may not be reifiable; it may be another TypeVariable for instance.
+     * <p/>
+     * Consider <br/>
+     * <i>class Pair&gt;A,B> { A getA(){...}; ...}</i><br/>
+     * <i>class StringLongPair extends Pair&gt;String, Long> { }</i><br/>
+     * <p/>
+     * To resolve the actual return type of Pair.getA() you must first resolve the TypeVariable "A".
+     * We can do that by first finding the index of "A" in the Pair.class.getTypeParameters() array of TypeVariables.
+     * <p/>
+     * To get to the Type provided by StringLongPair you access the generics information by calling
+     * StringLongPair.class.getGenericSuperclass; this will be a ParameterizedType. ParameterizedType gives you access
+     * to the actual type arguments provided to Pair by StringLongPair. The array is in the same order as the array in
+     * Pair.class.getTypeParameters so you can use the index we discovered earlier to extract the Type; String.class.
+     * <p/>
+     * When extracting Types we only have to consider the superclass hierarchy and not the interfaces implemented by
+     * the class. When a class implements a generic interface it must provide types for the interface and any generic
+     * methods implemented from the interface will be re-defined by the class with its generic type variables.
+     *
+     * @param typeVariable   - the type variable to resolve.
+     * @param containingType - the shallowest class in the class hierarchy (furthest from Object) where typeVariable is defined.
+     * @return a Type that has had all possible TypeVariables resolved that have been defined between the type variable
+     *         declaration and the containingType.
+     */
+    private static Type resolve(TypeVariable typeVariable, Type containingType)
+    {
+        // The generic declaration is either a Class, Method or Constructor
+        final GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
+
+        if (!(genericDeclaration instanceof Class))
+        {
+            // It's a method or constructor. The best we can do here is try to resolve the bounds
+            // e.g. <T extends E> T getT(T param){} where E is defined by the class.
+            final Type bounds0 = typeVariable.getBounds()[0];
+            return resolve(bounds0, containingType);
+        }
+
+        final Class typeVariableOwner = (Class) genericDeclaration;
+
+        // find the typeOwner in the containingType's hierarchy
+        final LinkedList<Type> stack = new LinkedList<Type>();
+
+        // If you pass a List<Long> as the containingType then the TypeVariable is going to be resolved by the
+        // containingType and not the super class.
+        if (containingType instanceof ParameterizedType)
+        {
+            stack.add(containingType);
+        }
+
+        Class theClass = asClass(containingType);
+        Type genericSuperclass = theClass.getGenericSuperclass();
+        while (genericSuperclass != null && // true for interfaces with no superclass
+                !theClass.equals(Object.class) &&
+                !theClass.equals(typeVariableOwner))
+        {
+            stack.addFirst(genericSuperclass);
+            theClass = asClass(genericSuperclass);
+            genericSuperclass = theClass.getGenericSuperclass();
+        }
+
+        int i = getTypeVariableIndex(typeVariable);
+        Type resolved = typeVariable;
+        for (Type t : stack)
+        {
+            if (t instanceof ParameterizedType)
+            {
+                resolved = ((ParameterizedType) t).getActualTypeArguments()[i];
+                if (resolved instanceof Class) return resolved;
+                if (resolved instanceof TypeVariable)
+                {
+                    // Need to look at the next class in the hierarchy
+                    i = getTypeVariableIndex((TypeVariable) resolved);
+                    continue;
+                }
+                return resolve(resolved, containingType);
+            }
+        }
+
+        // the only way we get here is if resolved is still a TypeVariable, otherwise an
+        // exception is thrown or a value is returned.
+        return ((TypeVariable) resolved).getBounds()[0];
+    }
+
+    /**
+     * @param type           - something like List&lt;T>[] or List&lt;? extends T>[] or T[]
+     * @param containingType - the shallowest type in the hierarchy where type is defined.
+     * @return either the passed type if no changes required or a copy with a best effort resolve of the component type.
+     */
+    private static GenericArrayType resolve(GenericArrayType type, Type containingType)
+    {
+        final Type componentType = type.getGenericComponentType();
+
+        if (!(componentType instanceof Class))
+        {
+            final Type resolved = resolve(componentType, containingType);
+            return create(resolved);
+        }
+
+        return type;
+    }
+
+    /**
+     * @param type           - something like List&lt;T>, List&lt;T extends Number>
+     * @param containingType - the shallowest type in the hierarchy where type is defined.
+     * @return the passed type if nothing to resolve or a copy of the type with the type arguments resolved.
+     */
+    private static ParameterizedType resolve(ParameterizedType type, Type containingType)
+    {
+        // Use a copy because we're going to modify it.
+        final Type[] types = type.getActualTypeArguments().clone();
+
+        boolean modified = resolve(types, containingType);
+        return modified ? create(type.getRawType(), type.getOwnerType(), types) : type;
+    }
+
+    /**
+     * @param type           - something like List&lt;? super T>, List<&lt;? extends T>, List&lt;? extends T & Comparable&lt? super T>>
+     * @param containingType - the shallowest type in the hierarchy where type is defined.
+     * @return the passed type if nothing to resolve or a copy of the type with the upper and lower bounds resolved.
+     */
+    private static WildcardType resolve(WildcardType type, Type containingType)
+    {
+        // Use a copy because we're going to modify them.
+        final Type[] upper = type.getUpperBounds().clone();
+        final Type[] lower = type.getLowerBounds().clone();
+
+        boolean modified = resolve(upper, containingType);
+        modified = modified || resolve(lower, containingType);
+
+        return modified ? create(upper, lower) : type;
+    }
+
+    /**
+     * @param types          - Array of types to resolve. The unresolved type is replaced in the array with the resolved type.
+     * @param containingType - the shallowest type in the hierarchy where type is defined.
+     * @return true if any of the types were resolved.
+     */
+    private static boolean resolve(Type[] types, Type containingType)
+    {
+        boolean modified = false;
+        for (int i = 0; i < types.length; ++i)
+        {
+            Type t = types[i];
+            if (!(t instanceof Class))
+            {
+                modified = true;
+                final Type resolved = resolve(t, containingType);
+                if (!resolved.equals(t))
+                {
+                    types[i] = resolved;
+                    modified = true;
+                }
+            }
+        }
+        return modified;
+    }
+
+    /**
+     * @param rawType       - the un-parameterized type.
+     * @param ownerType     - the outer class or null if the class is not defined within another class.
+     * @param typeArguments - type arguments.
+     * @return a copy of the type with the typeArguments replaced.
+     */
+    static ParameterizedType create(final Type rawType, final Type ownerType, final Type[] typeArguments)
+    {
+        return new ParameterizedType()
+        {
+            @Override
+            public Type[] getActualTypeArguments()
+            {
+                return typeArguments;
+            }
+
+            @Override
+            public Type getRawType()
+            {
+                return rawType;
+            }
+
+            @Override
+            public Type getOwnerType()
+            {
+                return ownerType;
+            }
+
+            @Override
+            public String toString()
+            {
+                return GenericsUtils.toString(this);
+            }
+        };
+    }
+
+    static GenericArrayType create(final Type componentType)
+    {
+        return new GenericArrayType()
+        {
+            @Override
+            public Type getGenericComponentType()
+            {
+                return componentType;
+            }
+
+            @Override
+            public String toString()
+            {
+                return GenericsUtils.toString(this);
+            }
+        };
+    }
+
+    /**
+     * @param upperBounds - e.g. ? extends Number
+     * @param lowerBounds - e.g. ? super Long
+     * @return An new copy of the type with the upper and lower bounds replaced.
+     */
+    static WildcardType create(final Type[] upperBounds, final Type[] lowerBounds)
+    {
+
+        return new WildcardType()
+        {
+            @Override
+            public Type[] getUpperBounds()
+            {
+                return upperBounds;
+            }
+
+            @Override
+            public Type[] getLowerBounds()
+            {
+                return lowerBounds;
+            }
+
+            @Override
+            public String toString()
+            {
+                return GenericsUtils.toString(this);
+            }
+        };
+    }
+
+    static String toString(ParameterizedType pt)
+    {
+        String s = toString(pt.getActualTypeArguments());
+        return String.format("%s<%s>", toString(pt.getRawType()), s);
+    }
+
+    static String toString(GenericArrayType gat)
+    {
+        return String.format("%s[]", toString(gat.getGenericComponentType()));
+    }
+
+    static String toString(WildcardType wt)
+    {
+        final boolean isSuper = wt.getLowerBounds().length > 0;
+        return String.format("? %s %s",
+                isSuper ? "super" : "extends",
+                isSuper ? toString(wt.getLowerBounds()) : toString(wt.getLowerBounds()));
+    }
+
+    static String toString(Type[] types)
+    {
+        StringBuilder sb = new StringBuilder();
+        for ( Type t : types )
+        {
+            sb.append(toString(t)).append(", ");
+        }
+        return sb.substring(0, sb.length() - 2);// drop last ,
+    }
+
+    /**
+     * Find the index of the TypeVariable in the classes parameters. The offset can be used on a subclass to find
+     * the actual type.
+     *
+     * @param typeVariable - the type variable in question.
+     * @return the index of the type variable in its declaring class/method/constructor's type parameters.
+     */
+    private static int getTypeVariableIndex(final TypeVariable typeVariable)
+    {
+        // the label from the class (the T in List<T>, the K or V in Map<K,V>, etc)
+        final String typeVarName = typeVariable.getName();
+        final TypeVariable[] typeParameters = typeVariable.getGenericDeclaration().getTypeParameters();
+        for (int typeArgumentIndex = 0; typeArgumentIndex < typeParameters.length; typeArgumentIndex++)
+        {
+            // The .equals for TypeVariable may not be compatible, a name check should be sufficient.
+            if (typeParameters[typeArgumentIndex].getName().equals(typeVarName))
+                return typeArgumentIndex;
+        }
+
+        // The only way this could happen is if the TypeVariable is hand built incorrectly, or it's corrupted.
+        throw new RuntimeException(
+                String.format("%s does not have a TypeVariable matching %s", typeVariable.getGenericDeclaration(), typeVariable));
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
new file mode 100644
index 0000000..ef217cb
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
@@ -0,0 +1,75 @@
+// 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.internal.util;
+
+import org.apache.tapestry5.ioc.Locatable;
+import org.apache.tapestry5.ioc.Location;
+
+/**
+ * Exception class used as a replacement for {@link java.lang.RuntimeException} when the exception is related to a
+ * particular location.
+ */
+public class TapestryException extends RuntimeException implements Locatable
+{
+    private static final long serialVersionUID = 6396903640977182682L;
+
+    private transient final Location location;
+
+    /**
+     * @param message  a message (may be null)
+     * @param location implements {@link Location} or {@link Locatable}
+     * @param cause    if not null, the root cause of the exception
+     */
+    public TapestryException(String message, Object location, Throwable cause)
+    {
+        this(message, InternalUtils.locationOf(location), cause);
+    }
+
+    /**
+     * @param message a message (may be null)
+     * @param cause   if not null, the root cause of the exception, also used to set the location
+     */
+    public TapestryException(String message, Throwable cause)
+    {
+        this(message, cause, cause);
+    }
+
+    /**
+     * @param message  a message (may be null)
+     * @param location location to associated with the exception, or null if not known
+     * @param cause    if not null, the root cause of the exception
+     */
+    public TapestryException(String message, Location location, Throwable cause)
+    {
+        super(message, cause);
+
+        this.location = location;
+    }
+
+    @Override
+    public Location getLocation()
+    {
+        return location;
+    }
+
+    @Override
+    public String toString()
+    {
+        if (location == null) return super.toString();
+
+        return String.format("%s [at %s]", super.toString(), location);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/services/ClassPropertyAdapter.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/services/ClassPropertyAdapter.java b/commons/src/main/java/org/apache/tapestry5/ioc/services/ClassPropertyAdapter.java
new file mode 100644
index 0000000..6159ed3
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/services/ClassPropertyAdapter.java
@@ -0,0 +1,79 @@
+// Copyright 2006, 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.services;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+/**
+ * Organizes all {@link org.apache.tapestry5.ioc.services.PropertyAdapter}s for a particular class.
+ * <p/>
+ * Only provides access to <em>simple</em> properties. Indexed properties are ignored.
+ * <p/>
+ * When accessing properties by name, the case of the name is ignored.
+ */
+public interface ClassPropertyAdapter
+{
+    /**
+     * Returns the names of all properties, sorted into alphabetic order. This includes true properties
+     * (as defined in the JavaBeans specification), but also public fields. Starting in Tapestry 5.3, even public static fields are included.
+     */
+    List<String> getPropertyNames();
+
+    /**
+     * Returns the type of bean this adapter provides properties for.
+     */
+    Class getBeanType();
+
+    /**
+     * Returns the property adapter with the given name, or null if no such adapter exists.
+     *
+     * @param name of the property (case is ignored)
+     */
+    PropertyAdapter getPropertyAdapter(String name);
+
+    /**
+     * Reads the value of a property.
+     *
+     * @param instance     the object to read a value from
+     * @param propertyName the name of the property to read (case is ignored)
+     * @throws UnsupportedOperationException if the property is write only
+     * @throws IllegalArgumentException      if property does not exist
+     */
+    Object get(Object instance, String propertyName);
+
+    /**
+     * Updates the value of a property.
+     *
+     * @param instance     the object to update
+     * @param propertyName the name of the property to update (case is ignored)
+     * @throws UnsupportedOperationException if the property is read only
+     * @throws IllegalArgumentException      if property does not exist
+     */
+    void set(Object instance, String propertyName, Object value);
+
+    /**
+     * Returns the annotation of a given property for the specified type if such an annotation is present, else null.
+     *
+     * @param instance     the object to read a value from
+     * @param propertyName the name of the property to read (case is ignored)
+     * @param annotationClass the type of annotation to return
+     *
+     * @throws IllegalArgumentException      if property does not exist
+     *
+     * @since 5.4
+     */
+    Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/services/Coercion.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/services/Coercion.java b/commons/src/main/java/org/apache/tapestry5/ioc/services/Coercion.java
new file mode 100644
index 0000000..b7a4cc8
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/services/Coercion.java
@@ -0,0 +1,31 @@
+// 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.services;
+
+/**
+ * Responsible for converting from one type to another. This is used primarily around component parameters.
+ *
+ * @param <S> the source type (input)
+ * @param <T> the target type (output)
+ */
+public interface Coercion<S, T>
+{
+    /**
+     * Converts an input value.
+     *
+     * @param input the input value
+     */
+    T coerce(S input);
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/services/CoercionTuple.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/services/CoercionTuple.java b/commons/src/main/java/org/apache/tapestry5/ioc/services/CoercionTuple.java
new file mode 100644
index 0000000..746de1e
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/services/CoercionTuple.java
@@ -0,0 +1,145 @@
+// Copyright 2006, 2007, 2008, 2010, 2011, 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.services;
+
+import org.apache.tapestry5.plastic.PlasticUtils;
+
+/**
+ * An immutable object that represents a mapping from one type to another. This is also the contribution type when
+ * building the {@link org.apache.tapestry5.ioc.services.TypeCoercer} service. Wraps a
+ * {@link org.apache.tapestry5.ioc.services.Coercion} object that performs the work with additional properties that
+ * describe
+ * the input and output types of the coercion, needed when searching for an appropriate coercion (or sequence of
+ * coercions).
+ *
+ * @param <S>
+ *         source (input) type
+ * @param <T>
+ *         target (output) type
+ */
+public final class CoercionTuple<S, T>
+{
+    private final Class<S> sourceType;
+
+    private final Class<T> targetType;
+
+    private final Coercion<S, T> coercion;
+
+    /**
+     * Wraps an arbitrary coercion with an implementation of toString() that identifies the source and target types.
+     */
+    private class CoercionWrapper<WS, WT> implements Coercion<WS, WT>
+    {
+        private final Coercion<WS, WT> coercion;
+
+        public CoercionWrapper(Coercion<WS, WT> coercion)
+        {
+            this.coercion = coercion;
+        }
+
+        @Override
+        public WT coerce(WS input)
+        {
+            return coercion.coerce(input);
+        }
+
+        @Override
+        public String toString()
+        {
+            return String.format("%s --> %s", convert(sourceType), convert(targetType));
+        }
+    }
+
+    private String convert(Class type)
+    {
+        if (Void.class.equals(type))
+            return "null";
+
+        String name = PlasticUtils.toTypeName(type);
+
+        int dotx = name.lastIndexOf('.');
+
+        // Strip off a package name of "java.lang"
+
+        if (dotx > 0 && name.substring(0, dotx).equals("java.lang"))
+            return name.substring(dotx + 1);
+
+        return name;
+    }
+
+    /**
+     * Standard constructor, which defaults wrap to true.
+     */
+    public CoercionTuple(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion)
+    {
+        this(sourceType, targetType, coercion, true);
+    }
+
+    /**
+     * Convenience constructor to help with generics.
+     *
+     * @since 5.2.0
+     */
+    public static <S, T> CoercionTuple<S, T> create(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion)
+    {
+        return new CoercionTuple<S, T>(sourceType, targetType, coercion);
+    }
+
+    /**
+     * Internal-use constructor.
+     *
+     * @param sourceType
+     *         the source (or input) type of the coercion, may be Void.class to indicate a coercion from null
+     * @param targetType
+     *         the target (or output) type of the coercion
+     * @param coercion
+     *         the object that performs the coercion
+     * @param wrap
+     *         if true, the coercion is wrapped to provide a useful toString()
+     */
+    @SuppressWarnings("unchecked")
+    public CoercionTuple(Class<S> sourceType, Class<T> targetType, Coercion<S, T> coercion, boolean wrap)
+    {
+        assert sourceType != null;
+        assert targetType != null;
+        assert coercion != null;
+
+        this.sourceType = PlasticUtils.toWrapperType(sourceType);
+        this.targetType = PlasticUtils.toWrapperType(targetType);
+        this.coercion = wrap ? new CoercionWrapper<S, T>(coercion) : coercion;
+    }
+
+    @Override
+    public String toString()
+    {
+        return coercion.toString();
+    }
+
+    public Coercion<S, T> getCoercion()
+    {
+        return coercion;
+    }
+
+    public Class<S> getSourceType()
+    {
+        return sourceType;
+    }
+
+    public Class<T> getTargetType()
+    {
+        return targetType;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java b/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java
new file mode 100644
index 0000000..ae542c5
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAccess.java
@@ -0,0 +1,77 @@
+// Copyright 2006, 2010, 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.ioc.services;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * A wrapper around the JavaBean Introspector that allows more manageable access to JavaBean properties of objects.
+ * <p/>
+ * Only provides access to <em>simple</em> properties. Indexed properties are ignored.
+ * <p>
+ * Starting in Tapestry 5.2, public fields can now be accessed as if they were properly JavaBean properties. Where there
+ * is a name conflict, the true property will be favored over the field access.
+ */
+public interface PropertyAccess
+{
+    /**
+     * Reads the value of a property.
+     *
+     * @throws UnsupportedOperationException
+     *             if the property is write only
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     */
+    Object get(Object instance, String propertyName);
+
+    /**
+     * Updates the value of a property.
+     *
+     * @throws UnsupportedOperationException
+     *             if the property is read only
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     */
+    void set(Object instance, String propertyName, Object value);
+
+    /**
+     * Returns the annotation of a given property for the specified type if such an annotation is present, else null.
+     * A convenience over invoking {@link #getAdapter(Object)}.{@link ClassPropertyAdapter#getPropertyAdapter(String)}.{@link PropertyAdapter#getAnnotation(Class)}
+     *
+     * @param instance     the object to read a value from
+     * @param propertyName the name of the property to read (case is ignored)
+     * @param annotationClass the type of annotation to return
+     * @throws IllegalArgumentException
+     *             if property does not exist
+     *
+     * @since 5.4
+     */
+    Annotation getAnnotation(Object instance, String propertyName, Class<? extends Annotation> annotationClass);
+
+    /**
+     * Returns the adapter for a particular object instance. A convienience over invoking {@link #getAdapter(Class)}.
+     */
+    ClassPropertyAdapter getAdapter(Object instance);
+
+    /**
+     * Returns the adapter used to access properties within the indicated class.
+     */
+    ClassPropertyAdapter getAdapter(Class forClass);
+
+    /**
+     * Discards all stored property access information, discarding all created class adapters.
+     */
+    void clearCache();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java b/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
new file mode 100644
index 0000000..947535e
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/services/PropertyAdapter.java
@@ -0,0 +1,121 @@
+// Copyright 2006, 2008, 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.services;
+
+import org.apache.tapestry5.ioc.AnnotationProvider;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Provides access to a single property within a class. Acts as an {@link org.apache.tapestry5.ioc.AnnotationProvider};
+ * when searching for annotations, the read method (if present) is checked first, followed by the write method, followed
+ * by the underlying field (when the property name matches the field name).
+ * <p/>
+ * Starting in release 5.2, this property may actually be a public field. In 5.3, it may be a public static field.
+ *
+ * @see org.apache.tapestry5.ioc.services.ClassPropertyAdapter
+ */
+@SuppressWarnings("unchecked")
+public interface PropertyAdapter extends AnnotationProvider
+{
+    /**
+     * Returns the name of the property (or public field).
+     */
+    String getName();
+
+    /**
+     * Returns true if the property is readable (i.e., has a getter method or is a public field).
+     */
+    boolean isRead();
+
+    /**
+     * Returns the method used to read the property, or null if the property is not readable (or is a public field).
+     */
+    public Method getReadMethod();
+
+    /**
+     * Returns true if the property is writeable (i.e., has a setter method or is a non-final field).
+     */
+    boolean isUpdate();
+
+    /**
+     * Returns the method used to update the property, or null if the property is not writeable (or a public field).
+     */
+    public Method getWriteMethod();
+
+    /**
+     * Reads the property value.
+     *
+     * @param instance to read from
+     * @throws UnsupportedOperationException if the property is write only
+     */
+    Object get(Object instance);
+
+    /**
+     * Updates the property value. The provided value must not be null if the property type is primitive, and must
+     * otherwise be of the proper type.
+     *
+     * @param instance to update
+     * @param value    new value for the property
+     * @throws UnsupportedOperationException if the property is read only
+     */
+    void set(Object instance, Object value);
+
+    /**
+     * Returns the type of the property.
+     */
+    Class getType();
+
+    /**
+     * Returns true if the return type of the read method is not the same as the property type. This can occur when the
+     * property has been defined using generics, in which case, the method's type may be Object when the property type
+     * is something more specific. This method is primarily used when generating runtime code related to the property.
+     */
+    boolean isCastRequired();
+
+    /**
+     * Returns the {@link org.apache.tapestry5.ioc.services.ClassPropertyAdapter} that provides access to other
+     * properties defined by the same class.
+     */
+    ClassPropertyAdapter getClassAdapter();
+
+    /**
+     * Returns the type of bean to which this property belongs. This is the same as
+     * {@link org.apache.tapestry5.ioc.services.ClassPropertyAdapter#getBeanType()}.
+     */
+    Class getBeanType();
+
+    /**
+     * Returns true if the property is actually a public field (possibly, a public static field).
+     *
+     * @since 5.2
+     */
+    boolean isField();
+
+    /**
+     * Returns the field if the property is a public field or null if the property is accessed via the read method.
+     *
+     * @since 5.2
+     */
+    Field getField();
+
+    /**
+     * The class in which the property (or public field) is defined.
+     *
+     * @since 5.2
+     */
+    Class getDeclaringClass();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/services/TypeCoercer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/services/TypeCoercer.java b/commons/src/main/java/org/apache/tapestry5/ioc/services/TypeCoercer.java
new file mode 100644
index 0000000..ec8eaad
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/services/TypeCoercer.java
@@ -0,0 +1,88 @@
+// Copyright 2006, 2007, 2008, 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.services;
+
+import org.apache.tapestry5.ioc.annotations.UsesConfiguration;
+
+/**
+ * Makes use of {@link org.apache.tapestry5.ioc.services.Coercion}s to convert between an input value (of some specific
+ * type) and a desired output type. Smart about coercing, even if it requires multiple coercion steps (i.e., via an
+ * intermediate type, such as String).
+ */
+@UsesConfiguration(CoercionTuple.class)
+public interface TypeCoercer
+{
+    /**
+     * Performs a coercion from an input type to a desired output type. When the target type is a primitive, the actual
+     * conversion will be to the equivalent wrapper type. In some cases, the TypeCoercer will need to search for an
+     * appropriate coercion, and may even combine existing coercions to form new ones; in those cases, the results of
+     * the search are cached.
+     * <p/>
+     * The TypeCoercer also caches the results of a coercion search.
+     * 
+     * @param <S>
+     *            source type (input)
+     * @param <T>
+     *            target type (output)
+     * @param input
+     * @param targetType
+     *            defines the target type
+     * @return the coerced value
+     * @throws RuntimeException
+     *             if the input can not be coerced
+     */
+    <S, T> T coerce(S input, Class<T> targetType);
+
+    /**
+     * Given a source and target type, computes the coercion that will be used.
+     * <p>
+     * Note: holding the returned coercion past the time when {@linkplain #clearCache() the cache is cleared} can cause
+     * a memory leak, especially in the context of live reloading (wherein holding a reference to a single class make
+     * keep an entire ClassLoader from being reclaimed).
+     * 
+     * @since 5.2.0
+     * @param <S>
+     *            source type (input)
+     * @param <T>
+     *            target type (output)
+     * @param sourceType
+     *            type to coerce from
+     * @param targetType
+     *            defines the target type
+     * @return the coercion that will ultimately be used
+     */
+    <S, T> Coercion<S, T> getCoercion(Class<S> sourceType, Class<T> targetType);
+
+    /**
+     * Used primarily inside test suites, this method performs the same steps as {@link #coerce(Object, Class)}, but
+     * returns a string describing the series of coercions, such as "Object --&gt; String --&gt; Long --&gt; Integer".
+     * 
+     * @param <S>
+     *            source type (input)
+     * @param <T>
+     *            target type (output)
+     * @param sourceType
+     *            the source coercion type (use void.class for coercions from null)
+     * @param targetType
+     *            defines the target type
+     * @return a string identifying the series of coercions, or the empty string if no coercion is necessary
+     */
+    <S, T> String explain(Class<S> sourceType, Class<T> targetType);
+
+    /**
+     * Clears cached information stored by the TypeCoercer.
+     */
+    void clearCache();
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
new file mode 100644
index 0000000..c4c5c6d
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
@@ -0,0 +1,87 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalUtils;
+
+/**
+ * Used (as part of a {@link UnknownValueException} to identify what available values
+ * are present.
+ * 
+ * @since 5.2.0
+ */
+public class AvailableValues
+{
+    private final String valueType;
+
+    private final List<String> values;
+
+    /**
+     * @param valueType
+     *            a word or phrase that describes what the values are such as "component types" or "service ids"
+     *@param values
+     *            a set of objects defining the values; the values will be converted to strings and sorted into
+     *            ascending order
+     */
+    public AvailableValues(String valueType, Collection<?> values)
+    {
+        this.valueType = valueType;
+        this.values = sortValues(values);
+    }
+
+    public AvailableValues(String valueType, Map<?, ?> map)
+    {
+        this(valueType, map.keySet());
+    }
+
+    private static List<String> sortValues(Collection<?> values)
+    {
+        List<String> result = CollectionFactory.newList();
+
+        for (Object v : values)
+        {
+            result.add(String.valueOf(v));
+        }
+
+        Collections.sort(result);
+
+        return Collections.unmodifiableList(result);
+    }
+
+    /** The type of value, i.e., "component types" or "service ids". */
+    public String getValueType()
+    {
+        return valueType;
+    }
+
+    /** The values, as strings, in sorted order. */
+    public List<String> getValues()
+    {
+        return values;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("AvailableValues[%s: %s]", valueType, InternalUtils.join(values));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/util/CaseInsensitiveMap.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/CaseInsensitiveMap.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/CaseInsensitiveMap.java
new file mode 100644
index 0000000..f5aff7e
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/CaseInsensitiveMap.java
@@ -0,0 +1,499 @@
+// Copyright 2007 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.util;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * An mapped collection where the keys are always strings and access to values is case-insensitive. The case of keys in
+ * the map is <em>maintained</em>, but on any access to a key (directly or indirectly), all key comparisons are
+ * performed in a case-insensitive manner. The map implementation is intended to support a reasonably finite number
+ * (dozens or hundreds, not thousands or millions of key/value pairs. Unlike HashMap, it is based on a sorted list of
+ * entries rather than hash bucket. It is also geared towards a largely static map, one that is created and then used
+ * without modification.
+ *
+ * @param <V> the type of value stored
+ */
+public class CaseInsensitiveMap<V> extends AbstractMap<String, V> implements Serializable
+{
+    private static final long serialVersionUID = 3362718337611953298L;
+
+    private static final int NULL_HASH = Integer.MIN_VALUE;
+
+    private static final int DEFAULT_SIZE = 20;
+
+    private static class CIMEntry<V> implements Map.Entry<String, V>, Serializable
+    {
+        private static final long serialVersionUID = 6713986085221148350L;
+
+        private String key;
+
+        private final int hashCode;
+
+        V value;
+
+        public CIMEntry(final String key, final int hashCode, V value)
+        {
+            this.key = key;
+            this.hashCode = hashCode;
+            this.value = value;
+        }
+
+        @Override
+        public String getKey()
+        {
+            return key;
+        }
+
+        @Override
+        public V getValue()
+        {
+            return value;
+        }
+
+        @Override
+        public V setValue(V value)
+        {
+            V result = this.value;
+
+            this.value = value;
+
+            return result;
+        }
+
+        /**
+         * Returns true if both keys are null, or if the provided key is the same as, or case-insensitively equal to,
+         * the entrie's key.
+         *
+         * @param key to compare against
+         * @return true if equal
+         */
+        @SuppressWarnings({ "StringEquality" })
+        boolean matches(String key)
+        {
+            return key == this.key || (key != null && key.equalsIgnoreCase(this.key));
+        }
+
+        boolean valueMatches(Object value)
+        {
+            return value == this.value || (value != null && value.equals(this.value));
+        }
+    }
+
+    private class EntrySetIterator implements Iterator
+    {
+        int expectedModCount = modCount;
+
+        int index;
+
+        int current = -1;
+
+        @Override
+        public boolean hasNext()
+        {
+            return index < size;
+        }
+
+        @Override
+        public Object next()
+        {
+            check();
+
+            if (index >= size) throw new NoSuchElementException();
+
+            current = index++;
+
+            return entries[current];
+        }
+
+        @Override
+        public void remove()
+        {
+            check();
+
+            if (current < 0) throw new NoSuchElementException();
+
+            new Position(current, true).remove();
+
+            expectedModCount = modCount;
+        }
+
+        private void check()
+        {
+            if (expectedModCount != modCount) throw new ConcurrentModificationException();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private class EntrySet extends AbstractSet
+    {
+        @Override
+        public Iterator iterator()
+        {
+            return new EntrySetIterator();
+        }
+
+        @Override
+        public int size()
+        {
+            return size;
+        }
+
+        @Override
+        public void clear()
+        {
+            CaseInsensitiveMap.this.clear();
+        }
+
+        @Override
+        public boolean contains(Object o)
+        {
+            if (!(o instanceof Map.Entry)) return false;
+
+            Map.Entry e = (Map.Entry) o;
+
+            Position position = select(e.getKey());
+
+            return position.isFound() && position.entry().valueMatches(e.getValue());
+        }
+
+        @Override
+        public boolean remove(Object o)
+        {
+            if (!(o instanceof Map.Entry)) return false;
+
+            Map.Entry e = (Map.Entry) o;
+
+            Position position = select(e.getKey());
+
+            if (position.isFound() && position.entry().valueMatches(e.getValue()))
+            {
+                position.remove();
+                return true;
+            }
+
+            return false;
+        }
+
+    }
+
+    private class Position
+    {
+        private final int cursor;
+
+        private final boolean found;
+
+        Position(int cursor, boolean found)
+        {
+            this.cursor = cursor;
+            this.found = found;
+        }
+
+        boolean isFound()
+        {
+            return found;
+        }
+
+        CIMEntry<V> entry()
+        {
+            return entries[cursor];
+        }
+
+        V get()
+        {
+            return found ? entries[cursor].value : null;
+        }
+
+        V remove()
+        {
+            if (!found) return null;
+
+            V result = entries[cursor].value;
+
+            // Remove the entry by shifting everything else down.
+
+            System.arraycopy(entries, cursor + 1, entries, cursor, size - cursor - 1);
+
+            // We shifted down, leaving one (now duplicate) entry behind.
+
+            entries[--size] = null;
+
+            // A structural change for sure
+
+            modCount++;
+
+            return result;
+        }
+
+        @SuppressWarnings("unchecked")
+        V put(String key, int hashCode, V newValue)
+        {
+            if (found)
+            {
+                CIMEntry<V> e = entries[cursor];
+
+                V result = e.value;
+
+                // Not a structural change, so no change to modCount
+
+                // Update the key (to maintain case). By definition, the hash code
+                // will not change.
+
+                e.key = key;
+                e.value = newValue;
+
+                return result;
+            }
+
+            // Not found, we're going to add it.
+
+            int newSize = size + 1;
+
+            if (newSize == entries.length)
+            {
+                // Time to expand!
+
+                int newCapacity = (size * 3) / 2 + 1;
+
+                CIMEntry<V>[] newEntries = new CIMEntry[newCapacity];
+
+                System.arraycopy(entries, 0, newEntries, 0, cursor);
+
+                System.arraycopy(entries, cursor, newEntries, cursor + 1, size - cursor);
+
+                entries = newEntries;
+            }
+            else
+            {
+                // Open up a space for the new entry
+
+                System.arraycopy(entries, cursor, entries, cursor + 1, size - cursor);
+            }
+
+            CIMEntry<V> newEntry = new CIMEntry<V>(key, hashCode, newValue);
+            entries[cursor] = newEntry;
+
+            size++;
+
+            // This is definately a structural change
+
+            modCount++;
+
+            return null;
+        }
+
+    }
+
+    // The list of entries. This is kept sorted by hash code. In some cases, there may be different
+    // keys with the same hash code in adjacent indexes.
+    private CIMEntry<V>[] entries;
+
+    private int size = 0;
+
+    // Used by iterators to check for concurrent modifications
+
+    private transient int modCount = 0;
+
+    private transient Set<Map.Entry<String, V>> entrySet;
+
+    public CaseInsensitiveMap()
+    {
+        this(DEFAULT_SIZE);
+    }
+
+    @SuppressWarnings("unchecked")
+    public CaseInsensitiveMap(int size)
+    {
+        entries = new CIMEntry[Math.max(size, 3)];
+    }
+
+    public CaseInsensitiveMap(Map<String, ? extends V> map)
+    {
+        this(map.size());
+
+        for (Map.Entry<String, ? extends V> entry : map.entrySet())
+        {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    @Override
+    public void clear()
+    {
+        for (int i = 0; i < size; i++)
+            entries[i] = null;
+
+        size = 0;
+        modCount++;
+    }
+
+    @Override
+    public boolean isEmpty()
+    {
+        return size == 0;
+    }
+
+    @Override
+    public int size()
+    {
+        return size;
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public V put(String key, V value)
+    {
+        int hashCode = caseInsenitiveHashCode(key);
+
+        return select(key, hashCode).put(key, hashCode, value);
+    }
+
+    @Override
+    public boolean containsKey(Object key)
+    {
+        return select(key).isFound();
+    }
+
+    @Override
+    public V get(Object key)
+    {
+        return select(key).get();
+    }
+
+    @Override
+    public V remove(Object key)
+    {
+        return select(key).remove();
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public Set<Map.Entry<String, V>> entrySet()
+    {
+        if (entrySet == null) entrySet = new EntrySet();
+
+        return entrySet;
+    }
+
+    private Position select(Object key)
+    {
+        if (key == null || key instanceof String)
+        {
+            String keyString = (String) key;
+            return select(keyString, caseInsenitiveHashCode(keyString));
+        }
+
+        return new Position(0, false);
+    }
+
+    /**
+     * Searches the elements for the index of the indicated key and (case insensitive) hash code. Sets the _cursor and
+     * _found attributes.
+     */
+    private Position select(String key, int hashCode)
+    {
+        if (size == 0) return new Position(0, false);
+
+        int low = 0;
+        int high = size - 1;
+
+        int cursor;
+
+        while (low <= high)
+        {
+            cursor = (low + high) >> 1;
+
+            CIMEntry e = entries[cursor];
+
+            if (e.hashCode < hashCode)
+            {
+                low = cursor + 1;
+                continue;
+            }
+
+            if (e.hashCode > hashCode)
+            {
+                high = cursor - 1;
+                continue;
+            }
+
+            return tunePosition(key, hashCode, cursor);
+        }
+
+        return new Position(low, false);
+    }
+
+    /**
+     * select() has located a matching hashCode, but there's an outlying possibility that multiple keys share the same
+     * hashCode. Backup the cursor until we get to locate the initial hashCode match, then march forward until the key
+     * is located, or the hashCode stops matching.
+     *
+     * @param key
+     * @param hashCode
+     */
+    private Position tunePosition(String key, int hashCode, int cursor)
+    {
+        boolean found = false;
+
+        while (cursor > 0)
+        {
+            if (entries[cursor - 1].hashCode != hashCode) break;
+
+            cursor--;
+        }
+
+        while (true)
+        {
+            if (entries[cursor].matches(key))
+            {
+                found = true;
+                break;
+            }
+
+            // Advance to the next entry.
+
+            cursor++;
+
+            // If out of entries,
+            if (cursor >= size || entries[cursor].hashCode != hashCode) break;
+        }
+
+        return new Position(cursor, found);
+    }
+
+    static int caseInsenitiveHashCode(String input)
+    {
+        if (input == null) return NULL_HASH;
+
+        int length = input.length();
+        int hash = 0;
+
+        // This should end up more or less equal to input.toLowerCase().hashCode(), unless String
+        // changes its implementation. Let's hope this is reasonably fast.
+
+        for (int i = 0; i < length; i++)
+        {
+            int ch = input.charAt(i);
+
+            int caselessCh = Character.toLowerCase(ch);
+
+            hash = 31 * hash + caselessCh;
+        }
+
+        return hash;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/util/ExceptionUtils.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/ExceptionUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/ExceptionUtils.java
new file mode 100644
index 0000000..2feaeca
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/ExceptionUtils.java
@@ -0,0 +1,115 @@
+// Copyright 2008-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.ioc.util;
+
+import org.apache.tapestry5.ioc.services.ClassPropertyAdapter;
+import org.apache.tapestry5.ioc.services.PropertyAccess;
+
+/**
+ * Contains static methods useful for manipulating exceptions.
+ */
+public class ExceptionUtils
+{
+    /**
+     * Locates a particular type of exception, working its way via the cause property of each exception in the exception
+     * stack.
+     *
+     * @param t    the outermost exception
+     * @param type the type of exception to search for
+     * @return the first exception of the given type, if found, or null
+     */
+    public static <T extends Throwable> T findCause(Throwable t, Class<T> type)
+    {
+        Throwable current = t;
+
+        while (current != null)
+        {
+            if (type.isInstance(current))
+            {
+                return type.cast(current);
+            }
+
+            // Not a match, work down.
+
+            current = current.getCause();
+        }
+
+        return null;
+    }
+
+    /**
+     * Locates a particular type of exception, working its way down via any property that returns some type of Exception.
+     * This is more expensive, but more accurate, than {@link #findCause(Throwable, Class)} as it works with older exceptions
+     * that do not properly implement the (relatively new) {@linkplain Throwable#getCause() cause property}.
+     *
+     * @param t      the outermost exception
+     * @param type   the type of exception to search for
+     * @param access used to access properties
+     * @return the first exception of the given type, if found, or null
+     */
+    public static <T extends Throwable> T findCause(Throwable t, Class<T> type, PropertyAccess access)
+    {
+        Throwable current = t;
+
+        while (current != null)
+        {
+            if (type.isInstance(current))
+            {
+                return type.cast(current);
+            }
+
+            Throwable next = null;
+
+            ClassPropertyAdapter adapter = access.getAdapter(current);
+
+            for (String name : adapter.getPropertyNames())
+            {
+
+                Object value = adapter.getPropertyAdapter(name).get(current);
+
+                if (value != null && value != current && value instanceof Throwable)
+                {
+                    next = (Throwable) value;
+                    break;
+                }
+            }
+
+            current = next;
+        }
+
+
+        return null;
+    }
+
+    /**
+     * Extracts the message from an exception. If the exception's message is null, returns the exceptions class name.
+     *
+     * @param exception
+     *         to extract message from
+     * @return message or class name
+     * @since 5.4
+     */
+    public static String toMessage(Throwable exception)
+    {
+        assert exception != null;
+
+        String message = exception.getMessage();
+
+        if (message != null)
+            return message;
+
+        return exception.getClass().getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/ioc/util/UnknownValueException.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/UnknownValueException.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/UnknownValueException.java
new file mode 100644
index 0000000..470b611
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/UnknownValueException.java
@@ -0,0 +1,47 @@
+// Copyright 2010 The Apache Software Foundation
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.ioc.util;
+
+import org.apache.tapestry5.ioc.internal.util.TapestryException;
+
+/**
+ * Special exception used when a value (typically from a map) is referenced that does not exist. Uses a
+ * {@link AvailableValues} object
+ * to track what the known values are.
+ * 
+ * @since 5.2.0
+ */
+public class UnknownValueException extends TapestryException
+{
+    private final AvailableValues availableValues;
+
+    public UnknownValueException(String message, AvailableValues availableValues)
+    {
+        this(message, null, null, availableValues);
+    }
+
+    public UnknownValueException(String message, Object location, Throwable cause, AvailableValues availableValues)
+    {
+        super(message, location, cause);
+
+        this.availableValues = availableValues;
+    }
+
+    public AvailableValues getAvailableValues()
+    {
+        return availableValues;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f963c7ab/commons/src/main/java/org/apache/tapestry5/services/InvalidationEventHub.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/services/InvalidationEventHub.java b/commons/src/main/java/org/apache/tapestry5/services/InvalidationEventHub.java
new file mode 100644
index 0000000..77b028e
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/services/InvalidationEventHub.java
@@ -0,0 +1,60 @@
+// Copyright 2006, 2007, 2008, 2011, 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.services;
+
+import java.util.Map;
+
+/**
+ * An object which manages a list of {@link org.apache.tapestry5.services.InvalidationListener}s. There are multiple
+ * event hub services implementing this interface, each with a specific marker annotation; each can register listeners
+ * and fire events; these are based on the type of resource that has been invalidated. Tapestry has built-in support
+ * for:
+ * <dl>
+ * <dt>message catalog resources
+ * <dd>{@link org.apache.tapestry5.services.ComponentMessages} marker annotation
+ * <dt>component templates
+ * <dd>{@link org.apache.tapestry5.services.ComponentTemplates} marker annotation
+ * <dt>component classes
+ * <dd>{@link org.apache.tapestry5.services.ComponentClasses} marker annotation
+ * </dl>
+ * <p/>
+ * Starting in Tapestry 5.3, these services are disabled in production (it does nothing).
+ *
+ * @since 5.1.0.0
+ */
+public interface InvalidationEventHub
+{
+    /**
+     * Adds a listener, who needs to know when an underlying resource of a given category has changed (so that the
+     * receiver may discard any cached data that may have been invalidated). Does nothing in production mode.
+     *
+     * @deprecated in 5.4, use {@link #addInvalidationCallback(Runnable)} instead}
+     */
+    void addInvalidationListener(InvalidationListener listener);
+
+    /**
+     * Adds a callback that is invoked when an underlying tracked resource has changed. Does nothing in production mode.
+     *
+     * @since  5.4
+     */
+    void addInvalidationCallback(Runnable callback);
+
+    /**
+     * Adds a callback that clears the map.
+     *
+     * @since 5.4
+     */
+    void clearOnInvalidation(Map<?,?> map);
+}