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<Foo, Bar> map = newMap();
+ * </pre>
+ * <p/>
+ * <p/>
+ * This is a replacement for:
+ * <p/>
+ * <pre>
+ * Map<Foo, Bar> map = new HashMap<Foo, Bar>();
+ * </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<E>; List<Map<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<Pet, String>
+ * @param suspectedSubType
+ * e.g. PetDAO extends GenericDAO<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>A,B> { A getA(){...}; ...}</i><br/>
+ * <i>class StringLongPair extends Pair>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<T>[] or List<? 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<T>, List<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<? super T>, List<<? extends T>, List<? extends T & Comparable<? 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 --> String --> Long --> 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);
+}