Dépôt officiel du code source de l'ERP OpenConcerto
Rev 144 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.utils;
import java.beans.Expression;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class ReflectUtils {
/**
* Find a constructor with compatible parameters. NOTE: This follow a simpler algorithm than the
* one in the JLS (<a href=
* "https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.12.2.5">15.12.2.5.
* Choosing the Most Specific Method</a>).
*
* @param cls the class to find a constructor for, not <code>null</code>.
* @param parameterTypes types of parameters the constructor must accept.
* @return the first matching constructor, <code>null</code> if no matching constructor found.
*/
static public final <T> Constructor<T> getMatchingConstructor(Class<T> cls, Class<?>... parameterTypes) {
try {
return cls.getConstructor(parameterTypes);
} catch (NoSuchMethodException e) {
for (final Constructor<?> c : cls.getConstructors()) {
final Class<?>[] ctorParameters = c.getParameterTypes();
if (ctorParameters.length == parameterTypes.length) {
boolean ok = true;
for (int i = 0; ok && i < ctorParameters.length; i++) {
ok &= ctorParameters[i].isAssignableFrom(parameterTypes[i]);
}
if (ok) {
@SuppressWarnings("unchecked")
final Constructor<T> res = (Constructor<T>) c;
assert c.getDeclaringClass() == cls;
return res;
}
}
}
return null;
}
}
@SuppressWarnings("unchecked")
static public final <T> T createInstance(final Class<T> clazz, Object... args) throws Exception {
// too difficult to order superclasses and all implemented interfaces of args in order to
// find the most specific constructor
return (T) new Expression(clazz, "new", args).getValue();
}
/**
* Return the class with the passed name if it is a subclass of <code>clazz</code>.
*
* @param name the name of the class, not <code>null</code>.
* @param clazz the superclass, not <code>null</code>.
* @return the requested class, <code>null</code> if {@link ClassNotFoundException not found} or
* not a {@link Class#asSubclass(Class) subclass} of <code>clazz</code>.
*/
static public final <T> Class<? extends T> getSubclass(final String name, final Class<T> clazz) {
return getSubclass(name, clazz, clazz.getClassLoader());
}
static public final <T> Class<? extends T> getSubclass(final String name, final Class<T> clazz, final ClassLoader cl) {
final Class<?> res;
try {
res = Class.forName(name, true, cl);
} catch (ClassNotFoundException e) {
return null;
}
return clazz.isAssignableFrom(res) ? res.asSubclass(clazz) : null;
}
static private Map<Type, Type> resolveTypes(Class<?> c, Class<?> raw) {
final Map<Type, Type> res = new HashMap<Type, Type>();
if (!raw.isAssignableFrom(c))
return res;
// c : ListDeString implements List<String>
final List<Type> types = new ArrayList<Type>(Arrays.asList(c.getGenericInterfaces()));
types.add(c.getGenericSuperclass());
for (final Type t : types) {
if (t instanceof ParameterizedType) {
// eg List<String>
final ParameterizedType pt = (ParameterizedType) t;
if (raw.isAssignableFrom((Class) pt.getRawType())) {
// eg List.class (List<E>)
final Class<?> rawType = (Class) pt.getRawType();
// eg [String.class]
final Type[] actualTypeArguments = pt.getActualTypeArguments();
// eg [E]
final TypeVariable<?>[] typeParameters = rawType.getTypeParameters();
for (int i = 0; i < actualTypeArguments.length; i++) {
res.put(typeParameters[i], actualTypeArguments[i]);
}
}
}
final Class<?> tc = getClass(t);
if (tc != null) {
res.putAll(resolveTypes(tc, raw));
}
}
return res;
}
/**
* The map of type arguments of baseClass to actual type for childClass.
*
* @param <T> the type of the baseClass.
* @param childClass the class to test, eg Props.class with Props<V> extends Map<String, V>.
* @param baseClass the generic superclass, eg Map.class.
* @return a the map, eg {K => String.class, V => null}.
*/
public static <T> Map<TypeVariable<Class<T>>, Class<?>> getTypeArgumentsMap(Class<? extends T> childClass, Class<T> baseClass) {
final TypeVariable<Class<T>>[] actualTypeArguments = baseClass.getTypeParameters();
if (actualTypeArguments.length == 0)
throw new IllegalArgumentException(baseClass + " is not generic");
final Map<TypeVariable<Class<T>>, Class<?>> res = new LinkedHashMap<TypeVariable<Class<T>>, Class<?>>();
final Map<Type, Type> resolvedTypes = resolveTypes(childClass, baseClass);
// for each actual type argument provided to baseClass, determine (if possible)
// the raw class for that type argument.
// resolve types by chasing down type variables.
for (final TypeVariable<Class<T>> baseType : actualTypeArguments) {
Type currentType = baseType;
while (resolvedTypes.containsKey(currentType)) {
currentType = resolvedTypes.get(currentType);
}
res.put(baseType, getClass(currentType));
}
return res;
}
/**
* Search for the list of class used to extend/implement a generic class/interface.
*
* @param <T> the type of the baseClass.
* @param childClass the class to test, eg Props.class with Props<V> extends Map<String, V>.
* @param baseClass the generic superclass, eg Map.class.
* @return the list of actual classes w/o the possible nulls (if childClass is generic), never
* <code>null</code>, eg [Boolean.class].
*/
public static <T> List<Class<?>> getTypeArguments(Class<? extends T> childClass, Class<T> baseClass) {
final ArrayList<Class<?>> res = new ArrayList<Class<?>>();
// ok since getTypeArgumentsMap returns a LinkedHashMap
for (final Class<?> c : getTypeArgumentsMap(childClass, baseClass).values()) {
if (c != null)
res.add(c);
}
return res;
}
static public <U> List<Class<?>> getTypeArguments(U o, Class<U> raw) {
return getTypeArguments(o.getClass().asSubclass(raw), raw);
}
/**
* Whether o can be casted to raw<typeArgs>.
*
* @param <U> type of the superclass.
* @param o the instance to check, eg new MapOfInt2Boolean().
* @param raw the generic superclass, eg Map.class.
* @param typeArgs arguments to <code>raw</code>, eg Integer.class, Boolean.class.
* @return whether o is a raw<typeArgs>, eg <code>true</code> : new MapOfInt2Boolean() is
* a Map<Integer, Boolean>.
*/
static public <U> boolean isCastable(U o, Class<U> raw, Class... typeArgs) {
return getTypeArguments(o, raw).equals(Arrays.asList(typeArgs));
}
// *** pasted from http://www.artima.com/weblogs/viewpost.jsp?thread=208860
/**
* Get the underlying class for a type, or null if the type is a variable type.
*
* @param type the type
* @return the underlying class
*/
private static Class<?> getClass(Type type) {
if (type instanceof Class) {
return (Class) type;
} else if (type instanceof ParameterizedType) {
return getClass(((ParameterizedType) type).getRawType());
} else if (type instanceof GenericArrayType) {
Type componentType = ((GenericArrayType) type).getGenericComponentType();
Class<?> componentClass = getClass(componentType);
if (componentClass != null) {
return Array.newInstance(componentClass, 0).getClass();
} else {
return null;
}
} else {
return null;
}
}
}