blob: fa46631850b25fa370af76cb4d6a1ca86b244778 [file] [log] [blame]
// ***************************************************************************************************************************
// * 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.reflection;
import static org.apache.juneau.internal.ClassFlags.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.utils.*;
/**
* Utility class for introspecting information about a class.
*/
@BeanIgnore
public final class ClassInfo {
private final Type type;
private final Class<?> c;
private Map<Class<?>,Optional<Annotation>> annotationMap;
private Map<Class<?>,List<?>> annotationsMap;
private Map<Class<?>,List<?>> annotationsPfMap;
private Optional<ClassInfo> parent;
private ClassInfo[] interfaces;
private List<FieldInfo> allFields, allFieldsPf, declaredFields;
private List<MethodInfo> allMethods, allMethodsPf, declaredMethods, publicMethods;
private List<ConstructorInfo> constructors;
private List<ClassInfo> parentClasses, parentClassesAndInterfaces;
private static final Map<Type,ClassInfo> CACHE = new ConcurrentHashMap<>();
/**
* Constructor.
*
* @param t The class type.
*/
public ClassInfo(Type t) {
this.type = t;
this.c = ClassUtils.toClass(t);
}
/**
* Same as using the constructor, but returns <jk>null</jk> if the type is <jk>null</jk>.
*
* @param t The class type.
* @return The constructed class info.
*/
public static ClassInfo of(Type t) {
if (t == null)
return null;
return new ClassInfo(t);
}
/**
* Same as using the constructor, but operates on an object instance.
*
* @param o The class instance.
* @return The constructed class info.
*/
public static ClassInfo of(Object o) {
if (o == null)
return null;
return new ClassInfo(o.getClass());
}
/**
* Returns the cached instance of the specified type.
*
* @param t The class type.
* @return The cached class info, or <jk>null</jk> if the type is <jk>null</jk>.
*/
public synchronized static ClassInfo lookup(Type t) {
if (t == null)
return null;
ClassInfo ci = CACHE.get(t);
if (ci == null) {
ci = of(t);
CACHE.put(t, ci);
}
return ci;
}
/**
* Returns the wrapped class.
*
* @return The wrapped class.
*/
public Type innerType() {
return type;
}
/**
* Returns the wrapped class.
*
* @return The wrapped class or <jk>null</jk> if it's not a class.
*/
public Class<?> inner() {
return c;
}
/**
* Returns the parent class info.
*
* @return The parent class info, or <jk>null</jk> if the class has no parent.
*/
public synchronized ClassInfo getParent() {
if (parent == null)
parent = Optional.ofNullable(c == null ? null : of(c.getSuperclass()));
return parent.isPresent() ? parent.get() : null;
}
/**
* Returns the interfaces info.
*
* @return The implemented interfaces info, or an empty array if the class has no interfaces.
*/
public synchronized ClassInfo[] getInterfaces() {
if (interfaces == null) {
interfaces = new ClassInfo[c == null ? 0 : c.getInterfaces().length];
for (int i = 0; i < interfaces.length; i++)
interfaces[i] = ClassInfo.of(c.getInterfaces()[i]);
}
return interfaces;
}
//-----------------------------------------------------------------------------------------------------------------
// Classes
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns an iterable over this class and all parent classes in child-to-parent order.
*
* <p>
* Does not include interfaces.
*
* @return An iterable over this class and all parent classes in child-to-parent order.
*/
public Iterable<ClassInfo> getParentClasses() {
if (parentClasses == null)
parentClasses = Collections.unmodifiableList(findParentClasses(new ArrayList<>(), c));
return parentClasses;
}
/**
* Returns an iterable over this class and all parent classes in parent-to-child order.
*
* <p>
* Does not include interfaces.
*
* @return An iterable over this class and all parent classes in parent-to-child order.
*/
public Iterable<ClassInfo> getParentClassesParentFirst() {
if (parentClasses == null)
parentClasses = Collections.unmodifiableList(findParentClasses(new ArrayList<>(), c));
return ReverseIterable.of(parentClasses);
}
/**
* Returns an iterable over this class and all parent classes and interfaces in child-to-parent order.
*
* @return An iterable over this class and all parent classes and interfaces in child-to-parent order.
*/
public Iterable<ClassInfo> getParentClassesAndInterfaces() {
if (parentClassesAndInterfaces == null)
parentClassesAndInterfaces = Collections.unmodifiableList(findParentClassesAndInterfaces(new ArrayList<>(), c));
return parentClassesAndInterfaces;
}
/**
* Returns an iterable over this class and all parent classes and interfaces in parent-to-child order.
*
* @return An iterable over this class and all parent classes and interfaces in parent-to-child order.
*/
public Iterable<ClassInfo> getParentClassesAndInterfacesParentFirst() {
if (parentClassesAndInterfaces == null)
parentClassesAndInterfaces = Collections.unmodifiableList(findParentClassesAndInterfaces(new ArrayList<>(), c));
return ReverseIterable.of(parentClassesAndInterfaces);
}
private static List<ClassInfo> findParentClasses(List<ClassInfo> l, Class<?> c) {
l.add(ClassInfo.lookup(c));
if (c.getSuperclass() != Object.class && c.getSuperclass() != null)
findParentClasses(l, c.getSuperclass());
return l;
}
private static List<ClassInfo> findParentClassesAndInterfaces(List<ClassInfo> l, Class<?> c) {
l.add(ClassInfo.lookup(c));
if (c.getSuperclass() != Object.class && c.getSuperclass() != null)
findParentClassesAndInterfaces(l, c.getSuperclass());
for (Class<?> i : c.getInterfaces())
l.add(ClassInfo.lookup(i));
return l;
}
//-----------------------------------------------------------------------------------------------------------------
// Methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all declared methods on this class and all parent classes in child-to-parent order.
*
* <p>
* Methods are sorted alphabetically per class before being aggregated.
*
* @return All declared methods on this class and all parent classes in child-to-parent order.
*/
public Iterable<MethodInfo> getAllMethods() {
if (allMethods == null)
allMethods = Collections.unmodifiableList(findAllMethods());
return allMethods;
}
/**
* Returns all declared methods on this class and all parent classes in parent-to-child order.
*
* <p>
* Methods are sorted alphabetically per class before being aggregated.
*
* @return All declared methods on this class and all parent classes in parent-to-child order.
*/
public Iterable<MethodInfo> getAllMethodsParentFirst() {
if (allMethodsPf == null)
allMethodsPf = Collections.unmodifiableList(findAllMethodsParentFirst());
return allMethodsPf;
}
/**
* Returns all methods declared on this class.
*
* @return All methods declared on this class in alphabetical order.
*/
public Iterable<MethodInfo> getDeclaredMethods() {
if (declaredMethods == null)
declaredMethods = Collections.unmodifiableList(findDeclaredMethods());
return declaredMethods;
}
/**
* Returns all public methods on this class.
*
* <p>
* Returns the methods (in the same order) as the call to {@link Class#getMethods()}.
*
* @return All public methods on this class.
*/
public Iterable<MethodInfo> getPublicMethods() {
if (publicMethods == null)
publicMethods = Collections.unmodifiableList(findPublicMethods());
return publicMethods;
}
private List<MethodInfo> findAllMethods() {
List<MethodInfo> l = new ArrayList<>();
for (ClassInfo c : getParentClassesAndInterfaces())
for (MethodInfo m : c.getDeclaredMethods())
l.add(m);
return l;
}
private List<MethodInfo> findAllMethodsParentFirst() {
List<MethodInfo> l = new ArrayList<>();
for (ClassInfo c : getParentClassesAndInterfacesParentFirst())
for (MethodInfo m : c.getDeclaredMethods())
l.add(m);
return l;
}
private List<MethodInfo> findDeclaredMethods() {
List<MethodInfo> l = new ArrayList<>(c.getDeclaredMethods().length);
for (Method m : sort(c.getDeclaredMethods()))
l.add(MethodInfo.of(this, m));
return l;
}
private List<MethodInfo> findPublicMethods() {
List<MethodInfo> l = new ArrayList<>(c.getMethods().length);
for (Method m : c.getMethods())
l.add(MethodInfo.of(this, m));
return l;
}
private static Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
@Override
public int compare(Method o1, Method o2) {
int i = o1.getName().compareTo(o2.getName());
if (i == 0) {
i = o1.getParameterTypes().length - o2.getParameterTypes().length;
if (i == 0) {
for (int j = 0; j < o1.getParameterTypes().length && i == 0; j++) {
i = o1.getParameterTypes()[j].getName().compareTo(o2.getParameterTypes()[j].getName());
}
}
}
return i;
}
};
private static Method[] sort(Method[] m) {
Arrays.sort(m, METHOD_COMPARATOR);
return m;
}
//-----------------------------------------------------------------------------------------------------------------
// Special methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Finds the public static "fromString" method on this class.
*
* <p>
* Looks for the following method names:
* <ul>
* <li><code>fromString</code>
* <li><code>fromValue</code>
* <li><code>valueOf</code>
* <li><code>parse</code>
* <li><code>parseString</code>
* <li><code>forName</code>
* <li><code>forString</code>
* </ul>
*
* @return The static method, or <jk>null</jk> if it couldn't be found.
*/
public MethodInfo findPublicFromStringMethod() {
for (String methodName : new String[]{"create","fromString","fromValue","valueOf","parse","parseString","forName","forString"})
for (MethodInfo m : getPublicMethods())
if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && m.hasName(methodName) && m.hasReturnType(c) && m.hasArgs(String.class))
return m;
return null;
}
/**
* Find the public static creator method on this class.
*
* @param ic The argument type.
* @param name The method name.
* @return The static method, or <jk>null</jk> if it couldn't be found.
*/
public MethodInfo findPublicStaticCreateMethod(Class<?> ic, String name) {
for (MethodInfo m : getPublicMethods())
if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && m.hasName(name) && m.hasReturnType(c) && m.hasArgs(ic))
return m;
return null;
}
/**
* Returns the <code>public static Builder create()</code> method on this class.
*
* @return The <code>public static Builder create()</code> method on this class, or <jk>null</jk> if it doesn't exist.
*/
public MethodInfo findBuilderCreateMethod() {
for (MethodInfo m : getDeclaredMethods())
if (m.isAll(PUBLIC, STATIC) && m.hasName("create") && ! m.hasReturnType(Void.class))
return m;
return null;
}
/**
* Returns the <code>T build()</code> method on this class.
*
* @return The <code>T build()</code> method on this class, or <jk>null</jk> if it doesn't exist.
*/
public MethodInfo findCreatePojoMethod() {
for (MethodInfo m : getDeclaredMethods())
if (m.isAll(NOT_STATIC) && m.hasName("build") && ! m.hasReturnType(Void.class))
return m;
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Constructors
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all the public constructors defined on this class.
*
* @return All public constructors defined on this class.
*/
public Iterable<ConstructorInfo> getPublicConstructors() {
if (constructors == null)
constructors = findConstructors();
return constructors;
}
private List<ConstructorInfo> findConstructors() {
List<ConstructorInfo> l = new ArrayList<>(c.getConstructors().length);
for (Constructor<?> cc : c.getConstructors())
l.add(ConstructorInfo.of(this, cc));
return Collections.unmodifiableList(l);
}
//-----------------------------------------------------------------------------------------------------------------
// Fields
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all field on this class and all parent classes in child-to-parent order.
*
* <p>
* Fields are sorted alphabetically per class before being aggregated.
*
* @return All declared methods on this class and all parent classes in child-to-parent order.
*/
public Iterable<FieldInfo> getAllFields() {
if (allFields == null)
allFields = findAllFields();
return allFields;
}
/**
* Returns all field on this class and all parent classes in parent-to-child order.
*
* <p>
* Fields are sorted alphabetically per class before being aggregated.
*
* @return All declared methods on this class and all parent classes in parent-to-child order.
*/
public Iterable<FieldInfo> getAllFieldsParentFirst() {
if (allFieldsPf == null)
allFieldsPf = findAllFieldsParentFirst();
return allFieldsPf;
}
/**
* Returns all fields declared on this class.
*
* @return All fields declared on this class in alphabetical order.
*/
public Iterable<FieldInfo> getDeclaredFields() {
if (declaredFields == null)
declaredFields = Collections.unmodifiableList(findDeclaredFields());
return declaredFields;
}
private List<FieldInfo> findAllFieldsParentFirst() {
List<FieldInfo> l = new ArrayList<>();
for (ClassInfo c : getParentClassesParentFirst())
for (FieldInfo f : c.getDeclaredFields())
l.add(f);
return l;
}
private List<FieldInfo> findAllFields() {
List<FieldInfo> l = new ArrayList<>();
for (ClassInfo c : getParentClasses())
for (FieldInfo f : c.getDeclaredFields())
l.add(f);
return l;
}
private List<FieldInfo> findDeclaredFields() {
List<FieldInfo> l = new ArrayList<>(c.getDeclaredFields().length);
for (Field f : sort(c.getDeclaredFields()))
l.add(FieldInfo.of(this, f));
return l;
}
private static Comparator<Field> FIELD_COMPARATOR = new Comparator<Field>() {
@Override
public int compare(Field o1, Field o2) {
return o1.getName().compareTo(o2.getName());
}
};
private static Field[] sort(Field[] m) {
Arrays.sort(m, FIELD_COMPARATOR);
return m;
}
//-----------------------------------------------------------------------------------------------------------------
// Constructors
//-----------------------------------------------------------------------------------------------------------------
/**
* Locates the no-arg constructor for this class.
*
* <p>
* Constructor must match the visibility requirements specified by parameter 'v'.
* If class is abstract, always returns <jk>null</jk>.
* Note that this also returns the 1-arg constructor for non-static member classes.
*
* @param v The minimum visibility.
* @return The constructor, or <jk>null</jk> if no no-arg constructor exists with the required visibility.
*/
public ConstructorInfo findNoArgConstructor(Visibility v) {
if (isAbstract())
return null;
boolean isMemberClass = isMemberClass() && ! isStatic();
for (ConstructorInfo cc : getPublicConstructors())
if (cc.hasNumArgs(isMemberClass ? 1 : 0) && cc.isVisible(v) && cc.isNotDeprecated())
return cc.transform(v);
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Annotations
//-----------------------------------------------------------------------------------------------------------------
/**
* Finds the annotation of the specified type defined on this method.
*
* <p>
* If the annotation cannot be found on the immediate method, searches methods with the same
* signature on the parent classes or interfaces.
* <br>The search is performed in child-to-parent order.
*
* <p>
* If still not found, searches for the annotation on the return type of the method.
*
* @param a
* The annotation to search for.
* @return
* The annotation if found, or <jk>null</jk> if not.
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getAnnotation(Class<T> a) {
Optional<Annotation> o = annotationMap().get(a);
if (o == null) {
o = Optional.ofNullable(findAnnotation(a));
annotationMap().put(a, o);
}
return o.isPresent() ? (T)o.get() : null;
}
/**
* Returns <jk>true</jk> if this method has the specified annotation.
*
* @param a
* The annotation to search for.
* @return
* The <jk>true</jk> if annotation if found.
*/
public boolean hasAnnotation(Class<? extends Annotation> a) {
return getAnnotation(a) != null;
}
/**
* Returns all annotations of the specified type defined on the specified method.
*
* <p>
* Searches all methods with the same signature on the parent classes or interfaces
* and the return type on the method.
*
* @param a
* The annotation to search for.
* @return
* A list of all matching annotations found in child-to-parent order, or an empty list if none found.
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> List<T> getAnnotations(Class<T> a) {
List<T> l = (List<T>)annotationsMap().get(a);
if (l == null) {
l = Collections.unmodifiableList(findAnnotations(a));
annotationsMap().put(a, l);
}
return l;
}
/**
* Identical to {@link #getAnnotations(Class)} but returns the list in reverse (parent-to-child) order.
*
* @param a
* The annotation to search for.
* @return
* A list of all matching annotations found in parent-to-child order, or an empty list if none found.
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) {
List<T> l = (List<T>)annotationsPfMap().get(a);
if (l == null) {
l = new ArrayList<>(getAnnotations(a));
Collections.reverse(l);
l = Collections.unmodifiableList(l);
annotationsPfMap().put(a, l);
}
return l;
}
/**
* 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.
* @return The annotation, or <jk>null</jk> if not found.
*/
@SuppressWarnings("unchecked")
public <T extends Annotation> T getDeclaredAnnotation(Class<T> a) {
if (c != null)
for (Annotation a2 : c.getDeclaredAnnotations())
if (a2.annotationType() == a)
return (T)a2;
return null;
}
private <T extends Annotation> T findAnnotation(Class<T> a) {
if (c != null) {
T t2 = getDeclaredAnnotation(a);
if (t2 != null)
return t2;
ClassInfo sci = getParent();
if (sci != null) {
t2 = sci.getAnnotation(a);
if (t2 != null)
return t2;
}
for (ClassInfo c2 : getInterfaces()) {
t2 = c2.getAnnotation(a);
if (t2 != null)
return t2;
}
}
return null;
}
private <T extends Annotation> List<T> findAnnotations(Class<T> a) {
List<T> l = new LinkedList<>();
ClassUtils.appendAnnotations(a, type, l);
return l;
}
private synchronized Map<Class<?>,Optional<Annotation>> annotationMap() {
if (annotationMap == null)
annotationMap = new ConcurrentHashMap<>();
return annotationMap;
}
private synchronized Map<Class<?>,List<?>> annotationsMap() {
if (annotationsMap == null)
annotationsMap = new ConcurrentHashMap<>();
return annotationsMap;
}
private synchronized Map<Class<?>,List<?>> annotationsPfMap() {
if (annotationsPfMap == null)
annotationsPfMap = new ConcurrentHashMap<>();
return annotationsPfMap;
}
//-----------------------------------------------------------------------------------------------------------------
// Characteristics
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns <jk>true</jk> if all specified flags are applicable to this class.
*
* @param flags The flags to test for.
* @return <jk>true</jk> if all specified flags are applicable to this class.
*/
public boolean isAll(ClassFlags...flags) {
for (ClassFlags f : flags) {
switch (f) {
case DEPRECATED:
if (isNotDeprecated())
return false;
break;
case NOT_DEPRECATED:
if (isDeprecated())
return false;
break;
case PUBLIC:
if (isNotPublic())
return false;
break;
case NOT_PUBLIC:
if (isPublic())
return false;
break;
case STATIC:
if (isNotStatic())
return false;
break;
case NOT_STATIC:
if (isStatic())
return false;
break;
case ABSTRACT:
if (isNotAbstract())
return false;
break;
case NOT_ABSTRACT:
if (isAbstract())
return false;
break;
case HAS_ARGS:
case HAS_NO_ARGS:
case TRANSIENT:
case NOT_TRANSIENT:
default:
break;
}
}
return true;
}
/**
* Returns <jk>true</jk> if all specified flags are applicable to this class.
*
* @param flags The flags to test for.
* @return <jk>true</jk> if all specified flags are applicable to this class.
*/
public boolean isAny(ClassFlags...flags) {
for (ClassFlags f : flags) {
switch (f) {
case DEPRECATED:
if (isDeprecated())
return true;
break;
case NOT_DEPRECATED:
if (isNotDeprecated())
return true;
break;
case PUBLIC:
if (isPublic())
return true;
break;
case NOT_PUBLIC:
if (isNotPublic())
return true;
break;
case STATIC:
if (isStatic())
return true;
break;
case NOT_STATIC:
if (isNotStatic())
return true;
break;
case ABSTRACT:
if (isAbstract())
return true;
break;
case NOT_ABSTRACT:
if (isNotAbstract())
return true;
break;
case TRANSIENT:
case NOT_TRANSIENT:
case HAS_ARGS:
case HAS_NO_ARGS:
default:
break;
}
}
return false;
}
/**
* Returns <jk>true</jk> if this class has the {@link Deprecated @Deprecated} annotation on it.
*
* @return <jk>true</jk> if this class has the {@link Deprecated @Deprecated} annotation on it.
*/
public boolean isDeprecated() {
return c.isAnnotationPresent(Deprecated.class);
}
/**
* Returns <jk>true</jk> if this class doesn't have the {@link Deprecated @Deprecated} annotation on it.
*
* @return <jk>true</jk> if this class doesn't have the {@link Deprecated @Deprecated} annotation on it.
*/
public boolean isNotDeprecated() {
return ! c.isAnnotationPresent(Deprecated.class);
}
/**
* Returns <jk>true</jk> if this class is public.
*
* @return <jk>true</jk> if this class is public.
*/
public boolean isPublic() {
return Modifier.isPublic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is not public.
*
* @return <jk>true</jk> if this class is not public.
*/
public boolean isNotPublic() {
return ! Modifier.isPublic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is public.
*
* @return <jk>true</jk> if this class is public.
*/
public boolean isStatic() {
return Modifier.isStatic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is not static.
*
* @return <jk>true</jk> if this class is not static.
*/
public boolean isNotStatic() {
return ! Modifier.isStatic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is abstract.
*
* @return <jk>true</jk> if this class is abstract.
*/
public boolean isAbstract() {
return Modifier.isAbstract(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is not abstract.
*
* @return <jk>true</jk> if this class is not abstract.
*/
public boolean isNotAbstract() {
return ! Modifier.isAbstract(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is a member class.
*
* @return <jk>true</jk> if this class is a member class.
*/
public boolean isMemberClass() {
return c.isMemberClass();
}
/**
* Identifies if the specified visibility matches this constructor.
*
* @param v The visibility to validate against.
* @return <jk>true</jk> if this visibility matches the modifier attribute of this constructor.
*/
public boolean isVisible(Visibility v) {
return v.isVisible(c);
}
/**
* Returns <jk>true</jk> if this is a primitive class.
*
* @return <jk>true</jk> if this is a primitive class.
*/
public boolean isPrimitive() {
return c.isPrimitive();
}
/**
* Returns <jk>true</jk> if this is not a primitive class.
*
* @return <jk>true</jk> if this is not a primitive class.
*/
public boolean isNotPrimitive() {
return ! c.isPrimitive();
}
/**
* Returns the underlying class name.
*
* @return The underlying class name.
*/
public String getName() {
return c.getName();
}
/**
* Returns the simple name of the underlying class.
*
* @return The simple name of the underlying class;
*/
public String getSimpleName() {
return c.getSimpleName();
}
/**
* Finds the public constructor that can take in the specified arguments.
*
* @param args The argument types we want to pass into the constructor.
* @return
* The constructor, or <jk>null</jk> if a public constructor could not be found that takes in the specified
* arguments.
*/
public <T> Constructor<T> findPublicConstructor(Class<?>...args) {
return findPublicConstructor(false, args);
}
/**
* Finds a public constructor with the specified parameters without throwing an exception.
*
* @param fuzzyArgs
* Use fuzzy-arg matching.
* Find a constructor that best matches the specified args.
* @param argTypes
* The argument types in the constructor.
* Can be subtypes of the actual constructor argument types.
* @return The matching constructor, or <jk>null</jk> if constructor could not be found.
*/
public <T> Constructor<T> findPublicConstructor(boolean fuzzyArgs, Class<?>...argTypes) {
return findConstructor(Visibility.PUBLIC, fuzzyArgs, argTypes);
}
/**
* Finds a constructor with the specified parameters without throwing an exception.
*
* @param vis The minimum visibility.
* @param fuzzyArgs
* Use fuzzy-arg matching.
* Find a constructor that best matches the specified args.
* @param argTypes
* The argument types in the constructor.
* Can be subtypes of the actual constructor argument types.
* @return The matching constructor, or <jk>null</jk> if constructor could not be found.
*/
@SuppressWarnings("unchecked")
public <T> Constructor<T> findConstructor(Visibility vis, boolean fuzzyArgs, Class<?>...argTypes) {
ConstructorCacheEntry cce = CONSTRUCTOR_CACHE.get(c);
if (cce != null && ClassUtils.argsMatch(cce.paramTypes, argTypes) && cce.isVisible(vis))
return (Constructor<T>)cce.constructor;
if (fuzzyArgs) {
int bestCount = -1;
Constructor<?> bestMatch = null;
for (Constructor<?> n : c.getDeclaredConstructors()) {
if (vis.isVisible(n)) {
int m = ClassUtils.fuzzyArgsMatch(n.getParameterTypes(), argTypes);
if (m > bestCount) {
bestCount = m;
bestMatch = n;
}
}
}
if (bestCount >= 0)
CONSTRUCTOR_CACHE.put(c, new ConstructorCacheEntry(c, bestMatch));
return (Constructor<T>)bestMatch;
}
final boolean isMemberClass = isMemberClass() && ! isStatic();
for (Constructor<?> n : c.getConstructors()) {
Class<?>[] paramTypes = n.getParameterTypes();
if (isMemberClass)
paramTypes = Arrays.copyOfRange(paramTypes, 1, paramTypes.length);
if (ClassUtils.argsMatch(paramTypes, argTypes) && vis.isVisible(n)) {
CONSTRUCTOR_CACHE.put(c, new ConstructorCacheEntry(c, n));
return (Constructor<T>)n;
}
}
return null;
}
private static final Map<Class<?>,ConstructorCacheEntry> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>();
private static final class ConstructorCacheEntry {
final Constructor<?> constructor;
final Class<?>[] paramTypes;
ConstructorCacheEntry(Class<?> forClass, Constructor<?> constructor) {
this.constructor = constructor;
this.paramTypes = constructor.getParameterTypes();
}
boolean isVisible(Visibility vis) {
return vis.isVisible(constructor);
}
}
/**
* Finds the public constructor that can take in the specified arguments.
*
* @param fuzzyArgs
* Use fuzzy-arg matching.
* Find a constructor that best matches the specified args.
* @param args The arguments we want to pass into the constructor.
* @return
* The constructor, or <jk>null</jk> if a public constructor could not be found that takes in the specified
* arguments.
*/
public <T> Constructor<T> findPublicConstructor(boolean fuzzyArgs, Object...args) {
return findPublicConstructor(fuzzyArgs, ClassUtils.getClasses(args));
}
//-----------------------------------------------------------------------------------------------------------------
// Primitive wrappers.
//-----------------------------------------------------------------------------------------------------------------
private static final Map<Class<?>, Class<?>>
pmap1 = new HashMap<>(),
pmap2 = new HashMap<>();
static {
pmap1.put(boolean.class, Boolean.class);
pmap1.put(byte.class, Byte.class);
pmap1.put(short.class, Short.class);
pmap1.put(char.class, Character.class);
pmap1.put(int.class, Integer.class);
pmap1.put(long.class, Long.class);
pmap1.put(float.class, Float.class);
pmap1.put(double.class, Double.class);
pmap2.put(Boolean.class, boolean.class);
pmap2.put(Byte.class, byte.class);
pmap2.put(Short.class, short.class);
pmap2.put(Character.class, char.class);
pmap2.put(Integer.class, int.class);
pmap2.put(Long.class, long.class);
pmap2.put(Float.class, float.class);
pmap2.put(Double.class, double.class);
}
/**
* Returns <jk>true</jk> if the {@link #getPrimitiveWrapper()} method returns a value.
*
* @return <jk>true</jk> if the {@link #getPrimitiveWrapper()} method returns a value.
*/
public boolean hasPrimitiveWrapper() {
return pmap1.containsKey(c);
}
/**
* If this class is a primitive (e.g. <code><jk>int</jk>.<jk>class</jk></code>) returns it's wrapper class
* (e.g. <code>Integer.<jk>class</jk></code>).
*
* @return The wrapper class, or <jk>null</jk> if class is not a primitive.
*/
public Class<?> getPrimitiveWrapper() {
return pmap1.get(c);
}
/**
* If this class is a primitive wrapper (e.g. <code><jk>Integer</jk>.<jk>class</jk></code>) returns it's
* primitive class (e.g. <code>int.<jk>class</jk></code>).
*
* @return The primitive class, or <jk>null</jk> if class is not a primitive wrapper.
*/
public Class<?> getPrimitiveForWrapper() {
return pmap2.get(c);
}
/**
* If this class is a primitive (e.g. <code><jk>int</jk>.<jk>class</jk></code>) returns it's wrapper class
* (e.g. <code>Integer.<jk>class</jk></code>).
*
* @return The wrapper class if it's primitive, or the same class if class is not a primitive.
*/
public Class<?> getWrapperIfPrimitive() {
if (! c.isPrimitive())
return c;
return pmap1.get(c);
}
/**
* Same as {@link #getWrapperIfPrimitive()} but wraps it in a {@link ClassInfo}.
*
* @return The wrapper class if it's primitive, or the same class if class is not a primitive.
*/
public ClassInfo getWrapperInfoIfPrimitive() {
if (! c.isPrimitive())
return this;
return of(pmap1.get(c));
}
/**
* Returns the default value for this primitive class.
*
* @return The default value, or <jk>null</jk> if this is not a primitive class.
*/
public Object getPrimitiveDefault() {
return primitiveDefaultMap.get(c);
}
private static final Map<Class<?>,Object> primitiveDefaultMap = Collections.unmodifiableMap(
new AMap<Class<?>,Object>()
.append(Boolean.TYPE, false)
.append(Character.TYPE, (char)0)
.append(Short.TYPE, (short)0)
.append(Integer.TYPE, 0)
.append(Long.TYPE, 0l)
.append(Float.TYPE, 0f)
.append(Double.TYPE, 0d)
.append(Byte.TYPE, (byte)0)
.append(Boolean.class, false)
.append(Character.class, (char)0)
.append(Short.class, (short)0)
.append(Integer.class, 0)
.append(Long.class, 0l)
.append(Float.class, 0f)
.append(Double.class, 0d)
.append(Byte.class, (byte)0)
);
/**
* Returns <jk>true</jk> if this class is a parent of <code>child</code>.
*
* @param child The child class.
* @param strict If <jk>true</jk> returns <jk>false</jk> if the classes are the same.
* @return <jk>true</jk> if this class is a parent of <code>child</code>.
*/
public boolean isParentOf(Class<?> child, boolean strict) {
return c.isAssignableFrom(child) && ((!strict) || ! c.equals(child));
}
/**
* Returns <jk>true</jk> if this class is a parent or the same as <code>child</code>.
*
* @param child The child class.
* @return <jk>true</jk> if this class is a parent or the same as <code>child</code>.
*/
public boolean isParentOf(Class<?> child) {
return isParentOf(child, false);
}
/**
* Returns <jk>true</jk> if this class is a parent or the same as <code>child</code>.
*
* @param child The child class.
* @return <jk>true</jk> if this class is a parent or the same as <code>child</code>.
*/
public boolean isParentOf(Type child) {
if (child instanceof Class)
return isParentOf((Class<?>)child);
return false;
}
/**
* Returns <jk>true</jk> if this class is a child of <code>parent</code>.
*
* @param parent The parent class.
* @param strict If <jk>true</jk> returns <jk>false</jk> if the classes are the same.
* @return <jk>true</jk> if this class is a parent of <code>child</code>.
*/
public boolean isChildOf(Class<?> parent, boolean strict) {
return parent.isAssignableFrom(c) && ((!strict) || ! c.equals(parent));
}
/**
* Returns <jk>true</jk> if this class is a child or the same as <code>parent</code>.
*
* @param parent The parent class.
* @return <jk>true</jk> if this class is a child or the same as <code>parent</code>.
*/
public boolean isChildOf(Class<?> parent) {
return isChildOf(parent, false);
}
/**
* Returns <jk>true</jk> if this class is a child or the same as any of the <code>parents</code>.
*
* @param parents The parents class.
* @return <jk>true</jk> if this class is a child or the same as any of the <code>parents</code>.
*/
public boolean isChildOfAny(Class<?>...parents) {
for (Class<?> p : parents)
if (isChildOf(p))
return true;
return false;
}
/**
* Returns <jk>true</jk> if this class is a child or the same as <code>parent</code>.
*
* @param parent The parent class.
* @return <jk>true</jk> if this class is a parent or the same as <code>parent</code>.
*/
public boolean isChildOf(Type parent) {
if (parent instanceof Class)
return isChildOf((Class<?>)parent);
return false;
}
/**
* Checks for equality with the specified class.
*
* @param c The class to check equality with.
* @return <jk>true</jk> if the specified class is the same as this one.
*/
public boolean is(Class<?> c) {
return this.c.equals(c);
}
/**
* Shortcut for calling {@link Class#newInstance()} on the underlying class.
*
* @return A new instance of the underlying class
* @throws IllegalAccessException
* @throws InstantiationException
*/
public Object newInstance() throws InstantiationException, IllegalAccessException {
return c.newInstance();
}
/**
* Returns <jk>true</jk> if this class is an interface.
*
* @return <jk>true</jk> if this class is an interface.
*/
public boolean isInterface() {
return c.isInterface();
}
}