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); +}