Repository: tapestry-5 Updated Branches: refs/heads/master ebdb52a19 -> faf3b20c9
Revert "TAP5-2453: move GenericsUtils (and tests) to plastic" This reverts commit 5c617af6b3bfef81d79c573aec0acf29e1d0eb8c. Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/faf3b20c Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/faf3b20c Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/faf3b20c Branch: refs/heads/master Commit: faf3b20c9c8f59e890bea4eff5ce8e42df083815 Parents: ebdb52a Author: Thiago H. de Paula Figueiredo <[email protected]> Authored: Wed Jan 16 16:06:45 2019 -0200 Committer: Thiago H. de Paula Figueiredo <[email protected]> Committed: Wed Jan 16 16:06:45 2019 -0200 ---------------------------------------------------------------------- .../ioc/internal/util/GenericsUtils.java | 643 +++++++++++++++++++ .../ioc/internal/util/GenericsUtils.java | 643 ------------------- .../groovy/ioc/specs/GenericUtilsSpec.groovy | 40 -- .../ioc/internal/util/BaseGenericBean.java | 30 - .../ioc/internal/util/NonGenericBean.java | 30 - .../tapestry5/ioc/internal/util/Pair.java | 42 -- .../tapestry5/ioc/internal/util/StringBean.java | 20 - .../ioc/internal/util/StringLongPair.java | 19 - .../groovy/ioc/specs/GenericUtilsSpec.groovy | 40 ++ .../ioc/internal/util/BaseGenericBean.java | 30 + .../ioc/internal/util/NonGenericBean.java | 30 + .../tapestry5/ioc/internal/util/StringBean.java | 20 + 12 files changed, 763 insertions(+), 824 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/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..abbb767 --- /dev/null +++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java @@ -0,0 +1,643 @@ +// 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.Array; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * 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. + * + * {@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. {@code List<E>; List<Map<Long, String>>}, E would be returned as a ParameterizedType with the raw + * type Map and type arguments Long and String. + * + * + * @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. {@code GenericDAO<Pet, String>} + * @param suspectedSubType + * e.g. {@code 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. + * + * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information + * is lost. + * + * {@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. + * + * + * 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. + * + * Consider <br/> + * <i>class Pair>A,B> { A getA(){...}; ...}</i><br/> + * <i>class StringLongPair extends Pair>String, Long> { }</i><br/> + * + * 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. + * + * 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. + * + * 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); + addGenericSuperclasses(theClass, typeVariableOwner, stack); + + 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]; + } + + + private static void addGenericSuperclasses(Class theClass, final Class typeVariableOwner, final LinkedList<Type> stack) { + 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(); + } + for (Type type : theClass.getGenericInterfaces()) { + stack.add(type); + } + for (Class implementedInterface : getAllImplementedInterfaces(theClass)) { + addGenericSuperclasses(implementedInterface, typeVariableOwner, stack); + } + } + + private static List<Class> getAllImplementedInterfaces(Class theClass) { + List<Class> list = new ArrayList<>(); + for (Class implementedInterface : theClass.getInterfaces()) { + list.add(implementedInterface); + list.addAll(getAllImplementedInterfaces(implementedInterface)); + } + return list; + } + + /** + * @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", + 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/faf3b20c/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java ---------------------------------------------------------------------- diff --git a/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java b/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java deleted file mode 100644 index abbb767..0000000 --- a/plastic/src/main/java/org/apache/tapestry5/ioc/internal/util/GenericsUtils.java +++ /dev/null @@ -1,643 +0,0 @@ -// 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.Array; -import java.lang.reflect.Field; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.GenericDeclaration; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -/** - * 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. - * - * {@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. {@code List<E>; List<Map<Long, String>>}, E would be returned as a ParameterizedType with the raw - * type Map and type arguments Long and String. - * - * - * @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. {@code GenericDAO<Pet, String>} - * @param suspectedSubType - * e.g. {@code 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. - * - * {@code TypeVariable} the first bound is returned. If your type variable extends multiple interfaces that information - * is lost. - * - * {@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. - * - * - * 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. - * - * Consider <br/> - * <i>class Pair>A,B> { A getA(){...}; ...}</i><br/> - * <i>class StringLongPair extends Pair>String, Long> { }</i><br/> - * - * 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. - * - * 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. - * - * 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); - addGenericSuperclasses(theClass, typeVariableOwner, stack); - - 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]; - } - - - private static void addGenericSuperclasses(Class theClass, final Class typeVariableOwner, final LinkedList<Type> stack) { - 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(); - } - for (Type type : theClass.getGenericInterfaces()) { - stack.add(type); - } - for (Class implementedInterface : getAllImplementedInterfaces(theClass)) { - addGenericSuperclasses(implementedInterface, typeVariableOwner, stack); - } - } - - private static List<Class> getAllImplementedInterfaces(Class theClass) { - List<Class> list = new ArrayList<>(); - for (Class implementedInterface : theClass.getInterfaces()) { - list.add(implementedInterface); - list.addAll(getAllImplementedInterfaces(implementedInterface)); - } - return list; - } - - /** - * @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", - 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/faf3b20c/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy ---------------------------------------------------------------------- diff --git a/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy b/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy deleted file mode 100644 index 6b527d2..0000000 --- a/plastic/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy +++ /dev/null @@ -1,40 +0,0 @@ -package ioc.specs - -import org.apache.tapestry5.ioc.internal.util.GenericsUtils -import org.apache.tapestry5.ioc.internal.util.NonGenericBean -import org.apache.tapestry5.ioc.internal.util.StringBean -import org.apache.tapestry5.ioc.internal.util.StringLongPair -import spock.lang.Specification -import spock.lang.Unroll - -class GenericUtilsSpec extends Specification { - - def find(clazz, name) { - def method = clazz.methods.find { it.name.equalsIgnoreCase(name) } - - if (method == null) { - throw new IllegalArgumentException("Unable to find method '$name' of ${clazz.name}.") - } - - return method - } - - @Unroll - def "generic return type for #method is #expected"() { - - expect: - - GenericsUtils.extractGenericReturnType(clazz, method).is(expected) - - where: - - clazz | name | expected - NonGenericBean | "getvalue" | String - StringBean | "getvalue" | String - StringLongPair | "getkey" | String - StringLongPair | "getvalue" | Long - - method = find(clazz, name) - } - -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java ---------------------------------------------------------------------- diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java deleted file mode 100644 index dc4c2c7..0000000 --- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2008 The Apache Software Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.apache.tapestry5.ioc.internal.util; - -public class BaseGenericBean<T> -{ - private T value; - - public T getValue() - { - return value; - } - - public void setValue(T value) - { - this.value = value; - } -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java ---------------------------------------------------------------------- diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java deleted file mode 100644 index 8cce49f..0000000 --- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2008 The Apache Software Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.apache.tapestry5.ioc.internal.util; - -public class NonGenericBean -{ - private String value; - - public String getValue() - { - return value; - } - - public void setValue(String value) - { - this.value = value; - } -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java ---------------------------------------------------------------------- diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java deleted file mode 100644 index 00722f4..0000000 --- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/Pair.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2008 The Apache Software Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.apache.tapestry5.ioc.internal.util; - -public class Pair<K, V> -{ - private K key; - - private V value; - - public K getKey() - { - return key; - } - - public void setKey(K key) - { - this.key = key; - } - - public V getValue() - { - return value; - } - - public void setValue(V value) - { - this.value = value; - } -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java ---------------------------------------------------------------------- diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java deleted file mode 100644 index 1b13856..0000000 --- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2008 The Apache Software Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.apache.tapestry5.ioc.internal.util; - -public class StringBean extends BaseGenericBean<String> -{ - -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java ---------------------------------------------------------------------- diff --git a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java b/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java deleted file mode 100644 index 0071beb..0000000 --- a/plastic/src/test/java/org/apache/tapestry5/ioc/internal/util/StringLongPair.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2008 The Apache Software Foundation -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package org.apache.tapestry5.ioc.internal.util; - -public class StringLongPair extends Pair<String, Long> -{ -} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy new file mode 100644 index 0000000..6b527d2 --- /dev/null +++ b/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy @@ -0,0 +1,40 @@ +package ioc.specs + +import org.apache.tapestry5.ioc.internal.util.GenericsUtils +import org.apache.tapestry5.ioc.internal.util.NonGenericBean +import org.apache.tapestry5.ioc.internal.util.StringBean +import org.apache.tapestry5.ioc.internal.util.StringLongPair +import spock.lang.Specification +import spock.lang.Unroll + +class GenericUtilsSpec extends Specification { + + def find(clazz, name) { + def method = clazz.methods.find { it.name.equalsIgnoreCase(name) } + + if (method == null) { + throw new IllegalArgumentException("Unable to find method '$name' of ${clazz.name}.") + } + + return method + } + + @Unroll + def "generic return type for #method is #expected"() { + + expect: + + GenericsUtils.extractGenericReturnType(clazz, method).is(expected) + + where: + + clazz | name | expected + NonGenericBean | "getvalue" | String + StringBean | "getvalue" | String + StringLongPair | "getkey" | String + StringLongPair | "getvalue" | Long + + method = find(clazz, name) + } + +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java new file mode 100644 index 0000000..dc4c2c7 --- /dev/null +++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/BaseGenericBean.java @@ -0,0 +1,30 @@ +// Copyright 2008 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.apache.tapestry5.ioc.internal.util; + +public class BaseGenericBean<T> +{ + private T value; + + public T getValue() + { + return value; + } + + public void setValue(T value) + { + this.value = value; + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java new file mode 100644 index 0000000..8cce49f --- /dev/null +++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/NonGenericBean.java @@ -0,0 +1,30 @@ +// Copyright 2008 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.apache.tapestry5.ioc.internal.util; + +public class NonGenericBean +{ + private String value; + + public String getValue() + { + return value; + } + + public void setValue(String value) + { + this.value = value; + } +} http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/faf3b20c/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java ---------------------------------------------------------------------- diff --git a/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java new file mode 100644 index 0000000..1b13856 --- /dev/null +++ b/tapestry-ioc/src/test/java/org/apache/tapestry5/ioc/internal/util/StringBean.java @@ -0,0 +1,20 @@ +// Copyright 2008 The Apache Software Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.apache.tapestry5.ioc.internal.util; + +public class StringBean extends BaseGenericBean<String> +{ + +}
