// *************************************************************************************************************************** | |
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * | |
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * | |
// * to you 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.juneau.internal; | |
import static org.apache.juneau.internal.CollectionUtils.*; | |
import java.io.*; | |
import java.lang.annotation.*; | |
import java.lang.reflect.*; | |
import java.util.*; | |
import org.apache.juneau.*; | |
import org.apache.juneau.reflection.*; | |
import org.apache.juneau.utils.*; | |
/** | |
* Class-related utility methods. | |
*/ | |
public final class ClassUtils { | |
/** | |
* Shortcut for calling {@link ClassInfo#of(Type)}. | |
* | |
* @param t The class being wrapped. | |
* @return The wrapped class. | |
*/ | |
public static ClassInfo getClassInfo(Type t) { | |
return ClassInfo.of(t); | |
} | |
/** | |
* Shortcut for calling {@link ClassInfo#of(Object)}. | |
* | |
* @param o The object whose class being wrapped. | |
* @return The wrapped class. | |
*/ | |
public static ClassInfo getClassInfo(Object o) { | |
return ClassInfo.of(o); | |
} | |
/** | |
* Shortcut for calling {@link MethodInfo#of(Method)}. | |
* | |
* @param m The method being wrapped. | |
* @return The wrapped method. | |
*/ | |
public static MethodInfo getMethodInfo(Method m) { | |
return MethodInfo.of(m); | |
} | |
/** | |
* Shortcut for calling {@link FieldInfo#of(Field)}. | |
* | |
* @param f The field being wrapped. | |
* @return The wrapped field. | |
*/ | |
public static FieldInfo getFieldInfo(Field f) { | |
return FieldInfo.of(f); | |
} | |
/** | |
* Shortcut for calling {@link ConstructorInfo#of(Constructor)}. | |
* | |
* @param c The constructor being wrapped. | |
* @return The wrapped constructor. | |
*/ | |
public static ConstructorInfo getConstructorInfo(Constructor<?> c) { | |
return ConstructorInfo.of(c); | |
} | |
/** | |
* Given the specified list of objects, return readable names for the class types of the objects. | |
* | |
* @param o The objects. | |
* @return An array of readable class type strings. | |
*/ | |
public static ObjectList getReadableClassNames(Object[] o) { | |
ObjectList l = new ObjectList(); | |
for (int i = 0; i < o.length; i++) | |
l.add(o[i] == null ? "null" : getReadableClassName(o[i].getClass())); | |
return l; | |
} | |
/** | |
* Shortcut for calling <code><jsm>getReadableClassName</jsm>(c.getName())</code> | |
* | |
* @param c The class. | |
* @return A readable class type name, or <jk>null</jk> if parameter is <jk>null</jk>. | |
*/ | |
public static String getReadableClassName(Class<?> c) { | |
if (c == null) | |
return null; | |
return getReadableClassName(c.getName()); | |
} | |
/** | |
* Shortcut for calling <code><jsm>getReadableClassName</jsm>(c.getClass().getName())</code> | |
* | |
* @param o The object whose class we want to render. | |
* @return A readable class type name, or <jk>null</jk> if parameter is <jk>null</jk>. | |
*/ | |
public static String getReadableClassNameForObject(Object o) { | |
if (o == null) | |
return null; | |
return getReadableClassName(o.getClass().getName()); | |
} | |
/** | |
* Converts the specified class name to a readable form when class name is a special construct like <js>"[[Z"</js>. | |
* | |
* <h5 class='section'>Example:</h5> | |
* <p class='bcode w800'> | |
* <jsm>getReadableClassName</jsm>(<js>"java.lang.Object"</js>); <jc>// Returns "java.lang.Object"</jc> | |
* <jsm>getReadableClassName</jsm>(<js>"boolean"</js>); <jc>// Returns "boolean"</jc> | |
* <jsm>getReadableClassName</jsm>(<js>"[Z"</js>); <jc>// Returns "boolean[]"</jc> | |
* <jsm>getReadableClassName</jsm>(<js>"[[Z"</js>); <jc>// Returns "boolean[][]"</jc> | |
* <jsm>getReadableClassName</jsm>(<js>"[Ljava.lang.Object;"</js>); <jc>// Returns "java.lang.Object[]"</jc> | |
* <jsm>getReadableClassName</jsm>(<jk>null</jk>); <jc>// Returns null</jc> | |
* </p> | |
* | |
* @param className The class name. | |
* @return A readable class type name, or <jk>null</jk> if parameter is <jk>null</jk>. | |
*/ | |
public static String getReadableClassName(String className) { | |
if (className == null) | |
return null; | |
if (! StringUtils.startsWith(className, '[')) | |
return className; | |
int depth = 0; | |
for (int i = 0; i < className.length(); i++) { | |
if (className.charAt(i) == '[') | |
depth++; | |
else | |
break; | |
} | |
char type = className.charAt(depth); | |
String c; | |
switch (type) { | |
case 'Z': c = "boolean"; break; | |
case 'B': c = "byte"; break; | |
case 'C': c = "char"; break; | |
case 'D': c = "double"; break; | |
case 'F': c = "float"; break; | |
case 'I': c = "int"; break; | |
case 'J': c = "long"; break; | |
case 'S': c = "short"; break; | |
default: c = className.substring(depth+1, className.length()-1); | |
} | |
StringBuilder sb = new StringBuilder(c.length() + 2*depth).append(c); | |
for (int i = 0; i < depth; i++) | |
sb.append("[]"); | |
return sb.toString(); | |
} | |
/** | |
* Returns <jk>true</jk> if the specified class is public. | |
* | |
* @param c The class. | |
* @return <jk>true</jk> if the specified class is public. | |
*/ | |
public static boolean isStatic(Class<?> c) { | |
return Modifier.isStatic(c.getModifiers()); | |
} | |
/** | |
* Returns <jk>true</jk> if the specified class is abstract. | |
* | |
* @param c The class. | |
* @return <jk>true</jk> if the specified class is abstract. | |
*/ | |
public static boolean isAbstract(Class<?> c) { | |
return Modifier.isAbstract(c.getModifiers()); | |
} | |
/** | |
* Finds the real parameter type of the specified class. | |
* | |
* @param c The class containing the parameters (e.g. PojoSwap<T,S>) | |
* @param index The zero-based index of the parameter to resolve. | |
* @param oc The class we're trying to resolve the parameter type for. | |
* @return The resolved real class. | |
*/ | |
public static Class<?> resolveParameterType(Class<?> c, int index, Class<?> oc) { | |
// We need to make up a mapping of type names. | |
Map<Type,Type> typeMap = new HashMap<>(); | |
while (c != oc.getSuperclass()) { | |
extractTypes(typeMap, oc); | |
oc = oc.getSuperclass(); | |
} | |
Type gsc = oc.getGenericSuperclass(); | |
// Not actually a parameterized type. | |
if (! (gsc instanceof ParameterizedType)) | |
return Object.class; | |
ParameterizedType opt = (ParameterizedType)gsc; | |
Type actualType = opt.getActualTypeArguments()[index]; | |
if (typeMap.containsKey(actualType)) | |
actualType = typeMap.get(actualType); | |
if (actualType instanceof Class) { | |
return (Class<?>)actualType; | |
} else if (actualType instanceof GenericArrayType) { | |
Class<?> cmpntType = (Class<?>)((GenericArrayType)actualType).getGenericComponentType(); | |
return Array.newInstance(cmpntType, 0).getClass(); | |
} else if (actualType instanceof TypeVariable) { | |
TypeVariable<?> typeVariable = (TypeVariable<?>)actualType; | |
List<Class<?>> nestedOuterTypes = new LinkedList<>(); | |
for (Class<?> ec = oc.getEnclosingClass(); ec != null; ec = ec.getEnclosingClass()) { | |
try { | |
Class<?> outerClass = oc.getClass(); | |
nestedOuterTypes.add(outerClass); | |
Map<Type,Type> outerTypeMap = new HashMap<>(); | |
extractTypes(outerTypeMap, outerClass); | |
for (Map.Entry<Type,Type> entry : outerTypeMap.entrySet()) { | |
Type key = entry.getKey(), value = entry.getValue(); | |
if (key instanceof TypeVariable) { | |
TypeVariable<?> keyType = (TypeVariable<?>)key; | |
if (keyType.getName().equals(typeVariable.getName()) && isInnerClass(keyType.getGenericDeclaration(), typeVariable.getGenericDeclaration())) { | |
if (value instanceof Class) | |
return (Class<?>)value; | |
typeVariable = (TypeVariable<?>)entry.getValue(); | |
} | |
} | |
} | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
throw new FormattedRuntimeException("Could not resolve type: {0}", actualType); | |
} else { | |
throw new FormattedRuntimeException("Invalid type found in resolveParameterType: {0}", actualType); | |
} | |
} | |
private static boolean isInnerClass(GenericDeclaration od, GenericDeclaration id) { | |
if (od instanceof Class && id instanceof Class) { | |
Class<?> oc = (Class<?>)od; | |
Class<?> ic = (Class<?>)id; | |
while ((ic = ic.getEnclosingClass()) != null) | |
if (ic == oc) | |
return true; | |
} | |
return false; | |
} | |
private static void extractTypes(Map<Type,Type> typeMap, Class<?> c) { | |
Type gs = c.getGenericSuperclass(); | |
if (gs instanceof ParameterizedType) { | |
ParameterizedType pt = (ParameterizedType)gs; | |
Type[] typeParameters = ((Class<?>)pt.getRawType()).getTypeParameters(); | |
Type[] actualTypeArguments = pt.getActualTypeArguments(); | |
for (int i = 0; i < typeParameters.length; i++) { | |
if (typeMap.containsKey(actualTypeArguments[i])) | |
actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]); | |
typeMap.put(typeParameters[i], actualTypeArguments[i]); | |
} | |
} | |
} | |
/** | |
* Returns <jk>true</jk> if the specified argument types are valid for the specified parameter types. | |
* | |
* @param paramTypes The parameters types specified on a method. | |
* @param argTypes The class types of the arguments being passed to the method. | |
* @return <jk>true</jk> if the arguments match the parameters. | |
*/ | |
public static boolean argsMatch(Class<?>[] paramTypes, Class<?>[] argTypes) { | |
if (paramTypes.length == argTypes.length) { | |
for (int i = 0; i < paramTypes.length; i++) | |
if (! getClassInfo(paramTypes[i]).isParentOf(argTypes[i])) | |
return false; | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Returns a number representing the number of arguments that match the specified parameters. | |
* | |
* @param paramTypes The parameters types specified on a method. | |
* @param argTypes The class types of the arguments being passed to the method. | |
* @return The number of matching arguments, or <code>-1</code> a parameter was found that isn't in the list of args. | |
*/ | |
public static int fuzzyArgsMatch(Class<?>[] paramTypes, Class<?>... argTypes) { | |
int matches = 0; | |
outer: for (Class<?> p : paramTypes) { | |
p = getClassInfo(p).getWrapperIfPrimitive(); | |
for (Class<?> a : argTypes) { | |
if (getClassInfo(p).isParentOf(a)) { | |
matches++; | |
continue outer; | |
} | |
} | |
return -1; | |
} | |
return matches; | |
} | |
/** | |
* Returns the class types for the specified arguments. | |
* | |
* @param args The objects we're getting the classes of. | |
* @return The classes of the arguments. | |
*/ | |
public static Class<?>[] getClasses(Object...args) { | |
Class<?>[] pt = new Class<?>[args.length]; | |
for (int i = 0; i < args.length; i++) | |
pt[i] = args[i] == null ? null : args[i].getClass(); | |
return pt; | |
} | |
/** | |
* Creates an instance of the specified class. | |
* | |
* @param c | |
* The class to cast to. | |
* @param c2 | |
* The class to instantiate. | |
* Can also be an instance of the class. | |
* @return | |
* The new class instance, or <jk>null</jk> if the class was <jk>null</jk> or is abstract or an interface. | |
* @throws | |
* RuntimeException if constructor could not be found or called. | |
*/ | |
public static <T> T newInstance(Class<T> c, Object c2) { | |
return newInstanceFromOuter(null, c, c2, false); | |
} | |
/** | |
* Creates an instance of the specified class. | |
* | |
* @param c | |
* The class to cast to. | |
* @param c2 | |
* The class to instantiate. | |
* Can also be an instance of the class. | |
* @param fuzzyArgs | |
* Use fuzzy constructor arg matching. | |
* <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. | |
* <br>No-arg constructors are also used if no other constructors are found. | |
* @param args | |
* The arguments to pass to the constructor. | |
* @return | |
* The new class instance, or <jk>null</jk> if the class was <jk>null</jk> or is abstract or an interface. | |
* @throws | |
* RuntimeException if constructor could not be found or called. | |
*/ | |
public static <T> T newInstance(Class<T> c, Object c2, boolean fuzzyArgs, Object...args) { | |
return newInstanceFromOuter(null, c, c2, fuzzyArgs, args); | |
} | |
/** | |
* Creates an instance of the specified class from within the context of another object. | |
* | |
* @param outer | |
* The outer object. | |
* Can be <jk>null</jk>. | |
* @param c | |
* The class to cast to. | |
* @param c2 | |
* The class to instantiate. | |
* Can also be an instance of the class. | |
* @param fuzzyArgs | |
* Use fuzzy constructor arg matching. | |
* <br>When <jk>true</jk>, constructor args can be in any order and extra args are ignored. | |
* <br>No-arg constructors are also used if no other constructors are found. | |
* @param args | |
* The arguments to pass to the constructor. | |
* @return | |
* The new class instance, or <jk>null</jk> if the class was <jk>null</jk> or is abstract or an interface. | |
* @throws | |
* RuntimeException if constructor could not be found or called. | |
*/ | |
@SuppressWarnings("unchecked") | |
public static <T> T newInstanceFromOuter(Object outer, Class<T> c, Object c2, boolean fuzzyArgs, Object...args) { | |
if (c2 == null) | |
return null; | |
if (c2 instanceof Class) { | |
try { | |
Class<?> c3 = (Class<?>)c2; | |
if (c3.isInterface() || isAbstract(c3)) | |
return null; | |
ClassInfo c3i = ClassInfo.lookup(c3); | |
// First look for an exact match. | |
Constructor<?> con = c3i.findPublicConstructor(false, args); | |
if (con != null) | |
return (T)con.newInstance(args); | |
// Next look for an exact match including the outer. | |
if (outer != null) { | |
args = new AList<>().append(outer).appendAll(args).toArray(); | |
con = c3i.findPublicConstructor(false, args); | |
if (con != null) | |
return (T)con.newInstance(args); | |
} | |
// Finally use fuzzy matching. | |
if (fuzzyArgs) { | |
con = c3i.findPublicConstructor(true, args); | |
if (con != null) | |
return (T)con.newInstance(getMatchingArgs(con.getParameterTypes(), args)); | |
} | |
throw new FormattedRuntimeException("Could not instantiate class {0}/{1}. Constructor not found.", c.getName(), c2); | |
} catch (Exception e) { | |
throw new FormattedRuntimeException(e, "Could not instantiate class {0}", c.getName()); | |
} | |
} else if (getClassInfo(c).isParentOf(c2.getClass())) { | |
return (T)c2; | |
} else { | |
throw new FormattedRuntimeException("Object of type {0} found but was expecting {1}.", c2.getClass(), c.getClass()); | |
} | |
} | |
/** | |
* Matches arguments to a list of parameter types. | |
* | |
* <p> | |
* Extra parameters are ignored. | |
* <br>Missing parameters are left null. | |
* | |
* @param paramTypes The parameter types. | |
* @param args The arguments to match to the parameter types. | |
* @return | |
* An array of parameters. | |
*/ | |
public static Object[] getMatchingArgs(Class<?>[] paramTypes, Object... args) { | |
Object[] params = new Object[paramTypes.length]; | |
for (int i = 0; i < paramTypes.length; i++) { | |
ClassInfo pt = getClassInfo(paramTypes[i]).getWrapperInfoIfPrimitive(); | |
for (int j = 0; j < args.length; j++) { | |
if (pt.isParentOf(args[j].getClass())) { | |
params[i] = args[j]; | |
break; | |
} | |
} | |
} | |
return params; | |
} | |
/** | |
* Returns a list of all the parent classes of the specified class including the class itself. | |
* | |
* @param c The class to retrieve the parent classes. | |
* @param parentFirst In parent-to-child order, otherwise child-to-parent. | |
* @param includeInterfaces Include interfaces. | |
* @return An iterator of parent classes in the class hierarchy. | |
*/ | |
public static Iterator<Class<?>> getParentClasses(final Class<?> c, boolean parentFirst, boolean includeInterfaces) { | |
List<Class<?>> l = getParentClasses(new ArrayList<Class<?>>(), c, parentFirst, includeInterfaces); | |
return l.iterator(); | |
} | |
private static List<Class<?>> getParentClasses(List<Class<?>> l, Class<?> c, boolean parentFirst, boolean includeInterfaces) { | |
if (parentFirst) { | |
if (includeInterfaces) | |
for (Class<?> i : c.getInterfaces()) | |
l.add(i); | |
if (c.getSuperclass() != Object.class && c.getSuperclass() != null) | |
getParentClasses(l, c.getSuperclass(), parentFirst, includeInterfaces); | |
l.add(c); | |
} else { | |
l.add(c); | |
if (c.getSuperclass() != Object.class && c.getSuperclass() != null) | |
getParentClasses(l, c.getSuperclass(), parentFirst, includeInterfaces); | |
if (includeInterfaces) | |
for (Class<?> i : c.getInterfaces()) | |
l.add(i); | |
} | |
return l; | |
} | |
/** | |
* Returns a readable representation of the specified method. | |
* | |
* <p> | |
* The format of the string is <js>"full-qualified-class.method-name(parameter-simple-class-names)"</js>. | |
* | |
* @param m The method to stringify. | |
* @return The stringified method. | |
*/ | |
public static String toString(Method m) { | |
StringBuilder sb = new StringBuilder(m.getDeclaringClass().getName() + "." + m.getName() + "("); | |
for (int i = 0; i < m.getParameterTypes().length; i++) { | |
if (i > 0) | |
sb.append(","); | |
sb.append(m.getParameterTypes()[i].getSimpleName()); | |
} | |
sb.append(")"); | |
return sb.toString(); | |
} | |
/** | |
* Returns a readable representation of the specified field. | |
* | |
* <p> | |
* The format of the string is <js>"full-qualified-class.field-name"</js>. | |
* | |
* @param f The field to stringify. | |
* @return The stringified field. | |
*/ | |
public static String toString(Field f) { | |
return f.getDeclaringClass().getName() + "." + f.getName(); | |
} | |
/** | |
* Constructs a new instance of the specified class from the specified string. | |
* | |
* <p> | |
* Class must be one of the following: | |
* <ul> | |
* <li>Have a public constructor that takes in a single <code>String</code> argument. | |
* <li>Have a static <code>fromString(String)</code> (or related) method. | |
* <br>See {@link ClassInfo#findPublicFromStringMethod()} for the list of possible static method names. | |
* <li>Be an <code>enum</code>. | |
* </ul> | |
* | |
* @param c The class. | |
* @param s The string to create the instance from. | |
* @return A new object instance, or <jk>null</jk> if a method for converting the string to an object could not be found. | |
*/ | |
public static <T> T fromString(Class<T> c, String s) { | |
Transform<String,T> t = TransformCache.get(String.class, c); | |
return t == null ? null : t.transform(s); | |
} | |
/** | |
* Converts an object to a string. | |
* | |
* <p> | |
* Normally, this is just going to call <code>toString()</code> on the object. | |
* However, the {@link Locale} and {@link TimeZone} objects are treated special so that the returned value | |
* works with the {@link #fromString(Class, String)} method. | |
* | |
* @param o The object to convert to a string. | |
* @return The stringified object, or <jk>null</jk> if the object was <jk>null</jk>. | |
*/ | |
@SuppressWarnings({ "unchecked" }) | |
public static String toString(Object o) { | |
if (o == null) | |
return null; | |
Transform<Object,String> t = (Transform<Object,String>)TransformCache.get(o.getClass(), String.class); | |
return t == null ? o.toString() : t.transform(o); | |
} | |
/** | |
* Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. | |
* | |
* @param x The constructor. | |
* @param ignoreExceptions Ignore {@link SecurityException SecurityExceptions} and just return <jk>false</jk> if thrown. | |
* @return <jk>true</jk> if call was successful. | |
*/ | |
public static boolean setAccessible(Constructor<?> x, boolean ignoreExceptions) { | |
try { | |
if (! (x == null || x.isAccessible())) | |
x.setAccessible(true); | |
return true; | |
} catch (SecurityException e) { | |
if (ignoreExceptions) | |
return false; | |
throw new ClassMetaRuntimeException("Could not set accessibility to true on constructor ''{0}''", x); | |
} | |
} | |
/** | |
* Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. | |
* | |
* @param x The method. | |
* @param ignoreExceptions Ignore {@link SecurityException SecurityExceptions} and just return <jk>false</jk> if thrown. | |
* @return <jk>true</jk> if call was successful. | |
*/ | |
public static boolean setAccessible(Method x, boolean ignoreExceptions) { | |
try { | |
if (! (x == null || x.isAccessible())) | |
x.setAccessible(true); | |
return true; | |
} catch (SecurityException e) { | |
if (ignoreExceptions) | |
return false; | |
throw new ClassMetaRuntimeException("Could not set accessibility to true on method ''{0}''", x); | |
} | |
} | |
/** | |
* Attempts to call <code>x.setAccessible(<jk>true</jk>)</code> and quietly ignores security exceptions. | |
* | |
* @param x The field. | |
* @param ignoreExceptions Ignore {@link SecurityException SecurityExceptions} and just return <jk>false</jk> if thrown. | |
* @return <jk>true</jk> if call was successful. | |
*/ | |
public static boolean setAccessible(Field x, boolean ignoreExceptions) { | |
try { | |
if (! (x == null || x.isAccessible())) | |
x.setAccessible(true); | |
return true; | |
} catch (SecurityException e) { | |
if (ignoreExceptions) | |
return false; | |
throw new ClassMetaRuntimeException("Could not set accessibility to true on field ''{0}''", x); | |
} | |
} | |
/** | |
* Returns the simple name of a class. | |
* | |
* <p> | |
* Similar to {@link Class#getSimpleName()}, but includes the simple name of an enclosing or declaring class. | |
* | |
* @param c The class to get the simple name on. | |
* @return The simple name of a class. | |
*/ | |
public static String getSimpleName(Class<?> c) { | |
if (c.isLocalClass()) | |
return getSimpleName(c.getEnclosingClass()) + '.' + c.getSimpleName(); | |
if (c.isMemberClass()) | |
return getSimpleName(c.getDeclaringClass()) + '.' + c.getSimpleName(); | |
return c.getSimpleName(); | |
} | |
/** | |
* Returns the simple name of a class. | |
* | |
* <p> | |
* Similar to {@link Class#getSimpleName()}, but includes the simple name of an enclosing or declaring class. | |
* | |
* @param t The class to get the simple name on. | |
* @return The simple name of a class. | |
*/ | |
public static String getSimpleName(Type t) { | |
if (t instanceof Class) | |
return getSimpleName((Class<?>)t); | |
if (t instanceof ParameterizedType) { | |
StringBuilder sb = new StringBuilder(); | |
ParameterizedType pt = (ParameterizedType)t; | |
sb.append(getSimpleName(pt.getRawType())); | |
sb.append("<"); | |
boolean first = true; | |
for (Type t2 : pt.getActualTypeArguments()) { | |
if (! first) | |
sb.append(','); | |
first = false; | |
sb.append(getSimpleName(t2)); | |
} | |
sb.append(">"); | |
return sb.toString(); | |
} | |
return null; | |
} | |
/** | |
* Returns the specified annotation only if it's been declared on the specified class. | |
* | |
* <p> | |
* More efficient than calling {@link Class#getAnnotation(Class)} since it doesn't recursively look for the class | |
* up the parent chain. | |
* | |
* @param <T> The annotation class type. | |
* @param a The annotation class. | |
* @param t The annotated class. | |
* @return The annotation, or <jk>null</jk> if not found. | |
*/ | |
@SuppressWarnings("unchecked") | |
public static <T extends Annotation> T getDeclaredAnnotation(Class<T> a, Type t) { | |
Class<?> c = toClass(t); | |
if (c != null) | |
for (Annotation a2 : c.getDeclaredAnnotations()) | |
if (a2.annotationType() == a) | |
return (T)a2; | |
return null; | |
} | |
/** | |
* Same as getAnnotations(Class, Type) except returns the annotations as a map with the keys being the | |
* class on which the annotation was found. | |
* | |
* <p> | |
* Results are ordered child-to-parent. | |
* | |
* @param <T> The annotation class type. | |
* @param a The annotation class type. | |
* @param t The class being searched. | |
* @return The found matches, or an empty map if annotation was not found. | |
*/ | |
public static <T extends Annotation> LinkedHashMap<Class<?>,T> getAnnotationsMap(Class<T> a, Type t) { | |
LinkedHashMap<Class<?>,T> m = new LinkedHashMap<>(); | |
findAnnotationsMap(a, t, m); | |
return m; | |
} | |
/** | |
* Same as {@link #getAnnotationsMap(Class, Type)} except returns results in parent-to-child order. | |
* | |
* @param <T> The annotation class type. | |
* @param a The annotation class type. | |
* @param t The class being searched. | |
* @return The found matches, or an empty map if annotation was not found. | |
*/ | |
public static <T extends Annotation> LinkedHashMap<Class<?>,T> getAnnotationsMapParentFirst(Class<T> a, Type t) { | |
return CollectionUtils.reverse(getAnnotationsMap(a, t)); | |
} | |
private static <T extends Annotation> void findAnnotationsMap(Class<T> a, Type t, Map<Class<?>,T> m) { | |
Class<?> c = toClass(t); | |
if (c != null) { | |
T t2 = getDeclaredAnnotation(a, c); | |
if (t2 != null) | |
m.put(c, t2); | |
findAnnotationsMap(a, c.getSuperclass(), m); | |
for (Class<?> c2 : c.getInterfaces()) | |
findAnnotationsMap(a, c2, m); | |
} | |
} | |
/** | |
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified | |
* list. | |
* | |
* @param a The annotation. | |
* @param t The class. | |
* @param l The list of annotations. | |
*/ | |
public static <T extends Annotation> void appendAnnotations(Class<T> a, Type t, List<T> l) { | |
Class<?> c = toClass(t); | |
if (c != null) { | |
addIfNotNull(l, getDeclaredAnnotation(a, c)); | |
if (c.getPackage() != null) | |
addIfNotNull(l, c.getPackage().getAnnotation(a)); | |
appendAnnotations(a, c.getSuperclass(), l); | |
for (Class<?> c2 : c.getInterfaces()) | |
appendAnnotations(a, c2, l); | |
} | |
} | |
/** | |
* Returns the specified type as a <code>Class</code>. | |
* | |
* <p> | |
* If it's already a <code>Class</code>, it just does a cast. | |
* <br>If it's a <code>ParameterizedType</code>, it returns the raw type. | |
* | |
* @param t The type to convert. | |
* @return The type converted to a <code>Class</code>, or <jk>null</jk> if it could not be converted. | |
*/ | |
public static Class<?> toClass(Type t) { | |
if (t instanceof Class) | |
return (Class<?>)t; | |
if (t instanceof ParameterizedType) { | |
ParameterizedType pt = (ParameterizedType)t; | |
// The raw type should always be a class (right?) | |
return (Class<?>)pt.getRawType(); | |
} | |
return null; | |
} | |
/** | |
* Similar to {@link Class#getResourceAsStream(String)} except looks up the parent hierarchy for the existence of | |
* the specified resource. | |
* | |
* @param c The class to return the resource on. | |
* @param name The resource name. | |
* @return An input stream on the specified resource, or <jk>null</jk> if the resource could not be found. | |
*/ | |
public static InputStream getResource(Class<?> c, String name) { | |
if (name == null) | |
return null; | |
while (c != null) { | |
InputStream is = c.getResourceAsStream(name); | |
if (is != null) | |
return is; | |
c = c.getSuperclass(); | |
} | |
return null; | |
} | |
} |