blob: 804df44b579b29b2b88321befe1d9e0880d47e9e [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.reflect;
import static org.apache.juneau.internal.StringUtils.*;
import static org.apache.juneau.reflect.ReflectFlags.*;
import static org.apache.juneau.internal.CollectionUtils.*;
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.annotation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.utils.*;
/**
* Lightweight utility class for introspecting information about a class.
*
* <p>
* Provides various convenience methods for introspecting fields/methods/annotations
* that aren't provided by the standard Java reflection APIs.
*
* <p>
* Objects are designed to be lightweight to create and threadsafe.
*
* <h5 class='figure'>Example:</h5>
* <p class='bpcode w800'>
* <jc>// Wrap our class inside a ClassInfo.</jc>
* ClassInfo ci = ClassInfo.<jsm>of</jsm>(MyClass.<jk>class</jk>);
*
* <jc>// Get all methods in parent-to-child order, sorted alphabetically per class.</jc>
* <jk>for</jk> (MethodInfo mi : ci.getAllMethodInfos(<jk>true</jk>, <jk>true</jk>)) {
* <jc>// Do something with it.</jc>
* }
*
* <jc>// Get all class-level annotations in parent-to-child order.</jc>
* <jk>for</jk> (MyAnnotation a : ci.getAnnotations(MyAnnotation.<jk>class</jk>, <jk>true</jk>)) {
* // Do something with it.
* }
* </p>
*/
@BeanIgnore
public final class ClassInfo {
private final Type t;
final Class<?> c;
private ClassInfo proxyFor;
private final boolean isParameterizedType;
private List<ClassInfo> interfaces, declaredInterfaces, parents, allParents;
private List<MethodInfo> publicMethods, declaredMethods, allMethods, allMethodsParentFirst;
private List<ConstructorInfo> publicConstructors, declaredConstructors;
private List<FieldInfo> publicFields, declaredFields, allFields, allFieldsParentFirst;
private int dim = -1;
private ClassInfo componentType;
private static final Map<Class<?>,ClassInfo> CACHE = new ConcurrentHashMap<>();
//-----------------------------------------------------------------------------------------------------------------
// Instantiation.
//-----------------------------------------------------------------------------------------------------------------
/**
* Constructor.
*
* @param c The class type.
* @param t The generic type (if parameterized type).
* @param proxyFor If the class is a CGLIB proxy, this is the underlying wrapped class.
*/
protected ClassInfo(Class<?> c, Type t, Class<?> proxyFor) {
this.t = t;
this.c = c;
this.proxyFor = proxyFor == null ? null : ClassInfo.of(proxyFor);
this.isParameterizedType = t == null ? false : (t instanceof ParameterizedType);
}
/**
* Returns a class info wrapper around the specified class type.
*
* @param t The class type.
* @return The constructed class info, or <jk>null</jk> if the type was <jk>null</jk>.
*/
public static ClassInfo of(Type t) {
if (t == null)
return null;
return new ClassInfo(ClassUtils.toClass(t), t, null);
}
/**
* Returns a class info wrapper around the specified class type.
*
* @param c The class type.
* @return The constructed class info, or <jk>null</jk> if the type was <jk>null</jk>.
*/
public static ClassInfo of(Class<?> c) {
if (c == null)
return null;
return new ClassInfo(c, c, null);
}
/**
* Same as {@link #of(Class)}} but caches the result for faster future lookup.
*
* @param c The class type.
* @return The constructed class info, or <jk>null</jk> if the type was <jk>null</jk>.
*/
public static ClassInfo ofc(Class<?> c) {
if (c == null)
return null;
ClassInfo ci = CACHE.get(c);
if (ci == null) {
ci = ClassInfo.of(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 <jk>null</jk> if the type was <jk>null</jk>.
*/
public static ClassInfo of(Class<?> c, Type t) {
return new ClassInfo(c, t, null);
}
/**
* Same as using the constructor, but operates on an object instance.
*
* @param o The class instance.
* @return The constructed class info, or <jk>null</jk> if the object was <jk>null</jk>.
*/
public static ClassInfo of(Object o) {
if (o == null)
return null;
return new ClassInfo(o.getClass(), o.getClass(), getProxyFor(o));
}
/**
* Same as {@link #of(Object)}} but caches the result for faster future lookup.
*
* @param o The class instance.
* @return The constructed class info, or <jk>null</jk> if the type was <jk>null</jk>.
*/
public static ClassInfo ofc(Object o) {
if (o == null)
return null;
Class<?> c = o.getClass();
ClassInfo ci = CACHE.get(c);
if (ci == null) {
ci = ClassInfo.of(o);
CACHE.put(c, ci);
}
return ci;
}
/**
* 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 <jk>null</jk> 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;
for (Method m : c.getMethods()) {
if (m.getName().equals("getTargetClass") && m.getParameterCount() == 0 && m.getReturnType().equals(Class.class)) {
try {
return (Class<?>) m.invoke(o);
} catch (Exception e) {}
}
}
return null;
}
/**
* 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}.
*
* @return The wrapped class as a {@link Class}, or <jk>null</jk> if it's not a class (e.g. it's a {@link ParameterizedType}).
*/
@SuppressWarnings("unchecked")
public <T> Class<T> inner() {
return (Class<T>)c;
}
/**
* If this class is a parameterized {@link Value} type, returns the parameterized type.
*
* @return The parameterized type, or this object if this class is not a parameterized {@link Value} type.
*/
public ClassInfo resolved() {
if (Value.isType(t))
return of(Value.getParameterType(t));
return this;
}
/**
* Identifies the inner target class when this class info represents a CGLIB proxy class.
*
* @param proxyFor The inner non-proxied class.
* @return This object (for method chaining).
*/
public ClassInfo proxyFor(Class<?> proxyFor) {
this.proxyFor = ClassInfo.of(proxyFor);
return this;
}
/**
* Returns the non-proxied inner class of a CGLIB proxy class.
*
* @return The non-proxied inner class of a CGLIB proxy class, or the inner class if it's not a proxy.
*/
public Class<?> getProxiedClass() {
return proxyFor == null ? c : proxyFor.inner();
}
//-----------------------------------------------------------------------------------------------------------------
// Parent classes and interfaces.
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the parent class.
*
* @return
* The parent class, or <jk>null</jk> if the class has no parent.
*/
public ClassInfo getParent() {
return c == null ? null : of(c.getSuperclass());
}
/**
* Returns a list of interfaces declared on this class.
*
* <p>
* Does not include interfaces declared on parent classes.
*
* @return
* An unmodifiable list of interfaces declared on this class.
* <br>Results are in the same order as {@link Class#getInterfaces()}.
*/
public List<ClassInfo> getDeclaredInterfaces() {
if (declaredInterfaces == null) {
Class<?>[] ii = c == null ? new Class[0] : c.getInterfaces();
List<ClassInfo> l = new ArrayList<>(ii.length);
for (Class<?> i : ii)
l.add(of(i));
declaredInterfaces = unmodifiableList(l);
}
return declaredInterfaces;
}
/**
* Returns a list of interfaces defined on this class and superclasses.
*
* @return
* An unmodifiable list of interfaces defined on this class and superclasses.
* <br>Results are in child-to-parent order.
*/
public List<ClassInfo> getInterfaces() {
if (interfaces == null) {
Set<ClassInfo> s = new LinkedHashSet<>();
for (ClassInfo ci : getParents())
for (ClassInfo ci2 : ci.getDeclaredInterfaces()) {
s.add(ci2);
for (ClassInfo ci3 : ci2.getInterfaces())
s.add(ci3);
}
interfaces = unmodifiableList(new ArrayList<>(s));
}
return interfaces;
}
/**
* Returns a list of interfaces defined on this class and superclasses.
*
* @return
* An unmodifiable list of interfaces defined on this class and superclasses.
* <br>Results are in parent-to-child order.
*/
public Iterable<ClassInfo> getInterfacesParentFirst() {
return iterable(getInterfaces(), true);
}
/**
* Returns a list including this class and all parent classes.
*
* <p>
* Does not include interfaces.
*
* @return An unmodifiable list including this class and all parent classes.
* <br>Results are in child-to-parent order.
*/
public List<ClassInfo> getParents() {
if (parents == null) {
List<ClassInfo> l = new ArrayList<>();
Class<?> pc = c;
while (pc != null && pc != Object.class) {
l.add(of(pc));
pc = pc.getSuperclass();
}
parents = Collections.unmodifiableList(l);
}
return parents;
}
/**
* Returns a list including this class and all parent classes.
*
* <p>
* Does not include interfaces.
*
* @return An unmodifiable list including this class and all parent classes.
* <br>Results are in parent-to-child order.
*/
public Iterable<ClassInfo> getParentsParentFirst() {
return iterable(getParents(), true);
}
/**
* Returns a list including this class and all parent classes and interfaces.
*
* @return An unmodifiable list including this class and all parent classes.
* <br>Results are ordered child-to-parent order with classes listed before interfaces.
*/
public List<ClassInfo> getAllParents() {
if (allParents == null) {
List<ClassInfo> l = new ArrayList<>();
l.addAll(getParents());
l.addAll(getInterfaces());
allParents = Collections.unmodifiableList(l);
}
return allParents;
}
/**
* Returns a list including this class and all parent classes and interfaces.
*
* @return An unmodifiable list including this class and all parent classes.
* <br>Results are ordered parent-to-child order with interfaces listed before classes.
*/
public Iterable<ClassInfo> getAllParentsParentFirst() {
return iterable(getAllParents(), true);
}
//-----------------------------------------------------------------------------------------------------------------
// Methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all public methods on this class.
*
* <p>
* Methods defined on the {@link Object} class are excluded from the results.
*
* @return
* All public methods on this class.
* <br>Results are ordered alphabetically.
*/
public List<MethodInfo> getPublicMethods() {
if (publicMethods == null) {
Method[] mm = c == null ? new Method[0] : c.getMethods();
List<MethodInfo> l = new ArrayList<>(mm.length);
for (Method m : mm)
if (m.getDeclaringClass() != Object.class)
l.add(MethodInfo.of(this, m, getProxyTarget(m)));
l.sort(null);
publicMethods = Collections.unmodifiableList(l);
}
return publicMethods;
}
private Method getProxyTarget(Method m) {
if (proxyFor != null) {
MethodInfo m2 = proxyFor.getMethod(m.getName(), m.getParameterTypes());
if (m2 != null)
return m2.inner();
}
return m;
}
private Constructor<?> getProxyTarget(Constructor<?> c) {
if (proxyFor != null) {
ConstructorInfo c2 = proxyFor.getConstructor(Visibility.PRIVATE, c.getParameterTypes());
if (c2 != null)
return c2.inner();
}
return c;
}
/**
* Returns the public method with the specified method name and argument types.
*
* @param name The method name (e.g. <js>"toString"</js>).
* @param args The exact argument types.
* @return
* The public method with the specified method name and argument types, or <jk>null</jk> if not found.
*/
public MethodInfo getPublicMethod(String name, Class<?>...args) {
for (MethodInfo mi : getPublicMethods())
if (mi.hasName(name) && mi.hasParamTypes(args))
return mi;
return null;
}
/**
* Returns the method with the specified method name and argument types.
*
* @param name The method name (e.g. <js>"toString"</js>).
* @param args The exact argument types.
* @return
* The method with the specified method name and argument types, or <jk>null</jk> if not found.
*/
public MethodInfo getMethod(String name, Class<?>...args) {
for (MethodInfo mi : getAllMethods())
if (mi.hasName(name) && mi.hasParamTypes(args))
return mi;
return null;
}
/**
* Returns all methods declared on this class.
*
* @return
* All methods declared on this class.
* <br>Results are ordered alphabetically.
*/
public List<MethodInfo> getDeclaredMethods() {
if (declaredMethods == null) {
Method[] mm = c == null ? new Method[0] : c.getDeclaredMethods();
List<MethodInfo> l = new ArrayList<>(mm.length);
for (Method m : mm)
if (! "$jacocoInit".equals(m.getName())) // Jacoco adds its own simulated methods.
l.add(MethodInfo.of(this, m, getProxyTarget(m)));
l.sort(null);
declaredMethods = Collections.unmodifiableList(l);
}
return declaredMethods;
}
/**
* Returns all declared methods on this class and all parent classes.
*
* @return
* All declared methods on this class and all parent classes.
* <br>Results are ordered child-to-parent, and then alphabetically per class.
*/
public List<MethodInfo> getAllMethods() {
if (allMethods == null) {
List<MethodInfo> l = new ArrayList<>();
for (ClassInfo c : getAllParents())
c.appendDeclaredMethods(l);
allMethods = Collections.unmodifiableList(l);
}
return allMethods;
}
/**
* Returns all declared methods on this class and all parent classes.
*
*
* @return
* All declared methods on this class and all parent classes.
* <br>Results are ordered parent-to-child, and then alphabetically per class.
*/
public List<MethodInfo> getAllMethodsParentFirst() {
if (allMethodsParentFirst == null) {
List<MethodInfo> l = new ArrayList<>();
for (ClassInfo c : getAllParentsParentFirst())
c.appendDeclaredMethods(l);
allMethodsParentFirst = Collections.unmodifiableList(l);
}
return allMethodsParentFirst;
}
private List<MethodInfo> appendDeclaredMethods(List<MethodInfo> l) {
l.addAll(getDeclaredMethods());
return l;
}
//-----------------------------------------------------------------------------------------------------------------
// Special methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Find the public static creator method on this class.
*
* <p>
* Looks for the following method names:
* <ul>
* <li><c>create</c>
* <li><c>from</c>
* <li><c>fromValue</c>
* <li><c>parse</c>
* <li><c>valueOf</c>
* <li><c>fromX</c>
* <li><c>forX</c>
* <li><c>parseX</c>
* </ul>
*
* @param ic The argument type.
* @param additionalNames Additional method names to check for.
* @return The static method, or <jk>null</jk> if it couldn't be found.
*/
public MethodInfo getStaticCreateMethod(Class<?> ic, String...additionalNames) {
if (c != null) {
for (MethodInfo m : getPublicMethods()) {
if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && m.hasReturnType(c) && m.hasParamTypes(ic)) {
String n = m.getSimpleName(), cn = ic.getSimpleName();
if (
isOneOf(n, "create","from","fromValue","parse","valueOf")
|| isOneOf(n, additionalNames)
|| (n.startsWith("from") && n.substring(4).equals(cn))
|| (n.startsWith("for") && n.substring(3).equals(cn))
|| (n.startsWith("parse") && n.substring(5).equals(cn))
) {
return m;
}
}
}
}
return null;
}
/**
* Find the public static method with the specified name and args.
*
* @param name The method name.
* @param rt The method return type.
* @param args The method arguments
* @return The method, or <jk>null</jk> if it couldn't be found.
*/
public MethodInfo getStaticPublicMethod(String name, Class<?> rt, Class<?>...args) {
if (c != null)
for (MethodInfo m : getPublicMethods())
if (m.isAll(STATIC, PUBLIC, NOT_DEPRECATED) && name.equals(m.getSimpleName()) && m.hasReturnType(rt) && m.hasParamTypes(args))
return m;
return null;
}
/**
* Find the public static method with the specified name and args.
*
* @param name The method name.
* @param rt The method return type.
* @param args The method arguments
* @return The method, or <jk>null</jk> if it couldn't be found.
*/
public Method getStaticPublicMethodInner(String name, Class<?> rt, Class<?>...args) {
MethodInfo mi = getStaticPublicMethod(name, rt, args);
return mi == null ? null : mi.inner();
}
/**
* Returns the <c>public static Builder create()</c> method on this class.
*
* @return The <c>public static Builder create()</c> method on this class, or <jk>null</jk> if it doesn't exist.
*/
public MethodInfo getBuilderCreateMethod() {
for (MethodInfo m : getDeclaredMethods())
if (m.isAll(PUBLIC, STATIC) && m.hasName("create") && (!m.hasReturnType(void.class)))
return m;
return null;
}
/**
* Returns the <c>T build()</c> method on this class.
*
* @return The <c>T build()</c> method on this class, or <jk>null</jk> if it doesn't exist.
*/
public MethodInfo getBuilderBuildMethod() {
for (MethodInfo m : getDeclaredMethods())
if (m.isAll(NOT_STATIC) && m.hasName("build") && (!m.hasParams()) && (!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 List<ConstructorInfo> getPublicConstructors() {
if (publicConstructors == null) {
Constructor<?>[] cc = c == null ? new Constructor[0] : c.getConstructors();
List<ConstructorInfo> l = new ArrayList<>(cc.length);
for (Constructor<?> ccc : cc)
l.add(ConstructorInfo.of(this, ccc, getProxyTarget(ccc)));
l.sort(null);
publicConstructors = Collections.unmodifiableList(l);
}
return publicConstructors;
}
/**
* Returns the public constructor with the specified argument types.
*
* @param args The exact argument types.
* @return
* The public constructor with the specified argument types, or <jk>null</jk> if not found.
*/
public ConstructorInfo getPublicConstructor(Class<?>...args) {
for (ConstructorInfo ci : getPublicConstructors())
if (ci.hasParamTypes(args))
return ci;
return null;
}
/**
* Returns the declared constructor with the specified argument types.
*
* @param args The exact argument types.
* @return
* The declared constructor with the specified argument types, or <jk>null</jk> if not found.
*/
public ConstructorInfo getDeclaredConstructor(Class<?>...args) {
for (ConstructorInfo ci : getDeclaredConstructors())
if (ci.hasParamTypes(args))
return ci;
return null;
}
/**
* Same as {@link #getPublicConstructor(Class...)} but allows for inexact arg type matching.
*
* <p>
* For example, the method <c>foo(CharSequence)</c> will be matched by <code>getAvailablePublicConstructor(String.<jk>class</jk>)</code>
*
* @param args The exact argument types.
* @return
* The public constructor with the specified argument types, or <jk>null</jk> if not found.
*/
public ConstructorInfo getAvailablePublicConstructor(Class<?>...args) {
return getConstructor(Visibility.PUBLIC, false, args);
}
/**
* Returns all the constructors defined on this class.
*
* @return All constructors defined on this class.
*/
public List<ConstructorInfo> getDeclaredConstructors() {
if (declaredConstructors == null) {
Constructor<?>[] cc = c == null ? new Constructor[0] : c.getDeclaredConstructors();
List<ConstructorInfo> l = new ArrayList<>(cc.length);
for (Constructor<?> ccc : cc)
l.add(ConstructorInfo.of(this, ccc, getProxyTarget(ccc)));
l.sort(null);
declaredConstructors = Collections.unmodifiableList(l);
}
return declaredConstructors;
}
/**
* Finds the public constructor that can take in the specified arguments.
*
* @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 ConstructorInfo getPublicConstructor(Object...args) {
return getPublicConstructor(ClassUtils.getClasses(args));
}
/**
* Finds the public constructor that can take in the specified arguments using fuzzy-arg matching.
*
* @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 ConstructorInfo getPublicConstructorFuzzy(Object...args) {
return getConstructor(Visibility.PUBLIC, true, ClassUtils.getClasses(args));
}
/**
* Finds a constructor with the specified parameters without throwing an exception.
*
* @param vis The minimum visibility.
* @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 ConstructorInfo getConstructor(Visibility vis, Class<?>...argTypes) {
return getConstructor(vis, false, argTypes);
}
private ConstructorInfo getConstructor(Visibility vis, boolean fuzzyArgs, Class<?>...argTypes) {
if (fuzzyArgs) {
int bestCount = -1;
ConstructorInfo bestMatch = null;
for (ConstructorInfo n : getDeclaredConstructors()) {
if (vis.isVisible(n.inner())) {
int m = ClassUtils.fuzzyArgsMatch(n.getParamTypes(), argTypes);
if (m > bestCount) {
bestCount = m;
bestMatch = n;
}
}
}
return bestMatch;
}
boolean isMemberClass = isNonStaticMemberClass();
for (ConstructorInfo n : getDeclaredConstructors()) {
List<ClassInfo> paramTypes = n.getParamTypes();
if (isMemberClass)
paramTypes = paramTypes.subList(1, paramTypes.size());
if (ClassUtils.argsMatch(paramTypes, argTypes) && vis.isVisible(n.inner()))
return n;
}
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Special 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 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.makeAccessible(v);
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Fields
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns all public fields on this class.
*
* <p>
* Hidden fields are excluded from the results.
*
* @return
* All public fields on this class.
* <br>Results are in alphabetical order.
*/
public List<FieldInfo> getPublicFields() {
if (publicFields == null) {
Map<String,FieldInfo> m = new LinkedHashMap<>();
for (ClassInfo c : getParents())
c.appendDeclaredPublicFields(m);
List<FieldInfo> l = new ArrayList<>(m.values());
l.sort(null);
publicFields = Collections.unmodifiableList(l);
}
return publicFields;
}
/**
* Returns all declared fields on this class.
*
* @return
* All declared fields on this class.
* <br>Results are in alphabetical order.
*/
public List<FieldInfo> getDeclaredFields() {
if (declaredFields == null) {
Field[] ff = c == null ? new Field[0] : c.getDeclaredFields();
List<FieldInfo> l = new ArrayList<>(ff.length);
for (Field f : ff)
if (! "$jacocoData".equals(f.getName()))
l.add(FieldInfo.of(this, f));
l.sort(null);
declaredFields = Collections.unmodifiableList(l);
}
return declaredFields;
}
/**
* Returns all declared fields on this class and all parent classes.
*
* @return
* All declared fields on this class.
* <br>Results are ordered child-to-parent, and then alphabetical per class.
*/
public List<FieldInfo> getAllFields() {
if (allFields == null) {
List<FieldInfo> l = new ArrayList<>();
for (ClassInfo c : getAllParents())
c.appendDeclaredFields(l);
allFields = Collections.unmodifiableList(l);
}
return allFields;
}
/**
* Returns all declared fields on this class and all parent classes.
*
* @return
* All declared fields on this class.
* <br>Results are ordered parent-to-child, and then alphabetical per class.
*/
public List<FieldInfo> getAllFieldsParentFirst() {
if (allFieldsParentFirst == null) {
List<FieldInfo> l = new ArrayList<>();
for (ClassInfo c : getAllParentsParentFirst())
c.appendDeclaredFields(l);
allFieldsParentFirst = Collections.unmodifiableList(l);
}
return allFieldsParentFirst;
}
private List<FieldInfo> appendDeclaredFields(List<FieldInfo> l) {
l.addAll(getDeclaredFields());
return l;
}
private Map<String,FieldInfo> appendDeclaredPublicFields(Map<String,FieldInfo> m) {
for (FieldInfo f : getDeclaredFields()) {
String fn = f.getName();
if (f.isPublic() && ! (m.containsKey(fn) || "$jacocoData".equals(fn)))
m.put(f.getName(), f);
}
return m;
}
/**
* Returns the public field with the specified name.
*
* @param name The field name.
* @return The public field, or <jk>null</jk> if not found.
*/
public FieldInfo getPublicField(String name) {
for (FieldInfo f : getPublicFields())
if (f.getName().equals(name))
return f;
return null;
}
/**
* Returns the declared field with the specified name.
*
* @param name The field name.
* @return The declared field, or <jk>null</jk> if not found.
*/
public FieldInfo getDeclaredField(String name) {
for (FieldInfo f : getDeclaredFields())
if (f.getName().equals(name))
return f;
return null;
}
/**
* Returns the static public field with the specified name.
*
* @param name The field name.
* @return The public field, or <jk>null</jk> if not found.
*/
public FieldInfo getStaticPublicField(String name) {
for (FieldInfo f : getPublicFields())
if (f.isStatic() && f.getName().equals(name))
return f;
return null;
}
/**
* Returns the static public field with the specified name.
*
* @param name The field name.
* @return The public field, or <jk>null</jk> if not found.
*/
public Field getStaticPublicFieldInner(String name) {
for (FieldInfo f : getPublicFields())
if (f.isStatic() && f.getName().equals(name))
return f.inner();
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// Annotations
//-----------------------------------------------------------------------------------------------------------------
/**
* Finds the annotation of the specified type defined on this class or parent class/interface.
*
* <p>
* If the annotation cannot be found on the immediate class, searches methods with the same
* signature on the parent classes or interfaces.
* <br>The search is performed in child-to-parent order.
*
* @param a
* The annotation to search for.
* @return
* The annotation if found, or <jk>null</jk> if not.
*/
public <T extends Annotation> T getAnnotation(Class<T> a) {
return getAnnotation(a, MetaProvider.DEFAULT);
}
/**
* Finds the annotation of the specified type defined on this class or parent class/interface.
*
* <p>
* If the annotation cannot be found on the immediate class, searches methods with the same
* signature on the parent classes or interfaces.
* <br>The search is performed in child-to-parent order.
*
* @param a
* The annotation to search for.
* @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
* @return
* The annotation if found, or <jk>null</jk> if not.
*/
public <T extends Annotation> T getAnnotation(Class<T> a, MetaProvider mp) {
return findAnnotation(a, mp);
}
/**
* Returns <jk>true</jk> if this class 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 the specified annotation only if it's been declared on this 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.
*/
public <T extends Annotation> T getDeclaredAnnotation(Class<T> a) {
return a == null ? null : c.getDeclaredAnnotation(a);
}
/**
* Returns the specified annotation only if it's been declared on this class.
*
* <p>
* More efficient than calling {@link Class#getAnnotation(Class)} since it doesn't recursively look for the class
* up the parent chain.
*
* @param <T> The annotation class type.
* @param a The annotation class.
* @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
* @return The annotation, or <jk>null</jk> if not found.
*/
public <T extends Annotation> T getDeclaredAnnotation(Class<T> a, MetaProvider mp) {
return mp.getDeclaredAnnotation(a, c);
}
/**
* Returns the specified annotation only if it's been declared on the package of this class.
*
* @param <T> The annotation class type.
* @param a The annotation class.
* @return The annotation, or <jk>null</jk> if not found.
*/
public <T extends Annotation> T getPackageAnnotation(Class<T> a) {
Package p = c == null ? null : c.getPackage();
return (p == null ? null : p.getAnnotation(a));
}
/**
* Same as {@link #getDeclaredAnnotation(Class)} but returns the annotation wrapped in a {@link AnnotationInfo}.
*
* @param a The annotation to search for.
* @return The annotation if found, or <jk>null</jk> if not.
*/
public <T extends Annotation> AnnotationInfo<T> getDeclaredAnnotationInfo(Class<T> a) {
T ca = getDeclaredAnnotation(a);
return ca == null ? null : AnnotationInfo.of(this, ca);
}
/**
* Same as {@link #getPackageAnnotation(Class)} but returns the annotation wrapped in a {@link AnnotationInfo}.
*
* @param a The annotation to search for.
* @return The annotation if found, or <jk>null</jk> if not.
*/
public <T extends Annotation> AnnotationInfo<T> getPackageAnnotationInfo(Class<T> a) {
T ca = getPackageAnnotation(a);
return ca == null ? null : AnnotationInfo.of(getPackage(), ca);
}
/**
* Returns all annotations of the specified type defined on the specified class or parent classes/interfaces.
*
* @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.
*/
public <T extends Annotation> List<T> getAnnotations(Class<T> a) {
return appendAnnotations(new ArrayList<>(), a);
}
/**
* Returns all annotations of the specified type defined on the specified class or parent classes/interfaces.
*
* @param <T> The annotation to search for.
* @param a The annotation to search for.
* @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
* @return
* A list of all matching annotations found in child-to-parent order, or an empty list if none found.
*/
public <T extends Annotation> List<T> getAnnotations(Class<T> a, MetaProvider mp) {
return appendAnnotations(new ArrayList<>(), a, mp);
}
/**
* Identical to {@link #getAnnotations(Class)} but optionally returns the list in reverse (parent-to-child) order.
*
* @param a
* The annotation to search for.
* @return
* A list of all matching annotations found or an empty list if none found.
*/
public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a) {
return appendAnnotationsParentFirst(new ArrayList<>(), a);
}
/**
* Identical to {@link #getAnnotations(Class,MetaProvider)} but optionally returns the list in reverse (parent-to-child) order.
*
* @param a
* The annotation to search for.
* @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
* @return
* A list of all matching annotations found or an empty list if none found.
*/
public <T extends Annotation> List<T> getAnnotationsParentFirst(Class<T> a, MetaProvider mp) {
return appendAnnotationsParentFirst(new ArrayList<>(), a, mp);
}
/**
* Same as getAnnotations(Class) except returns the annotations with the accompanying class.
*
* <p>
* Results are ordered child-to-parent.
*
* @param <T> The annotation class type.
* @param a The annotation class type.
* @return The found matches, or an empty list if annotation was not found.
*/
public <T extends Annotation> List<AnnotationInfo<T>> getAnnotationInfos(Class<T> a) {
return appendAnnotationInfos(new ArrayList<>(), a);
}
/**
* Same as getAnnotations(Class) except returns the annotations with the accompanying class.
*
* <p>
* Results are ordered parent-to-child.
*
* @param <T> The annotation class type.
* @param a The annotation class type.
* @return The found matches, or an empty list if annotation was not found.
*/
public <T extends Annotation> List<AnnotationInfo<T>> getAnnotationInfosParentFirst(Class<T> a) {
return appendAnnotationInfosParentFirst(new ArrayList<>(), a);
}
/**
* Constructs an {@link AnnotationList} of all annotations found on this class.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On this class.
* <li>On parent classes ordered child-to-parent.
* <li>On interfaces ordered child-to-parent.
* <li>On the package of this class.
* </ol>
*
* @param filter
* Optional filter to apply to limit which annotations are added to the list.
* <br>Can be <jk>null</jk> for no filtering.
* @return A new {@link AnnotationList} object on every call.
*/
public AnnotationList getAnnotationList(Predicate<AnnotationInfo<?>> filter) {
return appendAnnotationList(new AnnotationList(filter));
}
/**
* Constructs an {@link AnnotationList} of all annotations found on this class.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On the package of this class.
* <li>On interfaces ordered parent-to-child.
* <li>On parent classes ordered parent-to-child.
* <li>On this class.
* </ol>
*
* @param filter
* Optional filter to apply to limit which annotations are added to the list.
* <br>Can be <jk>null</jk> for no filtering.
* @return A new {@link AnnotationList} object on every call.
*/
public AnnotationList getAnnotationListParentFirst(Predicate<AnnotationInfo<?>> filter) {
return appendAnnotationListParentFirst(new AnnotationList(filter));
}
/**
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
* list.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On this class.
* <li>On parent classes ordered child-to-parent.
* <li>On interfaces ordered child-to-parent.
* <li>On the package of this class.
* </ol>
*
* @param l The list of annotations.
* @param a The annotation to search for.
* @return The same list.
*/
public <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a) {
return appendAnnotations(l, a, MetaProvider.DEFAULT);
}
/**
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
* list.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On this class.
* <li>On parent classes ordered child-to-parent.
* <li>On interfaces ordered child-to-parent.
* <li>On the package of this class.
* </ol>
*
* @param <T> The annotation to search for.
* @param l The list of annotations.
* @param a The annotation to search for.
* @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
* @return The same list.
*/
public <T extends Annotation> List<T> appendAnnotations(List<T> l, Class<T> a, MetaProvider mp) {
for (ClassInfo ci : getParents())
addIfNotNull(l, mp.getDeclaredAnnotation(a, ci.inner()));
for (ClassInfo ci : getInterfaces())
addIfNotNull(l, mp.getDeclaredAnnotation(a, ci.inner()));
addIfNotNull(l, getPackageAnnotation(a));
return l;
}
/**
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
* list.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On the package of this class.
* <li>On interfaces ordered child-to-parent.
* <li>On parent classes ordered child-to-parent.
* <li>On this class.
* </ol>
*
* @param l The list of annotations.
* @param a The annotation to search for.
* @return The same list.
*/
public <T extends Annotation> List<T> appendAnnotationsParentFirst(List<T> l, Class<T> a) {
return appendAnnotationsParentFirst(l, a, MetaProvider.DEFAULT);
}
/**
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
* list.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On the package of this class.
* <li>On interfaces ordered child-to-parent.
* <li>On parent classes ordered child-to-parent.
* <li>On this class.
* </ol>
*
* @param l The list of annotations.
* @param a The annotation to search for.
* @param mp The meta provider for looking up annotations on reflection objects (classes, methods, fields, constructors).
* @return The same list.
*/
public <T extends Annotation> List<T> appendAnnotationsParentFirst(List<T> l, Class<T> a, MetaProvider mp) {
addIfNotNull(l, getPackageAnnotation(a));
for (ClassInfo ci : getInterfacesParentFirst())
addIfNotNull(l, mp.getDeclaredAnnotation(a, ci.inner()));
for (ClassInfo ci : getParentsParentFirst())
addIfNotNull(l, mp.getDeclaredAnnotation(a, ci.inner()));
return l;
}
/**
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
* list.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On this class.
* <li>On parent classes ordered child-to-parent.
* <li>On interfaces ordered child-to-parent.
* <li>On the package of this class.
* </ol>
*
* @param l The list of annotations.
* @param a The annotation to search for.
* @return The same list.
*/
public <T extends Annotation> List<AnnotationInfo<T>> appendAnnotationInfos(List<AnnotationInfo<T>> l, Class<T> a) {
for (ClassInfo ci : getParents())
addIfNotNull(l, ci.getDeclaredAnnotationInfo(a));
for (ClassInfo ci : getInterfaces())
addIfNotNull(l, ci.getDeclaredAnnotationInfo(a));
addIfNotNull(l, getPackageAnnotationInfo(a));
return l;
}
/**
* Finds and appends the specified annotation on the specified class and superclasses/interfaces to the specified
* list.
*
* <p>
* Annotations are appended in the following orders:
* <ol>
* <li>On the package of this class.
* <li>On interfaces ordered child-to-parent.
* <li>On parent classes ordered child-to-parent.
* <li>On this class.
* </ol>
*
* @param l The list of annotations.
* @param a The annotation to search for.
* @return The same list.
*/
public <T extends Annotation> List<AnnotationInfo<T>> appendAnnotationInfosParentFirst(List<AnnotationInfo<T>> l, Class<T> a) {
addIfNotNull(l, getPackageAnnotationInfo(a));
for (ClassInfo ci : getInterfacesParentFirst())
addIfNotNull(l, ci.getDeclaredAnnotationInfo(a));
for (ClassInfo ci : getParentsParentFirst())
addIfNotNull(l, ci.getDeclaredAnnotationInfo(a));
return l;
}
AnnotationList appendAnnotationList(AnnotationList m) {
for (ClassInfo ci : getParents())
for (Annotation a : ci.c.getDeclaredAnnotations())
m.add(AnnotationInfo.of(ci, a));
for (ClassInfo ci : getInterfaces())
for (Annotation a : ci.c.getDeclaredAnnotations())
m.add(AnnotationInfo.of(ci, a));
Package p = c.getPackage();
if (p != null)
for (Annotation a : p.getDeclaredAnnotations())
m.add(AnnotationInfo.of(p, a));
return m;
}
AnnotationList appendAnnotationListParentFirst(AnnotationList m) {
Package p = c.getPackage();
if (p != null)
for (Annotation a : p.getDeclaredAnnotations())
m.add(AnnotationInfo.of(p, a));
for (ClassInfo ci : getInterfacesParentFirst())
for (Annotation a : ci.c.getDeclaredAnnotations())
m.add(AnnotationInfo.of(ci, a));
for (ClassInfo ci : getParentsParentFirst())
for (Annotation a : ci.c.getDeclaredAnnotations())
m.add(AnnotationInfo.of(ci, a));
return m;
}
<T extends Annotation> T findAnnotation(Class<T> a, MetaProvider mp) {
if (a == null)
return null;
T t2 = mp.getDeclaredAnnotation(a, c);
if (t2 != null)
return t2;
ClassInfo sci = getParent();
if (sci != null) {
t2 = sci.getAnnotation(a, mp);
if (t2 != null)
return t2;
}
for (ClassInfo c2 : getInterfaces()) {
t2 = c2.getAnnotation(a, mp);
if (t2 != null)
return t2;
}
return null;
}
//-----------------------------------------------------------------------------------------------------------------
// 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(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 RuntimeException("Invalid flag for class: " + f);
}
}
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(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 RuntimeException("Invalid flag for class: " + f);
}
}
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 != null && 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 == null || ! 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 c != null && 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 c == null || ! Modifier.isPublic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is public.
*
* <p>
* Note that interfaces are always reported as static, and the static keyword on a member interface is meaningless.
*
* @return <jk>true</jk> if this class is public.
*/
public boolean isStatic() {
return c != null && Modifier.isStatic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is not static.
*
* <p>
* Note that interfaces are always reported as static, and the static keyword on a member interface is meaningless.
*
* @return <jk>true</jk> if this class is not static.
*/
public boolean isNotStatic() {
return c == null || ! Modifier.isStatic(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is abstract.
*
* <p>
* Note that interfaces are always reported as abstract.
*
* @return <jk>true</jk> if this class is abstract.
*/
public boolean isAbstract() {
return c != null && Modifier.isAbstract(c.getModifiers());
}
/**
* Returns <jk>true</jk> if this class is not abstract.
*
* <p>
* Note that interfaces are always reported as abstract.
*
* @return <jk>true</jk> if this class is not abstract.
*/
public boolean isNotAbstract() {
return c == null || ! 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 != null && c.isMemberClass();
}
/**
* Returns <jk>true</jk> if this class is a member class.
*
* @return <jk>true</jk> if this class is a member class.
*/
public boolean isNotMemberClass() {
return c == null || ! c.isMemberClass();
}
/**
* Returns <jk>true</jk> if this class is a member class and not static.
*
* @return <jk>true</jk> if this class is a member class and not static.
*/
public boolean isNonStaticMemberClass() {
return c != null && c.isMemberClass() && ! isStatic();
}
/**
* Returns <jk>false</jk> if this class is a member class and not static.
*
* @return <jk>false</jk> if this class is a member class and not static.
*/
public boolean isNotNonStaticMemberClass() {
return ! isNonStaticMemberClass();
}
/**
* Returns <jk>true</jk> if this class is a local class.
*
* @return <jk>true</jk> if this class is a local class.
*/
public boolean isLocalClass() {
return c != null && c.isLocalClass();
}
/**
* Returns <jk>true</jk> if this class is a local class.
*
* @return <jk>true</jk> 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 <jk>true</jk> if this visibility matches the modifier attribute of this constructor.
*/
public boolean isVisible(Visibility v) {
return c != null && 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 != null && 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 == null || ! c.isPrimitive();
}
/**
* 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 != null && c.isInterface();
}
/**
* Returns <jk>true</jk> if this class is not an interface.
*
* @return <jk>true</jk> if this class is not an interface.
*/
public boolean isClass() {
return c != null && ! c.isInterface();
}
/**
* Returns <jk>true</jk> if this class is a {@link RuntimeException}.
*
* @return <jk>true</jk> if this class is a {@link RuntimeException}.
*/
public boolean isRuntimeException() {
return isChildOf(RuntimeException.class);
}
//-----------------------------------------------------------------------------------------------------------------
// Primitive wrappers
//-----------------------------------------------------------------------------------------------------------------
/**
* 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 != 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 <jk>null</jk> if this is not a primitive class.
*/
public Object getPrimitiveDefault() {
return primitiveDefaultMap.get(c);
}
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);
}
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)
);
//-----------------------------------------------------------------------------------------------------------------
// Labels
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns the full name of this class.
*
* <h5 class='section'>Examples:</h5>
* <ul>
* <li><js>"com.foo.MyClass"<js> - Normal class
* <li><js>"com.foo.MyClass[][]"<js> - Array.
* <li><js>"com.foo.MyClass$InnerClass"<js> - Inner class.
* <li><js>"com.foo.MyClass$InnerClass[][]"<js> - Inner class array.
* <li><js>"int"<js> - Primitive class.
* <li><js>"int[][]"<js> - Primitive class class.
* <li><js>"java.util.Map&lt;java.lang.String,java.lang.Object&gt;"<js> - Parameterized type.
* <li><js>"java.util.AbstractMap&lt;K,V&gt;"<js> - Parameterized generic type.
* <li><js>"V"<js> - Parameterized generic type argument.
* </ul>
*
* @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:
* <ul>
* <li>{@link #getFullName()}
* <li>{@link Class#getName()} - Note that this might be a dup.
* <li>{@link #getShortName()}
* <li>{@link #getSimpleName()}
* </ul>
*/
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.
*
* <p>
* 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.
*
* <p>
* 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 <js>"Array"</js> instead of <js>"[]"</js>.
*
* @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 <jk>true</jk> if this class is a parent or the same as <c>child</c>.
*
* @param child The child class.
* @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
*/
public boolean isParentOf(Class<?> child) {
return c != null && child != null && c.isAssignableFrom(child);
}
/**
* Returns <jk>true</jk> if this class is a parent or the same as <c>child</c>.
*
* @param child The child class.
* @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
*/
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 <c>parent</c>.
*
* @param parent The parent class.
* @return <jk>true</jk> if this class is a parent of <c>child</c>.
*/
public boolean isStrictChildOf(Class<?> parent) {
return c != null && parent != null && parent.isAssignableFrom(c) && ! c.equals(parent);
}
/**
* Returns <jk>true</jk> if this class is a child or the same as <c>parent</c>.
*
* @param parent The parent class.
* @return <jk>true</jk> if this class is a child or the same as <c>parent</c>.
*/
public boolean isChildOf(Class<?> parent) {
return c != null && parent != null && parent.isAssignableFrom(c);
}
/**
* Returns <jk>true</jk> if this class is a child or the same as any of the <c>parents</c>.
*
* @param parents The parents class.
* @return <jk>true</jk> if this class is a child or the same as any of the <c>parents</c>.
*/
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 <c>parent</c>.
*
* @param parent The parent class.
* @return <jk>true</jk> if this class is a parent or the same as <c>parent</c>.
*/
public boolean isChildOf(Type parent) {
if (parent instanceof Class)
return isChildOf((Class<?>)parent);
return false;
}
/**
* Returns <jk>true</jk> if this class is a child or the same as <c>parent</c>.
*
* @param parent The parent class.
* @return <jk>true</jk> if this class is a parent or the same as <c>parent</c>.
*/
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 <jk>true</jk> 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 <jk>true</jk> 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 <jk>true</jk> if this class is any of the specified types.
*
* @param types The types to check against.
* @return <jk>true</jk> 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 the package of this class.
*
* @return The package of this class.
*/
public Package getPackage() {
return c == null ? null : c.getPackage();
}
/**
* Returns <jk>true</jk> if this class is not in the root package.
*
* @return <jk>true</jk> 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 <c>0</c> 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 <jk>true</jk> if this class is an enum.
*
* @return <jk>true</jk> if this class is an enum.
*/
public boolean isEnum() {
return c != null && c.isEnum();
}
//-----------------------------------------------------------------------------------------------------------------
// Instantiation
//-----------------------------------------------------------------------------------------------------------------
/**
* Shortcut for calling {@link Class#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.newInstance();
} catch (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. <c>HashMap</c>).
* @return The resolved real class.
*/
public Class<?> getParameterType(int index, Class<?> pt) {
if (pt == null)
throw new FormattedIllegalArgumentException("Parameterized type cannot be null");
// We need to make up a mapping of type names.
Map<Type,Type> typeMap = new HashMap<>();
Class<?> cc = c;
while (pt != cc.getSuperclass()) {
extractTypes(typeMap, cc);
cc = cc.getSuperclass();
if (cc == null)
throw new FormattedIllegalArgumentException("Class ''{0}'' is not a subclass of parameterized type ''{1}''", c.getSimpleName(), pt.getSimpleName());
}
Type gsc = cc.getGenericSuperclass();
if (! (gsc instanceof ParameterizedType))
throw new FormattedIllegalArgumentException("Class ''{0}'' is not a parameterized type", pt.getSimpleName());
ParameterizedType cpt = (ParameterizedType)gsc;
Type[] atArgs = cpt.getActualTypeArguments();
if (index >= atArgs.length)
throw new FormattedIllegalArgumentException("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<Class<?>> nestedOuterTypes = new LinkedList<>();
for (Class<?> ec = cc.getEnclosingClass(); ec != null; ec = ec.getEnclosingClass()) {
Class<?> outerClass = cc.getClass();
nestedOuterTypes.add(outerClass);
Map<Type,Type> outerTypeMap = new HashMap<>();
extractTypes(outerTypeMap, outerClass);
for (Map.Entry<Type,Type> entry : outerTypeMap.entrySet()) {
Type key = entry.getKey(), value = entry.getValue();
if (key instanceof TypeVariable) {
TypeVariable<?> keyType = (TypeVariable<?>)key;
if (keyType.getName().equals(typeVariable.getName()) && isInnerClass(keyType.getGenericDeclaration(), typeVariable.getGenericDeclaration())) {
if (value instanceof Class)
return (Class<?>)value;
typeVariable = (TypeVariable<?>)entry.getValue();
}
}
}
}
} else if (actualType instanceof ParameterizedType) {
return (Class<?>)((ParameterizedType)actualType).getRawType();
}
throw new FormattedIllegalArgumentException("Could not resolve variable ''{0}'' to a type.", actualType.getTypeName());
}
private static boolean isInnerClass(GenericDeclaration od, GenericDeclaration id) {
if (od instanceof Class && id instanceof Class) {
Class<?> oc = (Class<?>)od;
Class<?> ic = (Class<?>)id;
while ((ic = ic.getEnclosingClass()) != null)
if (ic == oc)
return true;
}
return false;
}
private static void extractTypes(Map<Type,Type> typeMap, Class<?> c) {
Type gs = c.getGenericSuperclass();
if (gs instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType)gs;
Type[] typeParameters = ((Class<?>)pt.getRawType()).getTypeParameters();
Type[] actualTypeArguments = pt.getActualTypeArguments();
for (int i = 0; i < typeParameters.length; i++) {
if (typeMap.containsKey(actualTypeArguments[i]))
actualTypeArguments[i] = typeMap.get(actualTypeArguments[i]);
typeMap.put(typeParameters[i], actualTypeArguments[i]);
}
}
}
//-----------------------------------------------------------------------------------------------------------------
// Other
//-----------------------------------------------------------------------------------------------------------------
@Override
public String toString() {
return t.toString();
}
@Override
public int hashCode() {
return t.hashCode();
}
@Override
public boolean equals(Object o) {
return o == null ? false : ((ClassInfo)o).t.equals(t);
}
}