![JAR search and dependency download from the Maven repository](/logo.png)
org.apache.juneau.reflect.ClassInfo Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.reflect;
import static org.apache.juneau.internal.ObjectUtils.*;
import static org.apache.juneau.common.internal.ArgUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.common.internal.ThrowableUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ConsumerUtils.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import org.apache.juneau.*;
import org.apache.juneau.internal.*;
/**
* Lightweight utility class for introspecting information about a class.
*
*
* Provides various convenience methods for introspecting fields/methods/annotations
* that aren't provided by the standard Java reflection APIs.
*
*
* Objects are designed to be lightweight to create and threadsafe.
*
*
Example:
*
* // Wrap our class inside a ClassInfo.
* ClassInfo classInfo = ClassInfo.of (MyClass.class );
*
* // Get all methods in parent-to-child order, sorted alphabetically per class.
* for (MethodInfo methodInfo : classInfo .getAllMethods()) {
* // Do something with it.
* }
*
* // Get all class-level annotations in parent-to-child order.
* for (MyAnnotation annotation : classInfo .getAnnotations(MyAnnotation.class )) {
* // Do something with it.
* }
*
*
* See Also:
*
*/
public final class ClassInfo {
//-----------------------------------------------------------------------------------------------------------------
// Static
//-----------------------------------------------------------------------------------------------------------------
private static final Map,ClassInfo> CACHE = new ConcurrentHashMap<>();
/** Reusable ClassInfo for Object class. */
public static final ClassInfo OBJECT = ClassInfo.of(Object.class);
/**
* Returns a class info wrapper around the specified class type.
*
* @param t The class type.
* @return The constructed class info, or null if the type was null .
*/
public static ClassInfo of(Type t) {
if (t == null)
return null;
if (t instanceof Class)
return of((Class>)t);
return new ClassInfo(ClassUtils.toClass(t), t);
}
/**
* Returns a class info wrapper around the specified class type.
*
* @param c The class type.
* @return The constructed class info, or null if the type was null .
*/
public static ClassInfo of(Class> c) {
if (c == null)
return null;
ClassInfo ci = CACHE.get(c);
if (ci == null) {
ci = new ClassInfo(c, c);
CACHE.put(c, ci);
}
return ci;
}
/**
* Returns a class info wrapper around the specified class type.
*
* @param c The class type.
* @param t The generic type (if parameterized type).
* @return The constructed class info, or null if the type was null .
*/
public static ClassInfo of(Class> c, Type t) {
if (c == t)
return of(c);
return new ClassInfo(c, t);
}
/**
* Same as using the constructor, but operates on an object instance.
*
* @param o The class instance.
* @return The constructed class info, or null if the object was null .
*/
public static ClassInfo of(Object o) {
return of(o == null ? null : o instanceof Class ? (Class>)o : o.getClass());
}
/**
* Same as {@link #of(Object)} but attempts to deproxify the object if it's wrapped in a CGLIB proxy.
*
* @param o The class instance.
* @return The constructed class info, or null if the object was null .
*/
public static ClassInfo ofProxy(Object o) {
if (o == null)
return null;
Class> c = getProxyFor(o);
return c == null ? ClassInfo.of(o) : ClassInfo.of(c);
}
/**
* When this metadata is against a CGLIB proxy, this method finds the underlying "real" class.
*
* @param o The class instance.
* @return The non-proxy class, or null if it's not a CGLIB proxy.
*/
private static Class> getProxyFor(Object o) {
Class> c = o.getClass();
String s = c.getName();
if (s.indexOf('$') == -1 || ! s.contains("$$EnhancerBySpringCGLIB$$"))
return null;
Value> v = Value.empty();
ClassInfo.of(c).forEachPublicMethod(
m -> m.hasName("getTargetClass") && m.hasNoParams() && m.hasReturnType(Class.class),
m -> safeRun(() -> v.set(m.invoke(o)))
);
return v.orElse(null);
}
//-----------------------------------------------------------------------------------------------------------------
// Instance
//-----------------------------------------------------------------------------------------------------------------
private final Type t;
final Class> c;
private final boolean isParameterizedType;
private volatile Boolean isRepeatedAnnotation;
private volatile ClassInfo[] interfaces, declaredInterfaces, parents, allParents;
private volatile MethodInfo[] publicMethods, declaredMethods, allMethods, allMethodsParentFirst;
private volatile MethodInfo repeatedAnnotationMethod;
private volatile ConstructorInfo[] publicConstructors, declaredConstructors;
private volatile FieldInfo[] publicFields, declaredFields, allFields;
private volatile Annotation[] declaredAnnotations;
private int dim = -1;
private ClassInfo componentType;
private final ConcurrentHashMap methods = new ConcurrentHashMap<>();
private final ConcurrentHashMap fields = new ConcurrentHashMap<>();
private final ConcurrentHashMap,ConstructorInfo> constructors = new ConcurrentHashMap<>();
/**
* Constructor.
*
* @param c The class type.
* @param t The generic type (if parameterized type).
*/
protected ClassInfo(Class> c, Type t) {
this.t = t;
this.c = c;
this.isParameterizedType = t == null ? false : (t instanceof ParameterizedType);
}
/**
* Returns the wrapped class as a {@link Type}.
*
* @return The wrapped class as a {@link Type}.
*/
public Type innerType() {
return t;
}
/**
* Returns the wrapped class as a {@link Class}.
*
* @param The inner class type.
* @return The wrapped class as a {@link Class}, or null if it's not a class (e.g. it's a {@link ParameterizedType}).
*/
@SuppressWarnings("unchecked")
public Class inner() {
return (Class)c;
}
/**
* Unwrap this class if it's a parameterized type of the specified type such as {@link Value} or {@link Optional}.
*
* @param wrapperTypes The parameterized types to unwrap if this class is one of those types.
* @return The class info on the unwrapped type, or just this type if this isn't one of the specified types.
*/
public ClassInfo unwrap(Class>...wrapperTypes) {
for (Class> wt : wrapperTypes) {
if (isParameterizedTypeOf(wt)) {
Type t = getFirstParameterType(wt);
if (t != null)
return of(t).unwrap(wrapperTypes); // Recursively do it again.
}
}
return this;
}
private boolean isParameterizedTypeOf(Class> c) {
return
(t instanceof ParameterizedType && ((ParameterizedType)t).getRawType() == c)
|| (t instanceof Class && c.isAssignableFrom((Class>)t));
}
private Type getFirstParameterType(Class> parameterizedType) {
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)t;
Type[] ta = pt.getActualTypeArguments();
if (ta.length > 0)
return ta[0];
} else if (t instanceof Class) /* Class that extends Optional */ {
Class> c = (Class>)t;
if (c != parameterizedType && parameterizedType.isAssignableFrom(c))
return ClassInfo.of(c).getParameterType(0, parameterizedType);
}
return null;
}
MethodInfo getMethodInfo(Method x) {
MethodInfo i = methods.get(x);
if (i == null) {
i = new MethodInfo(this, x);
methods.put(x, i);
}
return i;
}
FieldInfo getFieldInfo(Field x) {
FieldInfo i = fields.get(x);
if (i == null) {
i = new FieldInfo(this, x);
fields.put(x, i);
}
return i;
}
ConstructorInfo getConstructorInfo(Constructor> x) {
ConstructorInfo i = constructors.get(x);
if (i == null) {
i = new ConstructorInfo(this, x);
constructors.put(x, i);
}
return i;
}
//-----------------------------------------------------------------------------------------------------------------
// Parent classes and interfaces.
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the parent class.
*
* @return
* The parent class, or null if the class has no parent.
*/
public ClassInfo getSuperclass() {
return c == null ? null : of(c.getSuperclass());
}
/**
* Returns a list of interfaces declared on this class.
*
*
* Does not include interfaces declared on parent classes.
*
*
* Results are in the same order as Class.getInterfaces().
*
* @return
* An unmodifiable list of interfaces declared on this class.
*
Results are in the same order as {@link Class#getInterfaces()}.
*/
public List getDeclaredInterfaces() {
return ulist(_getDeclaredInterfaces());
}
/**
* Returns a list of interfaces defined on this class and superclasses.
*
*
* Results are in child-to-parent order.
*
* @return
* An unmodifiable list of interfaces defined on this class and superclasses.
*
Results are in child-to-parent order.
*/
public List getInterfaces() {
return ulist(_getInterfaces());
}
/**
* Returns a list including this class and all parent classes.
*
*
* Does not include interfaces.
*
*
* Results are in child-to-parent order.
*
* @return An unmodifiable list including this class and all parent classes.
*
Results are in child-to-parent order.
*/
public List getParents() {
return ulist(_getParents());
}
/**
* Returns a list including this class and all parent classes and interfaces.
*
*
* Results are classes-before-interfaces, then child-to-parent order.
*
* @return An unmodifiable list including this class and all parent classes.
*
Results are ordered child-to-parent order with classes listed before interfaces.
*/
public List getAllParents() {
return ulist(_getAllParents());
}
/**
* Returns the first matching parent class or interface.
*
*
* Results are classes-before-interfaces, then child-to-parent order.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The parent class or interface that matches the specified predicate.
*/
public ClassInfo getAnyParent(Predicate filter) {
for (ClassInfo ci : _getAllParents())
if (test(filter, ci))
return ci;
return null;
}
/** Results are in child-to-parent order. */
ClassInfo[] _getInterfaces() {
if (interfaces == null) {
synchronized(this) {
Set s = set();
for (ClassInfo ci : _getParents())
for (ClassInfo ci2 : ci._getDeclaredInterfaces()) {
s.add(ci2);
for (ClassInfo ci3 : ci2._getInterfaces())
s.add(ci3);
}
interfaces = s.toArray(new ClassInfo[s.size()]);
}
}
return interfaces;
}
/** Results are in the same order as Class.getInterfaces(). */
ClassInfo[] _getDeclaredInterfaces() {
if (declaredInterfaces == null) {
synchronized(this) {
Class>[] ii = c == null ? new Class[0] : c.getInterfaces();
ClassInfo[] l = new ClassInfo[ii.length];
for (int i = 0; i < ii.length; i++)
l[i] = of(ii[i]);
declaredInterfaces = l;
}
}
return declaredInterfaces;
}
/** Results are in child-to-parent order. */
ClassInfo[] _getParents() {
if (parents == null) {
synchronized(this) {
List l = list();
Class> pc = c;
while (pc != null && pc != Object.class) {
l.add(of(pc));
pc = pc.getSuperclass();
}
parents = l.toArray(new ClassInfo[l.size()]);
}
}
return parents;
}
/** Results are classes-before-interfaces, then child-to-parent order. */
ClassInfo[] _getAllParents() {
if (allParents == null) {
synchronized(this) {
ClassInfo[] a1 = _getParents(), a2 = _getInterfaces();
ClassInfo[] l = new ClassInfo[a1.length + a2.length];
for (int i = 0; i < a1.length; i++)
l[i] = a1[i];
for (int i = 0; i < a2.length; i++)
l[i+a1.length] = a2[i];
allParents = l;
}
}
return allParents;
}
//-----------------------------------------------------------------------------------------------------------------
// Methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all public methods on this class.
*
*
* Methods defined on the {@link Object} class are excluded from the results.
*
* @return
* All public methods on this class.
*
Results are ordered alphabetically.
*/
public List getPublicMethods() {
return ulist(_getPublicMethods());
}
/**
* Performs an action on all matching public methods on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachPublicMethod(Predicate filter, Consumer action) {
for (MethodInfo mi : _getPublicMethods())
consume(filter, action, mi);
return this;
}
/**
* Returns the first matching public method on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The first matching method, or null if no methods matched.
*/
public MethodInfo getPublicMethod(Predicate filter) {
for (MethodInfo mi : _getPublicMethods())
if (test(filter, mi))
return mi;
return null;
}
/**
* Returns all methods declared on this class.
*
* @return
* All methods declared on this class.
*
Results are ordered alphabetically.
*
List is unmodifiable.
*/
public List getDeclaredMethods() {
return ulist(_getDeclaredMethods());
}
/**
* Performs an action on all matching declared methods on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachDeclaredMethod(Predicate filter, Consumer action) {
for (MethodInfo mi : _getDeclaredMethods())
consume(filter, action, mi);
return this;
}
/**
* Returns the first matching declared method on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The first matching method, or null if no methods matched.
*/
public MethodInfo getDeclaredMethod(Predicate filter) {
for (MethodInfo mi : _getDeclaredMethods())
if (test(filter, mi))
return mi;
return null;
}
/**
* Returns all declared methods on this class and all parent classes.
*
* @return
* All declared methods on this class and all parent classes.
*
Results are ordered child-to-parent, and then alphabetically per class.
*
List is unmodifiable.
*/
public List getMethods() {
return ulist(_getAllMethods());
}
/**
* Performs an action on all matching methods on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachMethod(Predicate filter, Consumer action) {
for (MethodInfo mi : _getAllMethods())
consume(filter, action, mi);
return this;
}
/**
* Returns the first matching method on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The first matching method, or null if no methods matched.
*/
public MethodInfo getMethod(Predicate filter) {
for (MethodInfo mi : _getAllMethods())
if (test(filter, mi))
return mi;
return null;
}
/**
* Returns all declared methods on this class and all parent classes.
*
* @return
* All declared methods on this class and all parent classes.
*
Results are ordered parent-to-child, and then alphabetically per class.
*
List is unmodifiable.
*/
public List getAllMethodsParentFirst() {
return ulist(_getAllMethodsParentFirst());
}
/**
* Performs an action on all matching declared methods on this class and all parent classes.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAllMethodParentFirst(Predicate filter, Consumer action) {
for (MethodInfo mi : _getAllMethodsParentFirst())
consume(filter, action, mi);
return this;
}
MethodInfo[] _getPublicMethods() {
if (publicMethods == null) {
synchronized(this) {
Method[] mm = c == null ? new Method[0] : c.getMethods();
List l = list(mm.length);
for (Method m : mm)
if (m.getDeclaringClass() != Object.class)
l.add(getMethodInfo(m));
l.sort(null);
publicMethods = l.toArray(new MethodInfo[l.size()]);
}
}
return publicMethods;
}
MethodInfo[] _getDeclaredMethods() {
if (declaredMethods == null) {
synchronized(this) {
Method[] mm = c == null ? new Method[0] : c.getDeclaredMethods();
List l = list(mm.length);
for (Method m : mm)
if (! "$jacocoInit".equals(m.getName())) // Jacoco adds its own simulated methods.
l.add(getMethodInfo(m));
l.sort(null);
declaredMethods = l.toArray(new MethodInfo[l.size()]);
}
}
return declaredMethods;
}
MethodInfo[] _getAllMethods() {
if (allMethods == null) {
synchronized(this) {
List l = list();
for (ClassInfo c : _getAllParents())
c._appendDeclaredMethods(l);
allMethods = l.toArray(new MethodInfo[l.size()]);
}
}
return allMethods;
}
MethodInfo[] _getAllMethodsParentFirst() {
if (allMethodsParentFirst == null) {
synchronized(this) {
List l = list();
ClassInfo[] parents = _getAllParents();
for (int i = parents.length-1; i >=0; i--)
parents[i]._appendDeclaredMethods(l);
allMethodsParentFirst = l.toArray(new MethodInfo[l.size()]);
}
}
return allMethodsParentFirst;
}
private synchronized List _appendDeclaredMethods(List l) {
for (MethodInfo mi : _getDeclaredMethods())
l.add(mi);
return l;
}
//-----------------------------------------------------------------------------------------------------------------
// Constructors
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all the public constructors defined on this class.
*
* @return All public constructors defined on this class.
*/
public List getPublicConstructors() {
return ulist(_getPublicConstructors());
}
/**
* Performs an action on all matching public constructors on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachPublicConstructor(Predicate filter, Consumer action) {
for (ConstructorInfo mi : _getPublicConstructors())
consume(filter, action, mi);
return this;
}
/**
* Returns the first matching public constructor on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The public constructor that matches the specified predicate.
*/
public ConstructorInfo getPublicConstructor(Predicate filter) {
for (ConstructorInfo ci : _getPublicConstructors())
if (test(filter, ci))
return ci;
return null;
}
/**
* Returns all the constructors defined on this class.
*
* @return
* All constructors defined on this class.
*
List is unmodifiable.
*/
public List getDeclaredConstructors() {
return ulist(_getDeclaredConstructors());
}
/**
* Performs an action on all matching declared constructors on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachDeclaredConstructor(Predicate filter, Consumer action) {
for (ConstructorInfo mi : _getDeclaredConstructors())
consume(filter, action, mi);
return this;
}
/**
* Returns the first matching declared constructor on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The declared constructor that matches the specified predicate.
*/
public ConstructorInfo getDeclaredConstructor(Predicate filter) {
for (ConstructorInfo ci : _getDeclaredConstructors())
if (test(filter, ci))
return ci;
return null;
}
ConstructorInfo[] _getPublicConstructors() {
if (publicConstructors == null) {
synchronized(this) {
Constructor>[] cc = c == null ? new Constructor[0] : c.getConstructors();
List l = list(cc.length);
for (Constructor> ccc : cc)
l.add(getConstructorInfo(ccc));
l.sort(null);
publicConstructors = l.toArray(new ConstructorInfo[l.size()]);
}
}
return publicConstructors;
}
ConstructorInfo[] _getDeclaredConstructors() {
if (declaredConstructors == null) {
synchronized(this) {
Constructor>[] cc = c == null ? new Constructor[0] : c.getDeclaredConstructors();
List l = list(cc.length);
for (Constructor> ccc : cc)
l.add(getConstructorInfo(ccc));
l.sort(null);
declaredConstructors = l.toArray(new ConstructorInfo[l.size()]);
}
}
return declaredConstructors;
}
//-----------------------------------------------------------------------------------------------------------------
// Special constructors
//-----------------------------------------------------------------------------------------------------------------
/**
* Locates the no-arg constructor for this class.
*
*
* Constructor must match the visibility requirements specified by parameter 'v'.
* If class is abstract, always returns null .
* Note that this also returns the 1-arg constructor for non-static member classes.
*
* @param v The minimum visibility.
* @return The constructor, or null if no no-arg constructor exists with the required visibility.
*/
public ConstructorInfo getNoArgConstructor(Visibility v) {
if (isAbstract())
return null;
boolean isMemberClass = isNonStaticMemberClass();
for (ConstructorInfo cc : _getDeclaredConstructors())
if (cc.hasNumParams(isMemberClass ? 1 : 0) && cc.isVisible(v))
return cc.accessible(v);
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Fields
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all public fields on this class.
*
*
* Hidden fields are excluded from the results.
*
* @return
* All public fields on this class.
*
Results are in alphabetical order.
*
List is unmodifiable.
*/
public List getPublicFields() {
return ulist(_getPublicFields());
}
/**
* Performs an action on all matching public fields on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachPublicField(Predicate filter, Consumer action) {
for (FieldInfo mi : _getPublicFields())
consume(filter, action, mi);
return this;
}
/**
* Returns the first matching public field on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The public field, or null if not found.
*/
public FieldInfo getPublicField(Predicate filter) {
for (FieldInfo f : _getPublicFields())
if (test(filter, f))
return f;
return null;
}
/**
* Returns all declared fields on this class.
*
* @return
* All declared fields on this class.
*
Results are in alphabetical order.
*
List is unmodifiable.
*/
public List getDeclaredFields() {
return ulist(_getDeclaredFields());
}
/**
* Performs an action on all matching declared fields on this class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachDeclaredField(Predicate filter, Consumer action) {
for (FieldInfo fi : _getDeclaredFields())
consume(filter, action, fi);
return this;
}
/**
* Returns the first matching declared field on this class.
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return The declared field, or null if not found.
*/
public FieldInfo getDeclaredField(Predicate filter) {
for (FieldInfo f : _getDeclaredFields())
if (test(filter, f))
return f;
return null;
}
/**
* Returns all fields on this class and all parent classes.
*
*
* Results are ordered parent-to-child, and then alphabetical per class.
*
* @return
* All declared fields on this class.
*
List is unmodifiable.
*/
public List getAllFields() {
return ulist(_getAllFields());
}
/**
* Performs an action on all matching fields on this class and all parent classes.
*
*
* Results are ordered parent-to-child, and then alphabetical per class.
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAllField(Predicate filter, Consumer action) {
for (FieldInfo fi : _getAllFields())
consume(filter, action, fi);
return this;
}
FieldInfo[] _getPublicFields() {
if (publicFields == null) {
synchronized(this) {
Map m = map();
for (ClassInfo c : _getParents()) {
for (FieldInfo f : c._getDeclaredFields()) {
String fn = f.getName();
if (f.isPublic() && ! (m.containsKey(fn) || "$jacocoData".equals(fn)))
m.put(f.getName(), f);
}
}
List l = listFrom(m.values());
l.sort(null);
publicFields = l.toArray(new FieldInfo[l.size()]);
}
}
return publicFields;
}
FieldInfo[] _getDeclaredFields() {
if (declaredFields == null) {
synchronized(this) {
Field[] ff = c == null ? new Field[0] : c.getDeclaredFields();
List l = list(ff.length);
for (Field f : ff)
if (! "$jacocoData".equals(f.getName()))
l.add(getFieldInfo(f));
l.sort(null);
declaredFields = l.toArray(new FieldInfo[l.size()]);
}
}
return declaredFields;
}
FieldInfo[] _getAllFields() {
if (allFields == null) {
synchronized(this) {
List l = list();
ClassInfo[] parents = _getAllParents();
for (int i = parents.length-1; i >=0; i--)
for (FieldInfo f : parents[i]._getDeclaredFields())
l.add(f);
allFields = l.toArray(new FieldInfo[l.size()]);
}
}
return allFields;
}
//-----------------------------------------------------------------------------------------------------------------
// Annotations
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all annotations of the specified type defined on the specified class or parent classes/interfaces in parent-to-child order.
*
* @param The annotation type to look for.
* @param type The annotation type to look for.
* @return The matching annotations.
*/
public List getAnnotations(Class type) {
return getAnnotations(null, type);
}
/**
* Returns all annotations of the specified type defined on this or parent classes/interfaces.
*
*
* Returns the list in reverse (parent-to-child) order.
*
* @param The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation type to look for.
* @return The matching annotations.
*/
public List getAnnotations(AnnotationProvider annotationProvider, Class type) {
List l = list();
forEachAnnotation(annotationProvider, type, x-> true, x -> l.add(x));
return l;
}
/**
* Performs an action on all matching annotations on this class and superclasses/interfaces.
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAnnotation(Class type, Predicate filter, Consumer action) {
return forEachAnnotation(null, type, filter, action);
}
/**
* Performs an action on all matching annotations on this class and superclasses/interfaces.
*
*
* Annotations are appended in the following orders:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
* @param The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAnnotation(AnnotationProvider annotationProvider, Class type, Predicate filter, Consumer action) {
if (annotationProvider == null)
annotationProvider = AnnotationProvider.DEFAULT;
A t2 = getPackageAnnotation(type);
if (t2 != null)
consume(filter, action, t2);
ClassInfo[] interfaces = _getInterfaces();
for (int i = interfaces.length-1; i >= 0; i--)
annotationProvider.forEachDeclaredAnnotation(type, interfaces[i].inner(), filter, action);
ClassInfo[] parents = _getParents();
for (int i = parents.length-1; i >= 0; i--)
annotationProvider.forEachDeclaredAnnotation(type, parents[i].inner(), filter, action);
return this;
}
/**
* Returns the first matching annotation on this class and superclasses/interfaces.
*
*
* Annotations are searched in the following orders:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if annotation should be returned. Can be null .
* @return This object.
*/
public A firstAnnotation(Class type, Predicate filter) {
return firstAnnotation(null, type, filter);
}
/**
* Returns the first matching annotation on this class and superclasses/interfaces.
*
*
* Annotations are searched in the following orders:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
* @param The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if annotation should be returned. Can be null .
* @return This object.
*/
public A firstAnnotation(AnnotationProvider annotationProvider, Class type, Predicate filter) {
if (annotationProvider == null)
annotationProvider = AnnotationProvider.DEFAULT;
A x = null;
x = getPackageAnnotation(type);
if (x != null && test(filter, x))
return x;
ClassInfo[] interfaces = _getInterfaces();
for (int i = interfaces.length-1; i >= 0; i--) {
x = annotationProvider.firstAnnotation(type, interfaces[i].inner(), filter);
if (x != null)
return x;
}
ClassInfo[] parents = _getParents();
for (int i = parents.length-1; i >= 0; i--) {
x = annotationProvider.firstAnnotation(type, parents[i].inner(), filter);
if (x != null)
return x;
}
return null;
}
/**
* Returns the last matching annotation on this class and superclasses/interfaces.
*
*
* Annotations are searched in the following orders:
*
* - On this class.
*
- On parent classes ordered child-to-parent.
*
- On interfaces ordered child-to-parent.
*
- On the package of this class.
*
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if annotation should be returned. Can be null .
* @return This object.
*/
public A lastAnnotation(Class type, Predicate filter) {
return lastAnnotation(null, type, filter);
}
/**
* Returns the last matching annotation on this class and superclasses/interfaces.
*
*
* Annotations are searched in the following orders:
*
* - On this class.
*
- On parent classes ordered child-to-parent.
*
- On interfaces ordered child-to-parent.
*
- On the package of this class.
*
*
* @param The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if annotation should be returned. Can be null .
* @return This object.
*/
public A lastAnnotation(AnnotationProvider annotationProvider, Class type, Predicate filter) {
if (annotationProvider == null)
annotationProvider = AnnotationProvider.DEFAULT;
A x = null;
ClassInfo[] parents = _getParents();
for (ClassInfo parent : parents) {
x = annotationProvider.lastAnnotation(type, parent.inner(), filter);
if (x != null)
return x;
}
ClassInfo[] interfaces = _getInterfaces();
for (ClassInfo element : interfaces) {
x = annotationProvider.lastAnnotation(type, element.inner(), filter);
if (x != null)
return x;
}
x = getPackageAnnotation(type);
if (x != null && test(filter, x))
return x;
return null;
}
/**
* Finds the annotation of the specified type defined on this class or parent class/interface.
*
*
* If the annotation cannot be found on the immediate class, searches methods with the same
* signature on the parent classes or interfaces.
*
The search is performed in child-to-parent order.
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @return The annotation if found, or null if not.
*/
public A getAnnotation(Class type) {
return getAnnotation(null, type);
}
/**
* Finds the annotation of the specified type defined on this class or parent class/interface.
*
*
* If the annotation cannot be found on the immediate class, searches methods with the same signature on the parent classes or interfaces.
* The search is performed in child-to-parent order.
*
* @param The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @return The annotation if found, or null if not.
*/
public A getAnnotation(AnnotationProvider annotationProvider, Class type) {
return findAnnotation(annotationProvider, type);
}
/**
* Returns true if this class has the specified annotation.
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @return The true if annotation if found.
*/
public boolean hasAnnotation(Class type) {
return hasAnnotation(null, type);
}
/**
* Returns true if this class doesn't have the specified annotation.
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @return The true if annotation if not found.
*/
public boolean hasNoAnnotation(Class type) {
return ! hasAnnotation(type);
}
/**
* Returns true if this class has the specified annotation.
*
* @param The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @return The true if annotation if found.
*/
public boolean hasAnnotation(AnnotationProvider annotationProvider, Class type) {
if (annotationProvider == null)
annotationProvider = AnnotationProvider.DEFAULT;
return annotationProvider.firstAnnotation(type, c, x -> true) != null;
}
/**
* Returns the specified annotation only if it's been declared on the package of this class.
*
* @param The annotation type to look for.
* @param type The annotation class.
* @return The annotation, or null if not found.
*/
public A getPackageAnnotation(Class type) {
Package p = c == null ? null : c.getPackage();
return (p == null ? null : p.getAnnotation(type));
}
/**
* Returns the first matching annotation of the specified type defined on the specified class or parent classes/interfaces in parent-to-child order.
*
* @param The annotation type to look for.
* @param type The annotation to look for.
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return This object.
*/
public A getAnnotation(Class type, Predicate filter) {
return getAnnotation(null, type, filter);
}
/**
* Constructs an {@link AnnotationList} of all annotations found on this class.
*
*
* Annotations are appended in the following orders:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
* @return A new {@link AnnotationList} object on every call.
*/
public AnnotationList getAnnotationList() {
return getAnnotationList(x -> true);
}
/**
* Constructs an {@link AnnotationList} of all matching annotations on this class.
*
*
* Annotations are appended in the following orders:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
* @param filter A predicate to apply to the entries to determine if value should be used. Can be null .
* @return A new {@link AnnotationList} object on every call.
*/
public AnnotationList getAnnotationList(Predicate> filter) {
AnnotationList l = new AnnotationList();
forEachAnnotationInfo(filter, x -> l.add(x));
return l;
}
/*
* If the annotation is an array of other annotations, returns the inner annotations.
*
* @param a The annotation to split if repeated.
* @return The nested annotations, or a singleton array of the same annotation if it's not repeated.
*/
private static Annotation[] splitRepeated(Annotation a) {
try {
ClassInfo ci = ClassInfo.of(a.annotationType());
MethodInfo mi = ci.getRepeatedAnnotationMethod();
if (mi != null)
return mi.invoke(a);
} catch (Exception e) {
e.printStackTrace();
}
return new Annotation[]{a};
}
private A findAnnotation(AnnotationProvider ap, Class a) {
if (a == null)
return null;
if (ap == null)
ap = AnnotationProvider.DEFAULT;
A t = ap.firstDeclaredAnnotation(a, c, x -> true);
if (t != null)
return t;
ClassInfo sci = getSuperclass();
if (sci != null) {
t = sci.getAnnotation(ap, a);
if (t != null)
return t;
}
for (ClassInfo c2 : _getInterfaces()) {
t = c2.getAnnotation(ap, a);
if (t != null)
return t;
}
return null;
}
private A getAnnotation(AnnotationProvider ap, Class a, Predicate filter) {
if (ap == null)
ap = AnnotationProvider.DEFAULT;
A t2 = getPackageAnnotation(a);
if (t2 != null && filter.test(t2))
return t2;
ClassInfo[] interfaces = _getInterfaces();
for (int i = interfaces.length-1; i >= 0; i--) {
A o = ap.firstDeclaredAnnotation(a, interfaces[i].inner(), filter);
if (o != null)
return o;
}
ClassInfo[] parents = _getParents();
for (int i = parents.length-1; i >= 0; i--) {
A o = ap.firstDeclaredAnnotation(a, parents[i].inner(), filter);
if (o != null)
return o;
}
return null;
}
/**
* Performs an action on all matching annotations on this class/parents/package.
*
*
* Annotations are consumed in the following order:
*
* - On the package of this class.
*
- On interfaces ordered parent-to-child.
*
- On parent classes ordered parent-to-child.
*
- On this class.
*
*
* @param filter A predicate to apply to the entries to determine if action should be performed. Can be null .
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAnnotationInfo(Predicate> filter, Consumer> action) {
Package p = c.getPackage();
if (p != null)
for (Annotation a : p.getDeclaredAnnotations())
for (Annotation a2 : splitRepeated(a))
AnnotationInfo.of(p, a2).accept(filter, action);
ClassInfo[] interfaces = _getInterfaces();
for (int i = interfaces.length-1; i >= 0; i--)
for (Annotation a : interfaces[i].c.getDeclaredAnnotations())
for (Annotation a2 : splitRepeated(a))
AnnotationInfo.of(interfaces[i], a2).accept(filter, action);
ClassInfo[] parents = _getParents();
for (int i = parents.length-1; i >= 0; i--)
for (Annotation a : parents[i].c.getDeclaredAnnotations())
for (Annotation a2 : splitRepeated(a))
AnnotationInfo.of(parents[i], a2).accept(filter, action);
return this;
}
Annotation[] _getDeclaredAnnotations() {
if (declaredAnnotations == null) {
synchronized(this) {
declaredAnnotations = c.getDeclaredAnnotations();
}
}
return declaredAnnotations;
}
//-----------------------------------------------------------------------------------------------------------------
// Characteristics
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns true if all specified flags are applicable to this class.
*
* @param flags The flags to test for.
* @return true if all specified flags are applicable to this class.
*/
public boolean isAll(ReflectFlags...flags) {
for (ReflectFlags 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 MEMBER:
if (isNotMemberClass())
return false;
break;
case NOT_MEMBER:
if (isMemberClass())
return false;
break;
case ABSTRACT:
if (isNotAbstract())
return false;
break;
case NOT_ABSTRACT:
if (isAbstract())
return false;
break;
case INTERFACE:
if (isClass())
return false;
break;
case CLASS:
if (isInterface())
return false;
break;
default:
throw new BasicRuntimeException("Invalid flag for class: {0}", f);
}
}
return true;
}
/**
* Returns true if all specified flags are applicable to this class.
*
* @param flags The flags to test for.
* @return true if all specified flags are applicable to this class.
*/
public boolean isAny(ReflectFlags...flags) {
for (ReflectFlags 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 MEMBER:
if (isMemberClass())
return true;
break;
case NOT_MEMBER:
if (isNotMemberClass())
return true;
break;
case ABSTRACT:
if (isAbstract())
return true;
break;
case NOT_ABSTRACT:
if (isNotAbstract())
return true;
break;
case INTERFACE:
if (isInterface())
return true;
break;
case CLASS:
if (isClass())
return true;
break;
default:
throw new BasicRuntimeException("Invalid flag for class: {0}", f);
}
}
return false;
}
/**
* Returns true if this class has the {@link Deprecated @Deprecated} annotation on it.
*
* @return true if this class has the {@link Deprecated @Deprecated} annotation on it.
*/
public boolean isDeprecated() {
return c != null && c.isAnnotationPresent(Deprecated.class);
}
/**
* Returns true if this class doesn't have the {@link Deprecated @Deprecated} annotation on it.
*
* @return true if this class doesn't have the {@link Deprecated @Deprecated} annotation on it.
*/
public boolean isNotDeprecated() {
return c == null || ! c.isAnnotationPresent(Deprecated.class);
}
/**
* Returns true if this class is public.
*
* @return true if this class is public.
*/
public boolean isPublic() {
return c != null && Modifier.isPublic(c.getModifiers());
}
/**
* Returns true if this class is not public.
*
* @return true if this class is not public.
*/
public boolean isNotPublic() {
return c == null || ! Modifier.isPublic(c.getModifiers());
}
/**
* Returns true if this class is public.
*
*
* Note that interfaces are always reported as static, and the static keyword on a member interface is meaningless.
*
* @return true if this class is public.
*/
public boolean isStatic() {
return c != null && Modifier.isStatic(c.getModifiers());
}
/**
* Returns true if this class is not static.
*
*
* Note that interfaces are always reported as static, and the static keyword on a member interface is meaningless.
*
* @return true if this class is not static.
*/
public boolean isNotStatic() {
return c == null || ! Modifier.isStatic(c.getModifiers());
}
/**
* Returns true if this class is abstract.
*
*
* Note that interfaces are always reported as abstract.
*
* @return true if this class is abstract.
*/
public boolean isAbstract() {
return c != null && Modifier.isAbstract(c.getModifiers());
}
/**
* Returns true if this class is not abstract.
*
*
* Note that interfaces are always reported as abstract.
*
* @return true if this class is not abstract.
*/
public boolean isNotAbstract() {
return c == null || ! Modifier.isAbstract(c.getModifiers());
}
/**
* Returns true if this class is a member class.
*
* @return true if this class is a member class.
*/
public boolean isMemberClass() {
return c != null && c.isMemberClass();
}
/**
* Returns true if this class is a member class.
*
* @return true if this class is a member class.
*/
public boolean isNotMemberClass() {
return c == null || ! c.isMemberClass();
}
/**
* Returns true if this class is a member class and not static.
*
* @return true if this class is a member class and not static.
*/
public boolean isNonStaticMemberClass() {
return c != null && c.isMemberClass() && ! isStatic();
}
/**
* Returns false if this class is a member class and not static.
*
* @return false if this class is a member class and not static.
*/
public boolean isNotNonStaticMemberClass() {
return ! isNonStaticMemberClass();
}
/**
* Returns true if this class is a local class.
*
* @return true if this class is a local class.
*/
public boolean isLocalClass() {
return c != null && c.isLocalClass();
}
/**
* Returns true if this class is a local class.
*
* @return true if this class is a local class.
*/
public boolean isNotLocalClass() {
return c == null || ! c.isLocalClass();
}
/**
* Identifies if the specified visibility matches this constructor.
*
* @param v The visibility to validate against.
* @return true if this visibility matches the modifier attribute of this constructor.
*/
public boolean isVisible(Visibility v) {
return c != null && v.isVisible(c);
}
/**
* Returns true if this is a primitive class.
*
* @return true if this is a primitive class.
*/
public boolean isPrimitive() {
return c != null && c.isPrimitive();
}
/**
* Returns true if this is not a primitive class.
*
* @return true if this is not a primitive class.
*/
public boolean isNotPrimitive() {
return c == null || ! c.isPrimitive();
}
/**
* Returns true if this class is an interface.
*
* @return true if this class is an interface.
*/
public boolean isInterface() {
return c != null && c.isInterface();
}
/**
* Returns true if this class is not an interface.
*
* @return true if this class is not an interface.
*/
public boolean isClass() {
return c != null && ! c.isInterface();
}
/**
* Returns true if this class is a {@link RuntimeException}.
*
* @return true if this class is a {@link RuntimeException}.
*/
public boolean isRuntimeException() {
return isChildOf(RuntimeException.class);
}
//-----------------------------------------------------------------------------------------------------------------
// Primitive wrappers
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns true if the {@link #getPrimitiveWrapper()} method returns a value.
*
* @return true if the {@link #getPrimitiveWrapper()} method returns a value.
*/
public boolean hasPrimitiveWrapper() {
return pmap1.containsKey(c);
}
/**
* If this class is a primitive (e.g. int .class
) returns it's wrapper class
* (e.g. Integer.class
).
*
* @return The wrapper class, or null if class is not a primitive.
*/
public Class> getPrimitiveWrapper() {
return pmap1.get(c);
}
/**
* If this class is a primitive wrapper (e.g. Integer .class
) returns it's
* primitive class (e.g. int.class
).
*
* @return The primitive class, or null if class is not a primitive wrapper.
*/
public Class> getPrimitiveForWrapper() {
return pmap2.get(c);
}
/**
* If this class is a primitive (e.g. int .class
) returns it's wrapper class
* (e.g. Integer.class
).
*
* @return The wrapper class if it's primitive, or the same class if class is not a primitive.
*/
public Class> getWrapperIfPrimitive() {
if (c != null && ! 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 == null || ! c.isPrimitive())
return this;
return of(pmap1.get(c));
}
/**
* Returns the default value for this primitive class.
*
* @return The default value, or null if this is not a primitive class.
*/
public Object getPrimitiveDefault() {
return primitiveDefaultMap.get(c);
}
private static final Map, 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);
}
@SuppressWarnings("rawtypes")
private static final Map primitiveDefaultMap =
mapBuilder(Class.class,Object.class).unmodifiable()
.add(Boolean.TYPE, false)
.add(Character.TYPE, (char)0)
.add(Short.TYPE, (short)0)
.add(Integer.TYPE, 0)
.add(Long.TYPE, 0L)
.add(Float.TYPE, 0f)
.add(Double.TYPE, 0d)
.add(Byte.TYPE, (byte)0)
.add(Boolean.class, false)
.add(Character.class, (char)0)
.add(Short.class, (short)0)
.add(Integer.class, 0)
.add(Long.class, 0L)
.add(Float.class, 0f)
.add(Double.class, 0d)
.add(Byte.class, (byte)0)
.build();
//-----------------------------------------------------------------------------------------------------------------
// Labels
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the full name of this class.
*
* Examples:
*
* "com.foo.MyClass" - Normal class
* "com.foo.MyClass[][]" - Array.
* "com.foo.MyClass$InnerClass" - Inner class.
* "com.foo.MyClass$InnerClass[][]" - Inner class array.
* "int" - Primitive class.
* "int[][]" - Primitive class class.
* "java.util.Map<java.lang.String,java.lang.Object>" - Parameterized type.
* "java.util.AbstractMap<K,V>" - Parameterized generic type.
* "V" - Parameterized generic type argument.
*
*
* @return The underlying class name.
*/
public String getFullName() {
Class> ct = getComponentType().inner();
int dim = getDimensions();
if (ct != null && dim == 0 && ! isParameterizedType)
return ct.getName();
StringBuilder sb = new StringBuilder(128);
appendFullName(sb);
return sb.toString();
}
/**
* Returns all possible names for this class.
*
* @return
* An array consisting of:
*
* - {@link #getFullName()}
*
- {@link Class#getName()} - Note that this might be a dup.
*
- {@link #getShortName()}
*
- {@link #getSimpleName()}
*
*/
public String[] getNames() {
return new String[]{ getFullName(), c.getName(), getShortName(), getSimpleName() };
}
/**
* Same as {@link #getFullName()} but appends to an existing string builder.
*
* @param sb The string builder to append to.
* @return The same string builder.
*/
public StringBuilder appendFullName(StringBuilder sb) {
Class> ct = getComponentType().inner();
int dim = getDimensions();
if (ct != null && dim == 0 && ! isParameterizedType)
return sb.append(ct.getName());
sb.append(ct != null ? ct.getName() : t.getTypeName());
if (isParameterizedType) {
ParameterizedType pt = (ParameterizedType)t;
sb.append('<');
boolean first = true;
for (Type t2 : pt.getActualTypeArguments()) {
if (! first)
sb.append(',');
first = false;
of(t2).appendFullName(sb);
}
sb.append('>');
}
for (int i = 0; i < dim; i++)
sb.append('[').append(']');
return sb;
}
/**
* Returns the short name of the underlying class.
*
*
* Similar to {@link #getSimpleName()} but also renders local or member class name prefixes.
*
* @return The short name of the underlying class.
*/
public String getShortName() {
Class> ct = getComponentType().inner();
int dim = getDimensions();
if (ct != null && dim == 0 && ! (isParameterizedType || isMemberClass() || c.isLocalClass()))
return ct.getSimpleName();
StringBuilder sb = new StringBuilder(32);
appendShortName(sb);
return sb.toString();
}
/**
* Same as {@link #getShortName()} but appends to an existing string builder.
*
* @param sb The string builder to append to.
* @return The same string builder.
*/
public StringBuilder appendShortName(StringBuilder sb) {
Class> ct = getComponentType().inner();
int dim = getDimensions();
if (ct != null) {
if (ct.isLocalClass())
sb.append(of(ct.getEnclosingClass()).getSimpleName()).append('$').append(ct.getSimpleName());
else if (ct.isMemberClass())
sb.append(of(ct.getDeclaringClass()).getSimpleName()).append('$').append(ct.getSimpleName());
else
sb.append(ct.getSimpleName());
} else {
sb.append(t.getTypeName());
}
if (isParameterizedType) {
ParameterizedType pt = (ParameterizedType)t;
sb.append('<');
boolean first = true;
for (Type t2 : pt.getActualTypeArguments()) {
if (! first)
sb.append(',');
first = false;
of(t2).appendShortName(sb);
}
sb.append('>');
}
for (int i = 0; i < dim; i++)
sb.append('[').append(']');
return sb;
}
/**
* Returns the simple name of the underlying class.
*
*
* Returns either {@link Class#getSimpleName()} or {@link Type#getTypeName()} depending on whether
* this is a class or type.
*
* @return The simple name of the underlying class;
*/
public String getSimpleName() {
return c != null ? c.getSimpleName() : t.getTypeName();
}
/**
* Returns the name of the underlying class.
*
* @return The name of the underlying class.
*/
public String getName() {
return c != null ? c.getName() : t.getTypeName();
}
/**
* Same as {@link #getSimpleName()} but uses "Array" instead of "[]" .
*
* @return The readable name for this class.
*/
public String getReadableName() {
if (c == null)
return t.getTypeName();
if (! c.isArray())
return c.getSimpleName();
Class> c = this.c;
StringBuilder sb = new StringBuilder();
while (c.isArray()) {
sb.append("Array");
c = c.getComponentType();
}
return c.getSimpleName() + sb;
}
//-----------------------------------------------------------------------------------------------------------------
// Hierarchy
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns true if this class is a parent or the same as child .
*
* @param child The child class.
* @return true if this class is a parent or the same as child .
*/
public boolean isParentOf(Class> child) {
return c != null && child != null && c.isAssignableFrom(child);
}
/**
* Returns true if this class is a parent or the same as child .
*
*
* Primitive classes are converted to wrapper classes and compared.
*
*
Examples:
*
* ClassInfo.of (String.class ).isParentOfFuzzyPrimitives(String.class ); // true
* ClassInfo.of (CharSequence.class ).isParentOfFuzzyPrimitives(String.class ); // true
* ClassInfo.of (String.class ).isParentOfFuzzyPrimitives(CharSequence.class ); // false
* ClassInfo.of (int .class ).isParentOfFuzzyPrimitives(Integer.class ); // true
* ClassInfo.of (Integer.class ).isParentOfFuzzyPrimitives(int .class ); // true
* ClassInfo.of (Number.class ).isParentOfFuzzyPrimitives(int .class ); // true
* ClassInfo.of (int .class ).isParentOfFuzzyPrimitives(Number.class ); // false
* ClassInfo.of (int .class ).isParentOfFuzzyPrimitives(long .class ); // false
*
*
* @param child The child class.
* @return true if this class is a parent or the same as child .
*/
public boolean isParentOfFuzzyPrimitives(Class> child) {
if (c == null || child == null)
return false;
if (c.isAssignableFrom(child))
return true;
if (this.isPrimitive() || child.isPrimitive()) {
return this.getWrapperIfPrimitive().isAssignableFrom(of(child).getWrapperIfPrimitive());
}
return false;
}
/**
* Returns true if this type can be used as a parameter for the specified object.
*
* @param child The argument to check.
* @return true if this type can be used as a parameter for the specified object.
*/
public boolean canAcceptArg(Object child) {
if (c == null || child == null)
return false;
if (c.isInstance(child))
return true;
if (this.isPrimitive() || child.getClass().isPrimitive()) {
return this.getWrapperIfPrimitive().isAssignableFrom(of(child).getWrapperIfPrimitive());
}
return false;
}
/**
* Same as {@link #isParentOfFuzzyPrimitives(Class)} but takes in a {@link ClassInfo}.
*
* @param child The child class.
* @return true if this class is a parent or the same as child .
*/
public boolean isParentOfFuzzyPrimitives(ClassInfo child) {
if (c == null || child == null)
return false;
if (c.isAssignableFrom(child.inner()))
return true;
if (this.isPrimitive() || child.isPrimitive()) {
return this.getWrapperIfPrimitive().isAssignableFrom(child.getWrapperIfPrimitive());
}
return false;
}
/**
* Returns true if this class is a parent or the same as child .
*
* @param child The child class.
* @return true if this class is a parent or the same as child .
*/
public boolean isParentOf(Type child) {
if (child instanceof Class)
return isParentOf((Class>)child);
return false;
}
/**
* Returns true if this class is a parent or the same as child .
*
* @param child The child class.
* @return true if this class is a parent or the same as child .
*/
public boolean isParentOfFuzzyPrimitives(Type child) {
if (child instanceof Class)
return isParentOfFuzzyPrimitives((Class>)child);
return false;
}
/**
* Returns true if this class is a child of parent .
*
* @param parent The parent class.
* @return true if this class is a parent of child .
*/
public boolean isStrictChildOf(Class> parent) {
return c != null && parent != null && parent.isAssignableFrom(c) && ! c.equals(parent);
}
/**
* Returns true if this class is a child or the same as parent .
*
* @param parent The parent class.
* @return true if this class is a child or the same as parent .
*/
public boolean isChildOf(Class> parent) {
return c != null && parent != null && parent.isAssignableFrom(c);
}
/**
* Returns true if this class is a child or the same as any of the parents .
*
* @param parents The parents class.
* @return true if this class is a child or the same as any of the parents .
*/
public boolean isChildOfAny(Class>...parents) {
for (Class> p : parents)
if (isChildOf(p))
return true;
return false;
}
/**
* Returns true if this class is a child or the same as parent .
*
* @param parent The parent class.
* @return true if this class is a parent or the same as parent .
*/
public boolean isChildOf(Type parent) {
if (parent instanceof Class)
return isChildOf((Class>)parent);
return false;
}
/**
* Returns true if this class is a child or the same as parent .
*
* @param parent The parent class.
* @return true if this class is a parent or the same as parent .
*/
public boolean isChildOf(ClassInfo parent) {
return isChildOf(parent.inner());
}
/**
* Checks for equality with the specified class.
*
* @param c The class to check equality with.
* @return true if the specified class is the same as this one.
*/
public boolean is(Class> c) {
return this.c != null && this.c.equals(c);
}
/**
* Checks for equality with the specified class.
*
* @param c The class to check equality with.
* @return true if the specified class is the same as this one.
*/
public boolean is(ClassInfo c) {
if (this.c != null)
return this.c.equals(c.inner());
return t.equals(c.t);
}
/**
* Returns true if the specified value is an instance of this class.
*
* @param value The value to check.
* @return true if the specified value is an instance of this class.
*/
public boolean isInstance(Object value) {
if (this.c != null)
return c.isInstance(value);
return false;
}
/**
* Returns true if this class is any of the specified types.
*
* @param types The types to check against.
* @return true if this class is any of the specified types.
*/
public boolean isAny(Class>...types) {
for (Class> cc : types)
if (is(cc))
return true;
return false;
}
/**
* Returns true if all specified flags are applicable to this field.
*
* @param flags The flags to test for.
* @return true if all specified flags are applicable to this field.
*/
public boolean is(ReflectFlags...flags) {
return isAll(flags);
}
/**
* Returns the package of this class.
*
* @return The package of this class.
*/
public Package getPackage() {
return c == null ? null : c.getPackage();
}
/**
* Returns true if this class is not in the root package.
*
* @return true if this class is not in the root package.
*/
public boolean hasPackage() {
return getPackage() != null;
}
/**
* Returns the number of dimensions if this is an array type.
*
* @return The number of dimensions if this is an array type, or 0 if it is not.
*/
public int getDimensions() {
if (dim == -1) {
int d = 0;
Class> ct = c;
while (ct != null && ct.isArray()) {
d++;
ct = ct.getComponentType();
}
this.dim = d;
this.componentType = ct == c ? this : of(ct);
}
return dim;
}
/**
* Returns the base component type of this class if it's an array.
*
* @return The base component type of this class if it's an array, or this object if it's not.
*/
public ClassInfo getComponentType() {
if (componentType == null) {
if (c == null)
componentType = this;
else
getDimensions();
}
return componentType;
}
/**
* Returns true if this class is an enum.
*
* @return true if this class is an enum.
*/
public boolean isEnum() {
return c != null && c.isEnum();
}
/**
* Returns true if this is a repeated annotation class.
*
*
* A repeated annotation has a single value()
method that returns an array
* of annotations who themselves are marked with the {@link Repeatable @Repeatable} annotation
* of this class.
*
* @return true if this is a repeated annotation class.
*/
public boolean isRepeatedAnnotation() {
if (isRepeatedAnnotation == null) {
synchronized(this) {
boolean b = false;
repeatedAnnotationMethod = getPublicMethod(x -> x.hasName("value"));
if (repeatedAnnotationMethod != null) {
ClassInfo rt = repeatedAnnotationMethod.getReturnType();
if (rt.isArray()) {
ClassInfo rct = rt.getComponentType();
if (rct.hasAnnotation(Repeatable.class)) {
Repeatable r = rct.getAnnotation(Repeatable.class);
b = r.value().equals(c);
}
}
}
isRepeatedAnnotation = b;
}
}
return isRepeatedAnnotation;
}
/**
* Returns the repeated annotation method on this class.
*
*
* The repeated annotation method is the value()
method that returns an array
* of annotations who themselves are marked with the {@link Repeatable @Repeatable} annotation
* of this class.
*
* @return The repeated annotation method on this class, or null if it doesn't exist.
*/
public MethodInfo getRepeatedAnnotationMethod() {
if (isRepeatedAnnotation()) {
if (repeatedAnnotationMethod == null) {
synchronized(this) {
repeatedAnnotationMethod = getPublicMethod(x -> x.hasName("value"));
}
}
return repeatedAnnotationMethod;
}
return null;
}
/**
* Returns true if this class is an array.
*
* @return true if this class is an array.
*/
public boolean isArray() {
return c != null && c.isArray();
}
/**
* Returns true if this class is an annotation.
*
* @return true if this class is an annotation.
*/
public boolean isAnnotation() {
return c != null && c.isAnnotation();
}
/**
* Returns true if this class is a {@link Collection} or an array.
*
* @return true if this class is a {@link Collection} or an array.
*/
public boolean isCollectionOrArray() {
return c != null && (Collection.class.isAssignableFrom(c) || c.isArray());
}
//-----------------------------------------------------------------------------------------------------------------
// Instantiation
//-----------------------------------------------------------------------------------------------------------------
/**
* Shortcut for calling Class.getDeclaredConstructor().newInstance() on the underlying class.
*
* @return A new instance of the underlying class
* @throws ExecutableException Exception occurred on invoked constructor/method/field.
*/
public Object newInstance() throws ExecutableException {
if (c == null)
throw new ExecutableException("Type ''{0}'' cannot be instantiated", getFullName());
try {
return c.getDeclaredConstructor().newInstance();
} catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
throw new ExecutableException(e);
}
}
//-----------------------------------------------------------------------------------------------------------------
// Parameter types
//-----------------------------------------------------------------------------------------------------------------
/**
* Finds the real parameter type of this class.
*
* @param index The zero-based index of the parameter to resolve.
* @param pt The parameterized type class containing the parameterized type to resolve (e.g. HashMap ).
* @return The resolved real class.
*/
@SuppressWarnings("null")
public Class> getParameterType(int index, Class> pt) {
assertArgNotNull("pt", pt);
// We need to make up a mapping of type names.
Map typeMap = new HashMap<>();
Class> cc = c;
while (pt != cc.getSuperclass()) {
extractTypes(typeMap, cc);
cc = cc.getSuperclass();
assertArg(cc != null, "Class ''{0}'' is not a subclass of parameterized type ''{1}''", c.getSimpleName(), pt.getSimpleName());
}
Type gsc = cc.getGenericSuperclass();
assertArg(gsc instanceof ParameterizedType, "Class ''{0}'' is not a parameterized type", pt.getSimpleName());
ParameterizedType cpt = (ParameterizedType)gsc;
Type[] atArgs = cpt.getActualTypeArguments();
assertArg(index < atArgs.length, "Invalid type index. index={0}, argsLength={1}", index, atArgs.length);
Type actualType = cpt.getActualTypeArguments()[index];
if (typeMap.containsKey(actualType))
actualType = typeMap.get(actualType);
if (actualType instanceof Class) {
return (Class>)actualType;
} else if (actualType instanceof GenericArrayType) {
Type gct = ((GenericArrayType)actualType).getGenericComponentType();
if (gct instanceof ParameterizedType)
return Array.newInstance((Class>)((ParameterizedType)gct).getRawType(), 0).getClass();
} else if (actualType instanceof TypeVariable) {
TypeVariable> typeVariable = (TypeVariable>)actualType;
List> nestedOuterTypes = new LinkedList<>();
for (Class> ec = cc.getEnclosingClass(); ec != null; ec = ec.getEnclosingClass()) {
Class> outerClass = cc.getClass();
nestedOuterTypes.add(outerClass);
Map outerTypeMap = new HashMap<>();
extractTypes(outerTypeMap, outerClass);
for (Map.Entry 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();
}
}
}
}
} else if (actualType instanceof ParameterizedType) {
return (Class>)((ParameterizedType)actualType).getRawType();
}
throw new IllegalArgumentException("Could not resolve variable '"+actualType.getTypeName()+"' to a type.");
}
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 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]);
}
}
}
//-----------------------------------------------------------------------------------------------------------------
// Other methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns true if this object passes the specified predicate test.
*
* @param test The test to perform.
* @return true if this object passes the specified predicate test.
*/
public boolean matches(Predicate test) {
return test(test, this);
}
/**
* Performs an action on this object if the specified predicate test passes.
*
* @param test A test to apply to determine if action should be executed. Can be null .
* @param action An action to perform on this object.
* @return This object.
*/
public ClassInfo accept(Predicate test, Consumer action) {
if (matches(test))
action.accept(this);
return this;
}
@Override
public String toString() {
return t.toString();
}
@Override
public int hashCode() {
return t.hashCode();
}
@Override
public boolean equals(Object o) {
return (o instanceof ClassInfo) && eq(this, (ClassInfo)o, (x,y)->eq(x.t, y.t));
}
}