blob: f3b35484bb697b78238930803f0fdbd2dc3433be [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.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.
*
* <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='bjava'>
* <jc>// Wrap our class inside a ClassInfo.</jc>
* ClassInfo <jv>classInfo</jv> = 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 <jv>methodInfo</jv> : <jv>classInfo</jv>.getAllMethods()) {
* <jc>// Do something with it.</jc>
* }
*
* <jc>// Get all class-level annotations in parent-to-child order.</jc>
* <jk>for</jk> (MyAnnotation <jv>annotation</jv> : <jv>classInfo</jv>.getAnnotations(MyAnnotation.<jk>class</jk>)) {
* <jc>// Do something with it.</jc>
* }
* </p>
*
* <h5 class='section'>See Also:</h5><ul>
* </ul>
*/
public final class ClassInfo {
//-----------------------------------------------------------------------------------------------------------------
// Static
//-----------------------------------------------------------------------------------------------------------------
private static final Map<Class<?>,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 <jk>null</jk> if the type was <jk>null</jk>.
*/
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 <jk>null</jk> if the type was <jk>null</jk>.
*/
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 <jk>null</jk> if the type was <jk>null</jk>.
*/
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 <jk>null</jk> if the object was <jk>null</jk>.
*/
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 <jk>null</jk> if the object was <jk>null</jk>.
*/
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 <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;
Value<Class<?>> 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<Method,MethodInfo> methods = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Field,FieldInfo> fields = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Constructor<?>,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 <T> The inner class type.
* @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;
}
/**
* 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<T> */ {
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 <jk>null</jk> 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.
*
* <p>
* Does not include interfaces declared on parent classes.
*
* <p>
* Results are in the same order as Class.getInterfaces().
*
* @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() {
return ulist(_getDeclaredInterfaces());
}
/**
* Returns a list of interfaces defined on this class and superclasses.
*
* <p>
* Results are in child-to-parent order.
*
* @return
* An unmodifiable list of interfaces defined on this class and superclasses.
* <br>Results are in child-to-parent order.
*/
public List<ClassInfo> getInterfaces() {
return ulist(_getInterfaces());
}
/**
* Returns a list including this class and all parent classes.
*
* <p>
* Does not include interfaces.
*
* <p>
* Results are in child-to-parent order.
*
* @return An unmodifiable list including this class and all parent classes.
* <br>Results are in child-to-parent order.
*/
public List<ClassInfo> getParents() {
return ulist(_getParents());
}
/**
* Returns a list including this class and all parent classes and interfaces.
*
* <p>
* Results are classes-before-interfaces, then child-to-parent order.
*
* @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() {
return ulist(_getAllParents());
}
/**
* Returns the first matching parent class or interface.
*
* <p>
* 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 <jk>null</jk>.
* @return The parent class or interface that matches the specified predicate.
*/
public ClassInfo getAnyParent(Predicate<ClassInfo> 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<ClassInfo> 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<ClassInfo> 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.
*
* <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() {
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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachPublicMethod(Predicate<MethodInfo> filter, Consumer<MethodInfo> 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 <jk>null</jk>.
* @return The first matching method, or <jk>null</jk> if no methods matched.
*/
public MethodInfo getPublicMethod(Predicate<MethodInfo> 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.
* <br>Results are ordered alphabetically.
* <br>List is unmodifiable.
*/
public List<MethodInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachDeclaredMethod(Predicate<MethodInfo> filter, Consumer<MethodInfo> 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 <jk>null</jk>.
* @return The first matching method, or <jk>null</jk> if no methods matched.
*/
public MethodInfo getDeclaredMethod(Predicate<MethodInfo> 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.
* <br>Results are ordered child-to-parent, and then alphabetically per class.
* <br>List is unmodifiable.
*/
public List<MethodInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachMethod(Predicate<MethodInfo> filter, Consumer<MethodInfo> 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 <jk>null</jk>.
* @return The first matching method, or <jk>null</jk> if no methods matched.
*/
public MethodInfo getMethod(Predicate<MethodInfo> 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.
* <br>Results are ordered parent-to-child, and then alphabetically per class.
* <br>List is unmodifiable.
*/
public List<MethodInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAllMethodParentFirst(Predicate<MethodInfo> filter, Consumer<MethodInfo> 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<MethodInfo> 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<MethodInfo> 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<MethodInfo> 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<MethodInfo> 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<MethodInfo> _appendDeclaredMethods(List<MethodInfo> 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<ConstructorInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachPublicConstructor(Predicate<ConstructorInfo> filter, Consumer<ConstructorInfo> 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 <jk>null</jk>.
* @return The public constructor that matches the specified predicate.
*/
public ConstructorInfo getPublicConstructor(Predicate<ConstructorInfo> 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.
* <br>List is unmodifiable.
*/
public List<ConstructorInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachDeclaredConstructor(Predicate<ConstructorInfo> filter, Consumer<ConstructorInfo> 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 <jk>null</jk>.
* @return The declared constructor that matches the specified predicate.
*/
public ConstructorInfo getDeclaredConstructor(Predicate<ConstructorInfo> 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<ConstructorInfo> 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<ConstructorInfo> 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.
*
* <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.accessible(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.
* <br>List is unmodifiable.
*/
public List<FieldInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachPublicField(Predicate<FieldInfo> filter, Consumer<FieldInfo> 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 <jk>null</jk>.
* @return The public field, or <jk>null</jk> if not found.
*/
public FieldInfo getPublicField(Predicate<FieldInfo> 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.
* <br>Results are in alphabetical order.
* <br>List is unmodifiable.
*/
public List<FieldInfo> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachDeclaredField(Predicate<FieldInfo> filter, Consumer<FieldInfo> 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 <jk>null</jk>.
* @return The declared field, or <jk>null</jk> if not found.
*/
public FieldInfo getDeclaredField(Predicate<FieldInfo> filter) {
for (FieldInfo f : _getDeclaredFields())
if (test(filter, f))
return f;
return null;
}
/**
* Returns all fields on this class and all parent classes.
*
* <p>
* Results are ordered parent-to-child, and then alphabetical per class.
*
* @return
* All declared fields on this class.
* <br>List is unmodifiable.
*/
public List<FieldInfo> getAllFields() {
return ulist(_getAllFields());
}
/**
* Performs an action on all matching fields on this class and all parent classes.
*
* <p>
* 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAllField(Predicate<FieldInfo> filter, Consumer<FieldInfo> action) {
for (FieldInfo fi : _getAllFields())
consume(filter, action, fi);
return this;
}
FieldInfo[] _getPublicFields() {
if (publicFields == null) {
synchronized(this) {
Map<String,FieldInfo> 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<FieldInfo> 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<FieldInfo> 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<FieldInfo> 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 <A> The annotation type to look for.
* @param type The annotation type to look for.
* @return The matching annotations.
*/
public <A extends Annotation> List<A> getAnnotations(Class<A> type) {
return getAnnotations(null, type);
}
/**
* Returns all annotations of the specified type defined on this or parent classes/interfaces.
*
* <p>
* Returns the list in reverse (parent-to-child) order.
*
* @param <A> The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation type to look for.
* @return The matching annotations.
*/
public <A extends Annotation> List<A> getAnnotations(AnnotationProvider annotationProvider, Class<A> type) {
List<A> 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 <A> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public <A extends Annotation> ClassInfo forEachAnnotation(Class<A> type, Predicate<A> filter, Consumer<A> action) {
return forEachAnnotation(null, type, filter, action);
}
/**
* Performs an action on all matching annotations on this class and superclasses/interfaces.
*
* <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 <A> 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 <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public <A extends Annotation> ClassInfo forEachAnnotation(AnnotationProvider annotationProvider, Class<A> type, Predicate<A> filter, Consumer<A> 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.
*
* <p>
* Annotations are searched 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 <A> 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 <jk>null</jk>.
* @return This object.
*/
public <A extends Annotation> A firstAnnotation(Class<A> type, Predicate<A> filter) {
return firstAnnotation(null, type, filter);
}
/**
* Returns the first matching annotation on this class and superclasses/interfaces.
*
* <p>
* Annotations are searched 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 <A> 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 <jk>null</jk>.
* @return This object.
*/
public <A extends Annotation> A firstAnnotation(AnnotationProvider annotationProvider, Class<A> type, Predicate<A> 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.
*
* <p>
* Annotations are searched 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 <A> 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 <jk>null</jk>.
* @return This object.
*/
public <A extends Annotation> A lastAnnotation(Class<A> type, Predicate<A> filter) {
return lastAnnotation(null, type, filter);
}
/**
* Returns the last matching annotation on this class and superclasses/interfaces.
*
* <p>
* Annotations are searched 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 <A> 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 <jk>null</jk>.
* @return This object.
*/
public <A extends Annotation> A lastAnnotation(AnnotationProvider annotationProvider, Class<A> type, Predicate<A> 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.
*
* <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 type to look for.
* @param type The annotation to look for.
* @return The annotation if found, or <jk>null</jk> if not.
*/
public <A extends Annotation> A getAnnotation(Class<A> type) {
return getAnnotation(null, type);
}
/**
* 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 type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @return The annotation if found, or <jk>null</jk> if not.
*/
public <A extends Annotation> A getAnnotation(AnnotationProvider annotationProvider, Class<A> type) {
return findAnnotation(annotationProvider, type);
}
/**
* Returns <jk>true</jk> if this class has the specified annotation.
*
* @param <A> The annotation type to look for.
* @param type The annotation to look for.
* @return The <jk>true</jk> if annotation if found.
*/
public <A extends Annotation> boolean hasAnnotation(Class<A> type) {
return hasAnnotation(null, type);
}
/**
* Returns <jk>true</jk> if this class doesn't have the specified annotation.
*
* @param <A> The annotation type to look for.
* @param type The annotation to look for.
* @return The <jk>true</jk> if annotation if not found.
*/
public <A extends Annotation> boolean hasNoAnnotation(Class<A> type) {
return ! hasAnnotation(type);
}
/**
* Returns <jk>true</jk> if this class has the specified annotation.
*
* @param <A> The annotation type to look for.
* @param annotationProvider The annotation provider.
* @param type The annotation to look for.
* @return The <jk>true</jk> if annotation if found.
*/
public <A extends Annotation> boolean hasAnnotation(AnnotationProvider annotationProvider, Class<A> 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 <A> The annotation type to look for.
* @param type The annotation class.
* @return The annotation, or <jk>null</jk> if not found.
*/
public <A extends Annotation> A getPackageAnnotation(Class<A> 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 <A> 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 <jk>null</jk>.
* @return This object.
*/
public <A extends Annotation> A getAnnotation(Class<A> type, Predicate<A> filter) {
return getAnnotation(null, type, 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>
*
* @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.
*
* <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 A predicate to apply to the entries to determine if value should be used. Can be <jk>null</jk>.
* @return A new {@link AnnotationList} object on every call.
*/
public AnnotationList getAnnotationList(Predicate<AnnotationInfo<?>> 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 extends Annotation> A findAnnotation(AnnotationProvider ap, Class<A> 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 extends Annotation> A getAnnotation(AnnotationProvider ap, Class<A> a, Predicate<A> 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.
*
* <p>
* Annotations are consumed in the following order:
* <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 A predicate to apply to the entries to determine if action should be performed. Can be <jk>null</jk>.
* @param action An action to perform on the entry.
* @return This object.
*/
public ClassInfo forEachAnnotationInfo(Predicate<AnnotationInfo<?>> filter, Consumer<AnnotationInfo<?>> 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 <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 BasicRuntimeException("Invalid flag for class: {0}", 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 BasicRuntimeException("Invalid flag for class: {0}", 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);
}
@SuppressWarnings("rawtypes")
private static final Map<Class,Object> 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.
*
* <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>.
*
* <p>
* Primitive classes are converted to wrapper classes and compared.
*
* <h5 class='section'>Examples:</h5>
* <p class='bjava'>
* ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).isParentOfFuzzyPrimitives(String.<jk>class</jk>); <jc>// true</jc>
* ClassInfo.<jsm>of</jsm>(CharSequence.<jk>class</jk>).isParentOfFuzzyPrimitives(String.<jk>class</jk>); <jc>// true</jc>
* ClassInfo.<jsm>of</jsm>(String.<jk>class</jk>).isParentOfFuzzyPrimitives(CharSequence.<jk>class</jk>); <jc>// false</jc>
* ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfFuzzyPrimitives(Integer.<jk>class</jk>); <jc>// true</jc>
* ClassInfo.<jsm>of</jsm>(Integer.<jk>class</jk>).isParentOfFuzzyPrimitives(<jk>int</jk>.<jk>class</jk>); <jc>// true</jc>
* ClassInfo.<jsm>of</jsm>(Number.<jk>class</jk>).isParentOfFuzzyPrimitives(<jk>int</jk>.<jk>class</jk>); <jc>// true</jc>
* ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfFuzzyPrimitives(Number.<jk>class</jk>); <jc>// false</jc>
* ClassInfo.<jsm>of</jsm>(<jk>int</jk>.<jk>class</jk>).isParentOfFuzzyPrimitives(<jk>long</jk>.<jk>class</jk>); <jc>// false</jc>
* </p>
*
* @param child The child class.
* @return <jk>true</jk> if this class is a parent or the same as <c>child</c>.
*/
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 <jk>true</jk> if this type can be used as a parameter for the specified object.
*
* @param child The argument to check.
* @return <jk>true</jk> 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 <jk>true</jk> if this class is a parent or the same as <c>child</c>.
*/
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 <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 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 isParentOfFuzzyPrimitives(Type child) {
if (child instanceof Class)
return isParentOfFuzzyPrimitives((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 the specified value is an instance of this class.
*
* @param value The value to check.
* @return <jk>true</jk> 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 <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 <jk>true</jk> if all specified flags are applicable to this field.
*
* @param flags The flags to test for.
* @return <jk>true</jk> 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 <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();
}
/**
* Returns <jk>true</jk> if this is a repeated annotation class.
*
* <p>
* A repeated annotation has a single <code>value()</code> method that returns an array
* of annotations who themselves are marked with the {@link Repeatable @Repeatable} annotation
* of this class.
*
* @return <jk>true</jk> 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.
*
* <p>
* The repeated annotation method is the <code>value()</code> 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 <jk>null</jk> 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 <jk>true</jk> if this class is an array.
*
* @return <jk>true</jk> if this class is an array.
*/
public boolean isArray() {
return c != null && c.isArray();
}
/**
* Returns <jk>true</jk> if this class is an annotation.
*
* @return <jk>true</jk> if this class is an annotation.
*/
public boolean isAnnotation() {
return c != null && c.isAnnotation();
}
/**
* Returns <jk>true</jk> if this class is a {@link Collection} or an array.
*
* @return <jk>true</jk> 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 <c>Class.getDeclaredConstructor().newInstance()</c> 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. <c>HashMap</c>).
* @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<Type,Type> 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<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 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<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 methods
//-----------------------------------------------------------------------------------------------------------------
/**
* Returns <jk>true</jk> if this object passes the specified predicate test.
*
* @param test The test to perform.
* @return <jk>true</jk> if this object passes the specified predicate test.
*/
public boolean matches(Predicate<ClassInfo> 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 <jk>null</jk>.
* @param action An action to perform on this object.
* @return This object.
*/
public ClassInfo accept(Predicate<ClassInfo> test, Consumer<ClassInfo> 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));
}
}