io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo Maven / Gradle / Ivy
Show all versions of fast-classpath-scanner Show documentation
/*
* This file is part of FastClasspathScanner.
*
* Author: Luke Hutchison
*
* Hosted at: https://github.com/lukehutch/fast-classpath-scanner
*
* --
*
* The MIT License (MIT)
*
* Copyright (c) 2016 Luke Hutchison
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
* EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.lukehutch.fastclasspathscanner.scanner;
import java.io.File;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.github.lukehutch.fastclasspathscanner.json.Id;
import io.github.lukehutch.fastclasspathscanner.scanner.AnnotationInfo.AnnotationParamValue;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult.InfoObject;
import io.github.lukehutch.fastclasspathscanner.typesignature.ClassTypeSignature;
import io.github.lukehutch.fastclasspathscanner.typesignature.MethodTypeSignature;
import io.github.lukehutch.fastclasspathscanner.typesignature.TypeSignature;
import io.github.lukehutch.fastclasspathscanner.typesignature.TypeUtils;
import io.github.lukehutch.fastclasspathscanner.utils.JarUtils;
import io.github.lukehutch.fastclasspathscanner.utils.LogNode;
import io.github.lukehutch.fastclasspathscanner.utils.MultiMapKeyToList;
import io.github.lukehutch.fastclasspathscanner.utils.Parser.ParseException;
/** Holds metadata about a class encountered during a scan. */
public class ClassInfo extends InfoObject implements Comparable {
/** Name of the class/interface/annotation. */
@Id
String className;
/** Class modifier flags, e.g. Modifier.PUBLIC */
int modifiers;
/** True if the classfile indicated this is an interface. */
boolean isInterface;
/** True if the classfile indicated this is an annotation. */
boolean isAnnotation;
/** The class type signature string. */
String typeSignatureStr;
/** The class type signature, parsed. */
transient ClassTypeSignature typeSignature;
/** The fully-qualified containing method name, for anonymous inner classes. */
String fullyQualifiedContainingMethodName;
/**
* If true, this class is only being referenced by another class' classfile as a superclass / implemented
* interface / annotation, but this class is not itself a whitelisted (non-blacklisted) class, or in a
* whitelisted (non-blacklisted) package.
*
* If false, this classfile was matched during scanning (i.e. its classfile contents read), i.e. this class is a
* whitelisted (and non-blacklisted) class in a whitelisted (and non-blacklisted) package.
*/
boolean isExternalClass;
/**
* The classpath element file (classpath root dir or jar) that this class was found within, or null if this
* class was found in a module.
*/
transient File classpathElementFile;
/**
* The package root within a jarfile (e.g. "BOOT-INF/classes"), or the empty string if this is not a jarfile, or
* the package root is the classpath element path (as opposed to within a subdirectory of the classpath
* element).
*/
transient String jarfilePackageRoot = "";
/**
* The classpath element module that this class was found within, or null if this class was found within a
* directory or jar.
*/
transient ModuleRef classpathElementModuleRef;
/** The classpath element URL (classpath root dir or jar) that this class was found within. */
transient URL classpathElementURL;
/** The classloaders to try to load this class with before calling a MatchProcessor. */
transient ClassLoader[] classLoaders;
/** The scan spec. */
transient ScanSpec scanSpec;
/** Info on class annotations, including optional annotation param values. */
List annotationInfo;
/** Info on fields. */
List fieldInfo;
/** Reverse mapping from field name to FieldInfo. */
transient Map fieldNameToFieldInfo;
/** Info on fields. */
List methodInfo;
/** Reverse mapping from method name to MethodInfo. */
transient MultiMapKeyToList methodNameToMethodInfo;
/** For annotations, the default values of parameters. */
List annotationDefaultParamValues;
transient ScanResult scanResult;
/** The set of classes related to this one. */
Map> relatedClasses = new HashMap<>();
/**
* The static constant initializer values of static final fields, if a StaticFinalFieldMatchProcessor matched a
* field in this class.
*/
Map staticFinalFieldNameToConstantInitializerValue;
// -------------------------------------------------------------------------------------------------------------
/** Sets back-reference to scan result after scan is complete. */
@Override
void setScanResult(final ScanResult scanResult) {
this.scanResult = scanResult;
if (annotationInfo != null) {
for (final AnnotationInfo ai : annotationInfo) {
ai.setScanResult(scanResult);
}
}
if (fieldInfo != null) {
for (final FieldInfo fi : fieldInfo) {
fi.setScanResult(scanResult);
}
}
if (methodInfo != null) {
for (final MethodInfo mi : methodInfo) {
mi.setScanResult(scanResult);
}
}
}
/** Sets back-reference to ScanSpec after deserialization. */
void setFields(final ScanSpec scanSpec) {
this.scanSpec = scanSpec;
if (this.methodInfo != null) {
for (final MethodInfo methodInfo : this.methodInfo) {
methodInfo.classInfo = this;
methodInfo.className = this.className;
}
}
}
private static final int ANNOTATION_CLASS_MODIFIER = 0x2000;
// -------------------------------------------------------------------------------------------------------------
/**
* Get the name of this class.
*
* @return The class name.
*/
public String getClassName() {
return className;
}
/**
* Get a class reference for this class. Causes the ClassLoader to load the class.
*
* @return The class reference.
* @throws IllegalArgumentException
* if there were problems loading or initializing the class. (Note that class initialization on load
* is disabled by default, you can enable it with
* {@code FastClasspathScanner#initializeLoadedClasses(true)} .)
*/
public Class> getClassRef() {
return scanResult.classNameToClassRef(className);
}
/**
* Get a class reference for this class, casting it to the requested interface or superclass type. Causes the
* ClassLoader to load the class.
*
* @param classType
* The class to cast the result to.
* @return The class reference.
* @throws IllegalArgumentException
* if there were problems loading the class, initializing the class, or casting it to the requested
* type. (Note that class initialization on load is disabled by default, you can enable it with
* {@code FastClasspathScanner#initializeLoadedClasses(true)} .)
*/
public Class getClassRef(final Class classType) {
return scanResult.classNameToClassRef(className, classType);
}
/**
* Returns true if this class is an external class, i.e. was referenced by a whitelisted class as a superclass /
* implemented interface / annotation, but is not itself a whitelisted class.
*/
public boolean isExternalClass() {
return isExternalClass;
}
/**
* Get the class modifier flags, e.g. Modifier.PUBLIC
*
* @return The class modifiers.
*/
public int getClassModifiers() {
return modifiers;
}
/**
* Get the field modifiers as a String, e.g. "public static final". For the modifier bits, call getModifiers().
*
* @return The class modifiers, in String format.
*/
public String getModifiersStr() {
return TypeUtils.modifiersToString(modifiers, /* isMethod = */ false);
}
/**
* Test whether this ClassInfo corresponds to a public class.
*
* @return true if this ClassInfo corresponds to a public class.
*/
public boolean isPublic() {
return (modifiers & Modifier.PUBLIC) != 0;
}
/**
* Test whether this ClassInfo corresponds to an abstract class.
*
* @return true if this ClassInfo corresponds to an abstract class.
*/
public boolean isAbstract() {
return (modifiers & 0x400) != 0;
}
/**
* Test whether this ClassInfo corresponds to a synthetic class.
*
* @return true if this ClassInfo corresponds to a synthetic class.
*/
public boolean isSynthetic() {
return (modifiers & 0x1000) != 0;
}
/**
* Test whether this ClassInfo corresponds to a final class.
*
* @return true if this ClassInfo corresponds to a final class.
*/
public boolean isFinal() {
return (modifiers & Modifier.FINAL) != 0;
}
/**
* Test whether this ClassInfo corresponds to an annotation.
*
* @return true if this ClassInfo corresponds to an annotation.
*/
public boolean isAnnotation() {
return isAnnotation;
}
/**
* Test whether this ClassInfo corresponds to an interface.
*
* @return true if this ClassInfo corresponds to an interface.
*/
public boolean isInterface() {
return isInterface;
}
/**
* Test whether this ClassInfo corresponds to an enum.
*
* @return true if this ClassInfo corresponds to an enum.
*/
public boolean isEnum() {
return (modifiers & 0x4000) != 0;
}
/**
* Get the low-level Java type signature for the class, including generic type parameters, if available (else
* returns null).
*
* @return The type signature, in string format
*/
public String getTypeSignatureStr() {
return typeSignatureStr;
}
/**
* Get the type signature for the class, if available (else returns null).
*
* @return The class type signature.
*/
public ClassTypeSignature getTypeSignature() {
if (typeSignatureStr == null) {
return null;
}
if (typeSignature == null) {
try {
typeSignature = ClassTypeSignature.parse(typeSignatureStr);
} catch (final ParseException e) {
throw new IllegalArgumentException(e);
}
}
return typeSignature;
}
/**
* The classpath element URL (classpath root dir or jar) that this class was found within. This will consist of
* exactly one entry, so you should call the getClasspathElementURL() method instead.
*
* @return The classpath element URL, stored in a set.
*/
@Deprecated
public Set getClasspathElementURLs() {
final Set urls = new HashSet<>();
urls.add(getClasspathElementURL());
return urls;
}
/**
* The classpath element URL (for a classpath root dir, jar or module) that this class was found within.
*
* N.B. Classpath elements are handled as File objects internally. It is much faster to call
* getClasspathElementFile() and/or getClasspathElementModule() -- the conversion of a File into a URL (via
* File#toURI()#toURL()) is time consuming.
*
* @return The classpath element, as a URL.
*/
public URL getClasspathElementURL() {
if (classpathElementURL == null) {
try {
if (classpathElementModuleRef != null) {
classpathElementURL = classpathElementModuleRef.getModuleLocation().toURL();
} else {
classpathElementURL = getClasspathElementFile().toURI().toURL();
}
} catch (final MalformedURLException e) {
// Shouldn't happen; File objects should always be able to be turned into URIs and then URLs
throw new RuntimeException(e);
}
}
return classpathElementURL;
}
/**
* The classpath element file (classpath root dir or jar) that this class was found within, or null if this
* class was found in a module.
*
* @return The classpath element, as a File.
*/
public File getClasspathElementFile() {
return classpathElementFile;
}
/**
* The classpath element module that this class was found within, or null if this class was found in a directory
* or jar.
*
* @return The classpath element, as a ModuleRef.
*/
public ModuleRef getClasspathElementModuleRef() {
return classpathElementModuleRef;
}
/**
* Get the ClassLoader(s) to use when trying to load the class. Typically there will only be one. If there is
* more than one, they will be listed in the order they should be called, until one is able to load the class.
*
*
* This is deprecated, because a different ClassLoader may need to be created dynamically if classloading fails
* for the class (specifically to handle the case of needing to load classes from a Spring-Boot jar or similar,
* when running outside that jar's own classloader -- see bug #209). If you need the classloader that loaded the
* class, call {@link #getClassRef()} and then get the classloader from that class ref.
*
* @return The Classloader(s) to use when trying to load the class.
*/
@Deprecated
public ClassLoader[] getClassLoaders() {
return classLoaders;
}
/** Compare based on class name. */
@Override
public int compareTo(final ClassInfo o) {
return this.className.compareTo(o.className);
}
/** Use class name for equals(). */
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (this.getClass() != obj.getClass()) {
return false;
}
final ClassInfo other = (ClassInfo) obj;
return className.equals(other.className);
}
/** Use hash code of class name. */
@Override
public int hashCode() {
return className != null ? className.hashCode() : 33;
}
@Override
public String toString() {
final ClassTypeSignature typeSig = getTypeSignature();
if (typeSig != null) {
return typeSig.toString(modifiers, isAnnotation, isInterface, className);
} else {
final StringBuilder buf = new StringBuilder();
TypeUtils.modifiersToString(modifiers, /* isMethod = */ false, buf);
if (buf.length() > 0) {
buf.append(' ');
}
buf.append(isAnnotation ? "@interface "
: isInterface ? "interface " : (modifiers & 0x4000) != 0 ? "enum " : "class ");
buf.append(className);
return buf.toString();
}
}
// -------------------------------------------------------------------------------------------------------------
/** How classes are related. */
private static enum RelType {
// Classes:
/**
* Superclasses of this class, if this is a regular class.
*
*
* (Should consist of only one entry, or null if superclass is java.lang.Object or unknown).
*/
SUPERCLASSES,
/** Subclasses of this class, if this is a regular class. */
SUBCLASSES,
/** Indicates that an inner class is contained within this one. */
CONTAINS_INNER_CLASS,
/** Indicates that an outer class contains this one. (Should only have zero or one entries.) */
CONTAINED_WITHIN_OUTER_CLASS,
// Interfaces:
/**
* Interfaces that this class implements, if this is a regular class, or superinterfaces, if this is an
* interface.
*
*
* (May also include annotations, since annotations are interfaces, so you can implement an annotation.)
*/
IMPLEMENTED_INTERFACES,
/**
* Classes that implement this interface (including sub-interfaces), if this is an interface.
*/
CLASSES_IMPLEMENTING,
// Class annotations:
/**
* Annotations on this class, if this is a regular class, or meta-annotations on this annotation, if this is
* an annotation.
*/
CLASS_ANNOTATIONS,
/** Classes annotated with this annotation, if this is an annotation. */
CLASSES_WITH_CLASS_ANNOTATION,
// Method annotations:
/** Annotations on one or more methods of this class. */
METHOD_ANNOTATIONS,
/**
* Classes that have one or more methods annotated with this annotation, if this is an annotation.
*/
CLASSES_WITH_METHOD_ANNOTATION,
// Field annotations:
/** Annotations on one or more fields of this class. */
FIELD_ANNOTATIONS,
/**
* Classes that have one or more fields annotated with this annotation, if this is an annotation.
*/
CLASSES_WITH_FIELD_ANNOTATION,
}
ClassInfo() {
}
private ClassInfo(final String className, final int classModifiers, final boolean isExternalClass,
final ScanSpec scanSpec) {
this.className = className;
if (className.endsWith(";")) {
// Spot check to make sure class names were parsed from descriptors
throw new RuntimeException("Bad class name");
}
this.modifiers = classModifiers;
this.isExternalClass = isExternalClass;
this.scanSpec = scanSpec;
}
/** The class type to return. */
static enum ClassType {
/** Get all class types. */
ALL,
/** A standard class (not an interface or annotation). */
STANDARD_CLASS,
/**
* An interface (this is named "implemented interface" rather than just "interface" to distinguish it from
* an annotation.)
*/
IMPLEMENTED_INTERFACE,
/** An annotation. */
ANNOTATION,
/** An interface or annotation (used since you can actually implement an annotation). */
INTERFACE_OR_ANNOTATION,
}
/** Get the classes related to this one in the specified way. */
static Set filterClassInfo(final Set classInfoSet,
final boolean removeExternalClassesIfStrictWhitelist, final ScanSpec scanSpec,
final ClassType... classTypes) {
if (classInfoSet == null) {
return Collections.emptySet();
}
boolean includeAllTypes = classTypes.length == 0;
boolean includeStandardClasses = false;
boolean includeImplementedInterfaces = false;
boolean includeAnnotations = false;
for (final ClassType classType : classTypes) {
switch (classType) {
case ALL:
includeAllTypes = true;
break;
case STANDARD_CLASS:
includeStandardClasses = true;
break;
case IMPLEMENTED_INTERFACE:
includeImplementedInterfaces = true;
break;
case ANNOTATION:
includeAnnotations = true;
break;
case INTERFACE_OR_ANNOTATION:
includeImplementedInterfaces = includeAnnotations = true;
break;
default:
throw new RuntimeException("Unknown ClassType: " + classType);
}
}
if (includeStandardClasses && includeImplementedInterfaces && includeAnnotations) {
includeAllTypes = true;
}
// Do two passes with the same filter logic to avoid copying the set if nothing is filtered out
final Set classInfoSetFiltered = new HashSet<>(classInfoSet.size());
for (final ClassInfo classInfo : classInfoSet) {
// Check class type against requested type(s)
if (includeAllTypes //
|| includeStandardClasses && classInfo.isStandardClass()
|| includeImplementedInterfaces && classInfo.isImplementedInterface()
|| includeAnnotations && classInfo.isAnnotation()) {
// Check whether class should be visible in results
final boolean isBlacklisted = scanSpec.classIsBlacklisted(classInfo.className);
final boolean isSystemClass = JarUtils.isInSystemPackageOrModule(classInfo.className);
final boolean includeExternalClasses = scanSpec.enableExternalClasses
|| !removeExternalClassesIfStrictWhitelist;
final boolean notExternalOrIncludeExternal = !classInfo.isExternalClass || includeExternalClasses;
if (notExternalOrIncludeExternal //
// If this is a system class, ignore blacklist unless the blanket blacklisting of
// all system jars or modules has been disabled, and this system class was specifically
// blacklisted by name
&& (!isBlacklisted || (isSystemClass && scanSpec.blacklistSystemJarsOrModules))) {
// Class passed filter criteria
classInfoSetFiltered.add(classInfo);
}
}
}
return classInfoSetFiltered;
}
/**
* Get the sorted list of the names of classes given a collection of {@link ClassInfo} objects. (Class names are
* not deduplicated.)
*
* @param classInfoColl
* The collection of {@link ClassInfo} objects.
* @return The names of classes in the collection.
*/
public static List getClassNames(final Collection classInfoColl) {
if (classInfoColl.isEmpty()) {
return Collections.emptyList();
} else {
final ArrayList classNames = new ArrayList<>(classInfoColl.size());
for (final ClassInfo classInfo : classInfoColl) {
classNames.add(classInfo.className);
}
Collections.sort(classNames);
return classNames;
}
}
/** Get the classes directly related to this ClassInfo object the specified way. */
private Set getDirectlyRelatedClasses(final RelType relType) {
final Set relatedClassClassInfo = relatedClasses.get(relType);
return relatedClassClassInfo == null ? Collections. emptySet() : relatedClassClassInfo;
}
/**
* Find all ClassInfo nodes reachable from this ClassInfo node over the given relationship type links (not
* including this class itself).
*/
private Set getReachableClasses(final RelType relType) {
final Set directlyRelatedClasses = this.getDirectlyRelatedClasses(relType);
if (directlyRelatedClasses.isEmpty()) {
return directlyRelatedClasses;
}
final Set reachableClasses = new HashSet<>(directlyRelatedClasses);
if (relType == RelType.METHOD_ANNOTATIONS || relType == RelType.FIELD_ANNOTATIONS) {
// For method and field annotations, need to change the RelType when finding meta-annotations
for (final ClassInfo annotation : directlyRelatedClasses) {
reachableClasses.addAll(annotation.getReachableClasses(RelType.CLASS_ANNOTATIONS));
}
} else if (relType == RelType.CLASSES_WITH_METHOD_ANNOTATION
|| relType == RelType.CLASSES_WITH_FIELD_ANNOTATION) {
// If looking for meta-annotated methods or fields, need to find all meta-annotated annotations, then
// look for the methods or fields that they annotate
for (final ClassInfo subAnnotation : filterClassInfo(
getReachableClasses(RelType.CLASSES_WITH_CLASS_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ANNOTATION)) {
reachableClasses.addAll(subAnnotation.getDirectlyRelatedClasses(relType));
}
} else {
// For other relationship types, the reachable type stays the same over the transitive closure. Find the
// transitive closure, breaking cycles where necessary.
final LinkedList queue = new LinkedList<>();
queue.addAll(directlyRelatedClasses);
while (!queue.isEmpty()) {
final ClassInfo head = queue.removeFirst();
for (final ClassInfo directlyReachableFromHead : head.getDirectlyRelatedClasses(relType)) {
// Don't get in cycle
if (reachableClasses.add(directlyReachableFromHead)) {
queue.add(directlyReachableFromHead);
}
}
}
}
return reachableClasses;
}
/**
* Add a class with a given relationship type. Test whether the collection changed as a result of the call.
*/
private boolean addRelatedClass(final RelType relType, final ClassInfo classInfo) {
Set classInfoSet = relatedClasses.get(relType);
if (classInfoSet == null) {
relatedClasses.put(relType, classInfoSet = new HashSet<>(4));
}
return classInfoSet.add(classInfo);
}
/**
* Get a ClassInfo object, or create it if it doesn't exist. N.B. not threadsafe, so ClassInfo objects should
* only ever be constructed by a single thread.
*/
private static ClassInfo getOrCreateClassInfo(final String className, final int classModifiers,
final ScanSpec scanSpec, final Map classNameToClassInfo) {
ClassInfo classInfo = classNameToClassInfo.get(className);
if (classInfo == null) {
classNameToClassInfo.put(className,
classInfo = new ClassInfo(className, classModifiers, /* isExternalClass = */ true, scanSpec));
}
return classInfo;
}
/** Add a superclass to this class. */
void addSuperclass(final String superclassName, final Map classNameToClassInfo) {
if (superclassName != null) {
final ClassInfo superclassClassInfo = getOrCreateClassInfo(superclassName, /* classModifiers = */ 0,
scanSpec, classNameToClassInfo);
this.addRelatedClass(RelType.SUPERCLASSES, superclassClassInfo);
superclassClassInfo.addRelatedClass(RelType.SUBCLASSES, this);
}
}
/** Add an annotation to this class. */
void addClassAnnotation(final AnnotationInfo classAnnotationInfo,
final Map classNameToClassInfo) {
final ClassInfo annotationClassInfo = getOrCreateClassInfo(classAnnotationInfo.annotationName,
ANNOTATION_CLASS_MODIFIER, scanSpec, classNameToClassInfo);
annotationClassInfo.isAnnotation = true;
if (this.annotationInfo == null) {
this.annotationInfo = new ArrayList<>();
}
this.annotationInfo.add(classAnnotationInfo);
annotationClassInfo.modifiers |= 0x2000; // Modifier.ANNOTATION
classAnnotationInfo.addDefaultValues(annotationClassInfo.annotationDefaultParamValues);
this.addRelatedClass(RelType.CLASS_ANNOTATIONS, annotationClassInfo);
annotationClassInfo.addRelatedClass(RelType.CLASSES_WITH_CLASS_ANNOTATION, this);
}
/** Add a method annotation to this class. */
void addMethodAnnotation(final AnnotationInfo methodAnnotationInfo,
final Map classNameToClassInfo) {
final ClassInfo annotationClassInfo = getOrCreateClassInfo(methodAnnotationInfo.annotationName,
ANNOTATION_CLASS_MODIFIER, scanSpec, classNameToClassInfo);
annotationClassInfo.isAnnotation = true;
annotationClassInfo.modifiers |= 0x2000; // Modifier.ANNOTATION
methodAnnotationInfo.addDefaultValues(annotationClassInfo.annotationDefaultParamValues);
this.addRelatedClass(RelType.METHOD_ANNOTATIONS, annotationClassInfo);
annotationClassInfo.addRelatedClass(RelType.CLASSES_WITH_METHOD_ANNOTATION, this);
}
/** Add a field annotation to this class. */
void addFieldAnnotation(final AnnotationInfo fieldAnnotationInfo,
final Map classNameToClassInfo) {
final ClassInfo annotationClassInfo = getOrCreateClassInfo(fieldAnnotationInfo.annotationName,
ANNOTATION_CLASS_MODIFIER, scanSpec, classNameToClassInfo);
annotationClassInfo.isAnnotation = true;
annotationClassInfo.modifiers |= 0x2000; // Modifier.ANNOTATION
fieldAnnotationInfo.addDefaultValues(annotationClassInfo.annotationDefaultParamValues);
this.addRelatedClass(RelType.FIELD_ANNOTATIONS, annotationClassInfo);
annotationClassInfo.addRelatedClass(RelType.CLASSES_WITH_FIELD_ANNOTATION, this);
}
/** Add an implemented interface to this class. */
void addImplementedInterface(final String interfaceName, final Map classNameToClassInfo) {
final ClassInfo interfaceClassInfo = getOrCreateClassInfo(interfaceName,
/* classModifiers = */ Modifier.INTERFACE, scanSpec, classNameToClassInfo);
interfaceClassInfo.isInterface = true;
interfaceClassInfo.modifiers |= Modifier.INTERFACE;
this.addRelatedClass(RelType.IMPLEMENTED_INTERFACES, interfaceClassInfo);
interfaceClassInfo.addRelatedClass(RelType.CLASSES_IMPLEMENTING, this);
}
/** Add class containment info */
static void addClassContainment(final List> classContainmentEntries,
final ScanSpec scanSpec, final Map classNameToClassInfo) {
for (final SimpleEntry ent : classContainmentEntries) {
final String innerClassName = ent.getKey();
final ClassInfo innerClassInfo = ClassInfo.getOrCreateClassInfo(innerClassName,
/* classModifiers = */ 0, scanSpec, classNameToClassInfo);
final String outerClassName = ent.getValue();
final ClassInfo outerClassInfo = ClassInfo.getOrCreateClassInfo(outerClassName,
/* classModifiers = */ 0, scanSpec, classNameToClassInfo);
innerClassInfo.addRelatedClass(RelType.CONTAINED_WITHIN_OUTER_CLASS, outerClassInfo);
outerClassInfo.addRelatedClass(RelType.CONTAINS_INNER_CLASS, innerClassInfo);
}
}
/** Add containing method name, for anonymous inner classes */
void addFullyQualifiedContainingMethodName(final String fullyQualifiedContainingMethodName) {
this.fullyQualifiedContainingMethodName = fullyQualifiedContainingMethodName;
}
/** Add a static final field's constant initializer value. */
void addStaticFinalFieldConstantInitializerValue(final String fieldName, final Object constValue) {
if (this.staticFinalFieldNameToConstantInitializerValue == null) {
this.staticFinalFieldNameToConstantInitializerValue = new HashMap<>();
}
this.staticFinalFieldNameToConstantInitializerValue.put(fieldName, constValue);
}
/** Add field info. */
void addFieldInfo(final List fieldInfoList, final Map classNameToClassInfo) {
for (final FieldInfo fieldInfo : fieldInfoList) {
final List fieldAnnotationInfoList = fieldInfo.annotationInfo;
if (fieldAnnotationInfoList != null) {
for (final AnnotationInfo fieldAnnotationInfo : fieldAnnotationInfoList) {
final ClassInfo classInfo = getOrCreateClassInfo(fieldAnnotationInfo.annotationName,
ANNOTATION_CLASS_MODIFIER, scanSpec, classNameToClassInfo);
fieldAnnotationInfo.addDefaultValues(classInfo.annotationDefaultParamValues);
}
}
}
if (this.fieldInfo == null) {
this.fieldInfo = fieldInfoList;
} else {
this.fieldInfo.addAll(fieldInfoList);
}
}
/** Add method info. */
void addMethodInfo(final List methodInfoList, final Map classNameToClassInfo) {
for (final MethodInfo methodInfo : methodInfoList) {
final List methodAnnotationInfoList = methodInfo.annotationInfo;
if (methodAnnotationInfoList != null) {
for (final AnnotationInfo methodAnnotationInfo : methodAnnotationInfoList) {
methodAnnotationInfo.addDefaultValues(
getOrCreateClassInfo(methodAnnotationInfo.annotationName, ANNOTATION_CLASS_MODIFIER,
scanSpec, classNameToClassInfo).annotationDefaultParamValues);
}
}
final AnnotationInfo[][] methodParamAnnotationInfoList = methodInfo.parameterAnnotationInfo;
if (methodParamAnnotationInfoList != null) {
for (int i = 0; i < methodParamAnnotationInfoList.length; i++) {
final AnnotationInfo[] paramAnnotationInfoArr = methodParamAnnotationInfoList[i];
if (paramAnnotationInfoArr != null) {
for (int j = 0; j < paramAnnotationInfoArr.length; j++) {
final AnnotationInfo paramAnnotationInfo = paramAnnotationInfoArr[j];
paramAnnotationInfo
.addDefaultValues(getOrCreateClassInfo(paramAnnotationInfo.annotationName,
ANNOTATION_CLASS_MODIFIER, scanSpec,
classNameToClassInfo).annotationDefaultParamValues);
}
}
}
}
// Add back-link from MethodInfo to enclosing ClassInfo instance
methodInfo.classInfo = this;
}
if (this.methodInfo == null) {
this.methodInfo = methodInfoList;
} else {
this.methodInfo.addAll(methodInfoList);
}
}
/** Add the class type signature, including type params */
void addTypeSignature(final String typeSignatureStr) {
if (this.typeSignatureStr == null) {
this.typeSignatureStr = typeSignatureStr;
} else {
if (typeSignatureStr != null && !this.typeSignatureStr.equals(typeSignatureStr)) {
throw new RuntimeException("Trying to merge two classes with different type signatures for class "
+ className + ": " + this.typeSignatureStr + " ; " + typeSignatureStr);
}
}
}
/**
* Add annotation default values. (Only called in the case of annotation class definitions, when the annotation
* has default parameter values.)
*/
void addAnnotationParamDefaultValues(final List paramNamesAndValues) {
if (this.annotationDefaultParamValues == null) {
this.annotationDefaultParamValues = paramNamesAndValues;
} else {
this.annotationDefaultParamValues.addAll(paramNamesAndValues);
}
}
/**
* Add a class that has just been scanned (as opposed to just referenced by a scanned class). Not threadsafe,
* should be run in single threaded context.
*/
static ClassInfo addScannedClass(final String className, final int classModifiers, final boolean isInterface,
final boolean isAnnotation, final ScanSpec scanSpec, final Map classNameToClassInfo,
final ClasspathElement classpathElement, final LogNode log) {
boolean classEncounteredMultipleTimes = false;
ClassInfo classInfo = classNameToClassInfo.get(className);
if (classInfo == null) {
// This is the first time this class has been seen, add it
classNameToClassInfo.put(className,
classInfo = new ClassInfo(className, classModifiers, /* isExternalClass = */ false, scanSpec));
} else {
if (!classInfo.isExternalClass) {
classEncounteredMultipleTimes = true;
}
}
// Remember which classpath element (zipfile / classpath root directory / module) the class was found in
final ModuleRef modRef = classpathElement.getClasspathElementModuleRef();
final File file = modRef != null ? null : classpathElement.getClasspathElementFile(log);
if ((classInfo.classpathElementModuleRef != null && modRef != null
&& !classInfo.classpathElementModuleRef.equals(modRef))
|| (classInfo.classpathElementFile != null && file != null
&& !classInfo.classpathElementFile.equals(file))) {
classEncounteredMultipleTimes = true;
}
if (classEncounteredMultipleTimes) {
// The same class was encountered more than once in a single jarfile -- should not happen. However,
// actually there is no restriction for paths within a zipfile to be unique (!!), and in fact
// zipfiles in the wild do contain the same classfiles multiple times with the same exact path,
// e.g.: xmlbeans-2.6.0.jar!org/apache/xmlbeans/xml/stream/Location.class
if (log != null) {
log.log("Class " + className + " is defined in multiple different classpath elements or modules -- "
+ "ClassInfo#getClasspathElementFile() and/or ClassInfo#getClasspathElementModuleRef "
+ "will only return the first of these; attempting to merge info from all copies of "
+ "the classfile");
}
}
if (classInfo.classpathElementFile == null) {
// If class was found in more than one classpath element, keep the first classpath element reference
classInfo.classpathElementFile = file;
// Save jarfile package root, if any
classInfo.jarfilePackageRoot = classpathElement.getJarfilePackageRoot();
}
if (classInfo.classpathElementModuleRef == null) {
// If class was found in more than one module, keep the first module reference
classInfo.classpathElementModuleRef = modRef;
}
// Remember which classloader handles the class was found in, for classloading
final ClassLoader[] classLoaders = classpathElement.getClassLoaders();
if (classInfo.classLoaders == null) {
classInfo.classLoaders = classLoaders;
} else if (classLoaders != null && !classInfo.classLoaders.equals(classLoaders)) {
// Merge together ClassLoader list (concatenate and dedup)
final LinkedHashSet allClassLoaders = new LinkedHashSet<>(
Arrays.asList(classInfo.classLoaders));
for (final ClassLoader classLoader : classLoaders) {
allClassLoaders.add(classLoader);
}
final List classLoaderOrder = new ArrayList<>(allClassLoaders);
classInfo.classLoaders = classLoaderOrder.toArray(new ClassLoader[classLoaderOrder.size()]);
}
// Mark the classfile as scanned
classInfo.isExternalClass = false;
// Merge modifiers
classInfo.modifiers |= classModifiers;
classInfo.isInterface |= isInterface;
classInfo.isAnnotation |= isAnnotation;
return classInfo;
}
// -------------------------------------------------------------------------------------------------------------
/**
* Get the names of any classes (other than this class itself) referenced in this class' type descriptor, or the
* type descriptors of fields or methods (if field or method info is recorded).
*
* @return The names of the referenced classes.
*/
public Set getClassNamesReferencedInAnyTypeDescriptor() {
final Set referencedClassNames = new HashSet<>();
if (methodInfo != null) {
for (final MethodInfo mi : methodInfo) {
final MethodTypeSignature methodSig = mi.getTypeSignature();
if (methodSig != null) {
methodSig.getAllReferencedClassNames(referencedClassNames);
}
}
}
if (fieldInfo != null) {
for (final FieldInfo fi : fieldInfo) {
final TypeSignature fieldSig = fi.getTypeSignature();
if (fieldSig != null) {
fieldSig.getAllReferencedClassNames(referencedClassNames);
}
}
}
final ClassTypeSignature classSig = getTypeSignature();
if (classSig != null) {
classSig.getAllReferencedClassNames(referencedClassNames);
}
// Remove self-reference, and any reference to java.lang.Object
referencedClassNames.remove(className);
referencedClassNames.remove("java.lang.Object");
return referencedClassNames;
}
/**
* Get the names of any classes referenced in the type descriptors of this class' methods (if method info is
* recorded).
*
* @return The names of the referenced classes.
*/
public Set getClassNamesReferencedInMethodTypeDescriptors() {
final Set referencedClassNames = new HashSet<>();
if (methodInfo != null) {
for (final MethodInfo mi : methodInfo) {
final MethodTypeSignature methodSig = mi.getTypeSignature();
if (methodSig != null) {
methodSig.getAllReferencedClassNames(referencedClassNames);
}
}
}
// Remove any reference to java.lang.Object
referencedClassNames.remove("java.lang.Object");
return referencedClassNames;
}
/**
* Get the names of any classes referenced in the type descriptors of this class' fields (if field info is
* recorded).
*
* @return The names of the referenced classes.
*/
public Set getClassNamesReferencedInFieldTypeDescriptors() {
final Set referencedClassNames = new HashSet<>();
if (fieldInfo != null) {
for (final FieldInfo fi : fieldInfo) {
final TypeSignature fieldSig = fi.getTypeSignature();
if (fieldSig != null) {
fieldSig.getAllReferencedClassNames(referencedClassNames);
}
}
}
// Remove any reference to java.lang.Object
referencedClassNames.remove("java.lang.Object");
return referencedClassNames;
}
/**
* Get the names of any classes (other than this class itself) referenced in the type descriptor of this class.
*
* @return The names of the referenced classes.
*/
public Set getClassNamesReferencedInClassTypeDescriptor() {
final Set referencedClassNames = new HashSet<>();
final ClassTypeSignature classSig = getTypeSignature();
if (classSig != null) {
classSig.getAllReferencedClassNames(referencedClassNames);
}
// Remove self-reference, and any reference to java.lang.Object
referencedClassNames.remove(className);
referencedClassNames.remove("java.lang.Object");
return referencedClassNames;
}
// -------------------------------------------------------------------------------------------------------------
// Standard classes
/**
* Get the names of all classes, interfaces and annotations found during the scan, or the empty list if none.
*
* @return the sorted unique list of names of all classes, interfaces and annotations found during the scan, or
* the empty list if none.
*/
static List getNamesOfAllClasses(final ScanSpec scanSpec, final Set allClassInfo) {
return getClassNames(filterClassInfo(allClassInfo, /* removeExternalClassesIfStrictWhitelist = */ true,
scanSpec, ClassType.ALL));
}
/**
* Get the names of all standard (non-interface/annotation) classes found during the scan, or the empty list if
* none.
*
* @return the sorted unique names of all standard (non-interface/annotation) classes found during the scan, or
* the empty list if none.
*/
static List getNamesOfAllStandardClasses(final ScanSpec scanSpec, final Set allClassInfo) {
return getClassNames(filterClassInfo(allClassInfo, /* removeExternalClassesIfStrictWhitelist = */ true,
scanSpec, ClassType.STANDARD_CLASS));
}
/**
* Test whether this class is a standard class (not an annotation or interface).
*
* @return true if this class is a standard class (not an annotation or interface).
*/
public boolean isStandardClass() {
return !(isAnnotation || isInterface);
}
// -------------
/**
* Get the subclasses of this class.
*
* @return the set of subclasses of this class, or the empty set if none.
*/
public Set getSubclasses() {
// Make an exception for querying all subclasses of java.lang.Object
return filterClassInfo(getReachableClasses(RelType.SUBCLASSES),
/* removeExternalClassesIfStrictWhitelist = */ !className.equals("java.lang.Object"), scanSpec,
ClassType.ALL);
}
/**
* Get the names of subclasses of this class.
*
* @return the sorted list of names of the subclasses of this class, or the empty list if none.
*/
public List getNamesOfSubclasses() {
return getClassNames(getSubclasses());
}
/**
* Test whether this class has the named class as a subclass.
*
* @param subclassName
* The name of the subclass.
* @return true if this class has the named class as a subclass.
*/
public boolean hasSubclass(final String subclassName) {
return getNamesOfSubclasses().contains(subclassName);
}
// -------------
/**
* Get the direct subclasses of this class.
*
* @return the set of direct subclasses of this class, or the empty set if none.
*/
public Set getDirectSubclasses() {
// Make an exception for querying all direct subclasses of java.lang.Object
return filterClassInfo(getDirectlyRelatedClasses(RelType.SUBCLASSES),
/* removeExternalClassesIfStrictWhitelist = */ !className.equals("java.lang.Object"), scanSpec,
ClassType.ALL);
}
/**
* Get the names of direct subclasses of this class.
*
* @return the sorted list of names of direct subclasses of this class, or the empty list if none.
*/
public List getNamesOfDirectSubclasses() {
return getClassNames(getDirectSubclasses());
}
/**
* Test whether this class has the named direct subclass.
*
* @param directSubclassName
* The name of the direct subclass.
* @return true if this class has the named direct subclass.
*/
public boolean hasDirectSubclass(final String directSubclassName) {
return getNamesOfDirectSubclasses().contains(directSubclassName);
}
// -------------
/**
* Get all direct and indirect superclasses of this class (i.e. the direct superclass(es) of this class, and
* their superclass(es), all the way up to the top of the class hierarchy).
*
*
* (Includes the union of all mixin superclass hierarchies in the case of Scala mixins.)
*
* @return the set of all superclasses of this class, or the empty set if none.
*/
public Set getSuperclasses() {
return filterClassInfo(getReachableClasses(RelType.SUPERCLASSES),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of all direct and indirect superclasses of this class (i.e. the direct superclass(es) of this
* class, and their superclass(es), all the way up to the top of the class hierarchy).
*
*
* (Includes the union of all mixin superclass hierarchies in the case of Scala mixins.)
*
* @return the sorted list of names of all superclasses of this class, or the empty list if none.
*/
public List getNamesOfSuperclasses() {
return getClassNames(getSuperclasses());
}
/**
* Test whether this class extends the named superclass, directly or indirectly.
*
* @param superclassName
* The name of the superclass.
* @return true if this class has the named direct or indirect superclass.
*/
public boolean hasSuperclass(final String superclassName) {
return getNamesOfSuperclasses().contains(superclassName);
}
/**
* Returns true if this is an inner class (call isAnonymousInnerClass() to test if this is an anonymous inner
* class). If true, the containing class can be determined by calling getOuterClasses() or getOuterClassNames().
*
* @return True if this class is an inner class.
*/
public boolean isInnerClass() {
return !getOuterClasses().isEmpty();
}
/**
* Returns the containing outer classes, for inner classes. Note that all containing outer classes are returned,
* not just the innermost containing outer class. Returns the empty set if this is not an inner class.
*
* @return The set of containing outer classes.
*/
public Set getOuterClasses() {
return filterClassInfo(getReachableClasses(RelType.CONTAINED_WITHIN_OUTER_CLASS),
/* removeExternalClassesIfStrictWhitelist = */ false, scanSpec, ClassType.ALL);
}
/**
* Returns the names of the containing outer classes, for inner classes. Note that all containing outer classes
* are returned, not just the innermost containing outer class. Returns the empty list if this is not an inner
* class.
*
* @return The name of containing outer classes.
*/
public List getOuterClassName() {
return getClassNames(getOuterClasses());
}
/**
* Returns true if this class contains inner classes. If true, the inner classes can be determined by calling
* getInnerClasses() or getInnerClassNames().
*
* @return True if this is an outer class.
*/
public boolean isOuterClass() {
return !getInnerClasses().isEmpty();
}
/**
* Returns the inner classes contained within this class. Returns the empty set if none.
*
* @return The set of inner classes within this class.
*/
public Set getInnerClasses() {
return filterClassInfo(getReachableClasses(RelType.CONTAINS_INNER_CLASS),
/* removeExternalClassesIfStrictWhitelist = */ false, scanSpec, ClassType.ALL);
}
/**
* Returns the names of inner classes contained within this class. Returns the empty list if none.
*
* @return The names of inner classes within this class.
*/
public List getInnerClassNames() {
return getClassNames(getInnerClasses());
}
/**
* Returns true if this is an anonymous inner class. If true, the name of the containing method can be obtained
* by calling getFullyQualifiedContainingMethodName().
*
* @return True if this is an anonymous inner class.
*/
public boolean isAnonymousInnerClass() {
return fullyQualifiedContainingMethodName != null;
}
/**
* Get fully-qualified containing method name (i.e. fully qualified classname, followed by dot, followed by
* method name, for the containing method that creates an anonymous inner class.
*
* @return The fully-qualified method name of the method that this anonymous inner class was defined within.
*/
public String getFullyQualifiedContainingMethodName() {
return fullyQualifiedContainingMethodName;
}
// -------------
/**
* Get the direct superclasses of this class.
*
*
* Typically the returned set will contain zero or one direct superclass(es), but may contain more than one
* direct superclass in the case of Scala mixins.
*
* @return the direct superclasses of this class, or the empty set if none.
*/
public Set getDirectSuperclasses() {
return filterClassInfo(getDirectlyRelatedClasses(RelType.SUPERCLASSES),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Convenience method for getting the single direct superclass of this class. Returns null if the class does not
* have a superclass (e.g. in the case of interfaces). Throws IllegalArgumentException if there are multiple
* direct superclasses (e.g. in the case of Scala mixins) -- use getDirectSuperclasses() if you need to deal
* with mixins.
*
* @return the direct superclass of this class, or null if the class does not have a superclass.
* @throws IllegalArgumentException
* if there are multiple direct superclasses of this class (in the case of Scala mixins).
*/
public ClassInfo getDirectSuperclass() {
final Set directSuperclasses = getDirectSuperclasses();
final int numDirectSuperclasses = directSuperclasses.size();
if (numDirectSuperclasses == 0) {
return null;
} else if (numDirectSuperclasses > 1) {
throw new IllegalArgumentException("Class has multiple direct superclasses: "
+ directSuperclasses.toString() + " -- need to call getDirectSuperclasses() instead");
} else {
return directSuperclasses.iterator().next();
}
}
/**
* Get the names of direct superclasses of this class.
*
*
* Typically the returned list will contain zero or one direct superclass name(s), but may contain more than one
* direct superclass name in the case of Scala mixins.
*
* @return the direct superclasses of this class, or the empty set if none.
*/
public List getNamesOfDirectSuperclasses() {
return getClassNames(getDirectSuperclasses());
}
/**
* Convenience method for getting the name of the single direct superclass of this class. Returns null if the
* class does not have a superclass (e.g. in the case of interfaces). Throws IllegalArgumentException if there
* are multiple direct superclasses (e.g. in the case of Scala mixins) -- use getNamesOfDirectSuperclasses() if
* you need to deal with mixins.
*
* @return the name of the direct superclass of this class, or null if the class does not have a superclass.
* @throws IllegalArgumentException
* if there are multiple direct superclasses of this class (in the case of Scala mixins).
*/
public String getNameOfDirectSuperclass() {
final List namesOfDirectSuperclasses = getNamesOfDirectSuperclasses();
final int numDirectSuperclasses = namesOfDirectSuperclasses.size();
if (numDirectSuperclasses == 0) {
return null;
} else if (numDirectSuperclasses > 1) {
throw new IllegalArgumentException(
"Class has multiple direct superclasses: " + namesOfDirectSuperclasses.toString()
+ " -- need to call getNamesOfDirectSuperclasses() instead");
} else {
return namesOfDirectSuperclasses.get(0);
}
}
/**
* Test whether this class directly extends the named superclass.
*
*
* If this class has multiple direct superclasses (in the case of Scala mixins), returns true if the named
* superclass is one of the direct superclasses of this class.
*
* @param directSuperclassName
* The direct superclass name to match. If null, matches classes without a direct superclass (e.g.
* interfaces). Note that standard classes that do not extend another class have java.lang.Object as
* their superclass.
* @return true if this class has the named class as its direct superclass (or as one of its direct
* superclasses, in the case of Scala mixins).
*/
public boolean hasDirectSuperclass(final String directSuperclassName) {
final List namesOfDirectSuperclasses = getNamesOfDirectSuperclasses();
if (directSuperclassName == null && namesOfDirectSuperclasses.isEmpty()) {
return true;
} else if (directSuperclassName == null || namesOfDirectSuperclasses.isEmpty()) {
return false;
} else {
return namesOfDirectSuperclasses.contains(directSuperclassName);
}
}
// -------------------------------------------------------------------------------------------------------------
// Interfaces
/**
* Get the names of interface classes found during the scan.
*
* @return the sorted list of names of interface classes found during the scan, or the empty list if none.
*/
static List getNamesOfAllInterfaceClasses(final ScanSpec scanSpec, final Set allClassInfo) {
return getClassNames(filterClassInfo(allClassInfo, /* removeExternalClassesIfStrictWhitelist = */ true,
scanSpec, ClassType.IMPLEMENTED_INTERFACE));
}
/**
* Test whether this class is an "implemented interface" (meaning a standard, non-annotation interface, or an
* annotation that has also been implemented as an interface by some class).
*
*
* Annotations are interfaces, but you can also implement an annotation, so to we Test whether an interface
* (even an annotation) is implemented by a class or extended by a subinterface, or (failing that) if it is not
* an interface but not an annotation.
*
*
* (This is named "implemented interface" rather than just "interface" to distinguish it from an annotation.)
*
* @return true if this class is an "implemented interface".
*/
public boolean isImplementedInterface() {
return !getDirectlyRelatedClasses(RelType.CLASSES_IMPLEMENTING).isEmpty() || (isInterface && !isAnnotation);
}
// -------------
/**
* Get the subinterfaces of this interface.
*
* @return the set of subinterfaces of this interface, or the empty set if none.
*/
public Set getSubinterfaces() {
return !isImplementedInterface() ? Collections. emptySet()
: filterClassInfo(getReachableClasses(RelType.CLASSES_IMPLEMENTING),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec,
ClassType.IMPLEMENTED_INTERFACE);
}
/**
* Get the names of subinterfaces of this interface.
*
* @return the sorted list of names of subinterfaces of this interface, or the empty list if none.
*/
public List getNamesOfSubinterfaces() {
return getClassNames(getSubinterfaces());
}
/**
* Test whether this class is has the named subinterface.
*
* @param subinterfaceName
* The name of the subinterface.
* @return true if this class is an interface and has the named subinterface.
*/
public boolean hasSubinterface(final String subinterfaceName) {
return getNamesOfSubinterfaces().contains(subinterfaceName);
}
// -------------
/**
* Get the direct subinterfaces of this interface.
*
* @return the set of direct subinterfaces of this interface, or the empty set if none.
*/
public Set getDirectSubinterfaces() {
return !isImplementedInterface() ? Collections. emptySet()
: filterClassInfo(getDirectlyRelatedClasses(RelType.CLASSES_IMPLEMENTING),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec,
ClassType.IMPLEMENTED_INTERFACE);
}
/**
* Get the names of direct subinterfaces of this interface.
*
* @return the sorted list of names of direct subinterfaces of this interface, or the empty list if none.
*/
public List getNamesOfDirectSubinterfaces() {
return getClassNames(getDirectSubinterfaces());
}
/**
* Test whether this class is and interface and has the named direct subinterface.
*
* @param directSubinterfaceName
* The name of the direct subinterface.
* @return true if this class is and interface and has the named direct subinterface.
*/
public boolean hasDirectSubinterface(final String directSubinterfaceName) {
return getNamesOfDirectSubinterfaces().contains(directSubinterfaceName);
}
// -------------
/**
* Get the superinterfaces of this interface.
*
* @return the set of superinterfaces of this interface, or the empty set if none.
*/
public Set getSuperinterfaces() {
return !isImplementedInterface() ? Collections. emptySet()
: filterClassInfo(getReachableClasses(RelType.IMPLEMENTED_INTERFACES),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec,
ClassType.IMPLEMENTED_INTERFACE);
}
/**
* Get the names of superinterfaces of this interface.
*
* @return the sorted list of names of superinterfaces of this interface, or the empty list if none.
*/
public List getNamesOfSuperinterfaces() {
return getClassNames(getSuperinterfaces());
}
/**
* Test whether this class is an interface and has the named superinterface.
*
* @param superinterfaceName
* The name of the superinterface.
* @return true if this class is an interface and has the named superinterface.
*/
public boolean hasSuperinterface(final String superinterfaceName) {
return getNamesOfSuperinterfaces().contains(superinterfaceName);
}
// -------------
/**
* Get the direct superinterfaces of this interface.
*
* @return the set of direct superinterfaces of this interface, or the empty set if none.
*/
public Set getDirectSuperinterfaces() {
return !isImplementedInterface() ? Collections. emptySet()
: filterClassInfo(getDirectlyRelatedClasses(RelType.IMPLEMENTED_INTERFACES),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec,
ClassType.IMPLEMENTED_INTERFACE);
}
/**
* Get the names of direct superinterfaces of this interface.
*
* @return the sorted list of names of direct superinterfaces of this interface, or the empty list if none.
*/
public List getNamesOfDirectSuperinterfaces() {
return getClassNames(getDirectSuperinterfaces());
}
/**
* Test whether this class is an interface and has the named direct superinterface.
*
* @param directSuperinterfaceName
* The name of the direct superinterface.
* @return true if this class is an interface and has the named direct superinterface.
*/
public boolean hasDirectSuperinterface(final String directSuperinterfaceName) {
return getNamesOfDirectSuperinterfaces().contains(directSuperinterfaceName);
}
// -------------
/**
* Get the interfaces implemented by this standard class, or by one of its superclasses.
*
* @return the set of interfaces implemented by this standard class, or by one of its superclasses. Returns the
* empty set if none.
*/
public Set getImplementedInterfaces() {
if (!isStandardClass()) {
return Collections. emptySet();
} else {
final Set superclasses = filterClassInfo(getReachableClasses(RelType.SUPERCLASSES),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.STANDARD_CLASS);
// Subclasses of implementing classes also implement the interface
final Set allInterfaces = new HashSet<>();
allInterfaces.addAll(getReachableClasses(RelType.IMPLEMENTED_INTERFACES));
for (final ClassInfo superClass : superclasses) {
allInterfaces.addAll(superClass.getReachableClasses(RelType.IMPLEMENTED_INTERFACES));
}
return allInterfaces;
}
}
/**
* Get the interfaces implemented by this standard class, or by one of its superclasses.
*
* @return the set of interfaces implemented by this standard class, or by one of its superclasses. Returns the
* empty list if none.
*/
public List getNamesOfImplementedInterfaces() {
return getClassNames(getImplementedInterfaces());
}
/**
* Test whether this standard class implements the named interface, or by one of its superclasses.
*
* @param interfaceName
* The name of the interface.
* @return true this class is a standard class, and it (or one of its superclasses) implements the named
* interface.
*/
public boolean implementsInterface(final String interfaceName) {
return getNamesOfImplementedInterfaces().contains(interfaceName);
}
// -------------
/**
* Get the interfaces directly implemented by this standard class.
*
* @return the set of interfaces directly implemented by this standard class. Returns the empty set if none.
*/
public Set getDirectlyImplementedInterfaces() {
return !isStandardClass() ? Collections. emptySet()
: filterClassInfo(getDirectlyRelatedClasses(RelType.IMPLEMENTED_INTERFACES),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec,
ClassType.IMPLEMENTED_INTERFACE);
}
/**
* Get the interfaces directly implemented by this standard class, or by one of its superclasses.
*
* @return the set of interfaces directly implemented by this standard class, or by one of its superclasses.
* Returns the empty list if none.
*/
public List getNamesOfDirectlyImplementedInterfaces() {
return getClassNames(getDirectlyImplementedInterfaces());
}
/**
* Test whether this standard class directly implements the named interface, or by one of its superclasses.
*
* @param interfaceName
* The name of the interface.
* @return true this class is a standard class, and directly implements the named interface.
*/
public boolean directlyImplementsInterface(final String interfaceName) {
return getNamesOfDirectlyImplementedInterfaces().contains(interfaceName);
}
// -------------
/**
* Get the classes that implement this interface, and their subclasses.
*
* @return the set of classes implementing this interface, or the empty set if none.
*/
public Set getClassesImplementing() {
if (!isImplementedInterface()) {
return Collections. emptySet();
} else {
final Set implementingClasses = filterClassInfo(
getReachableClasses(RelType.CLASSES_IMPLEMENTING),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.STANDARD_CLASS);
// Subclasses of implementing classes also implement the interface
final Set allImplementingClasses = new HashSet<>();
for (final ClassInfo implementingClass : implementingClasses) {
allImplementingClasses.add(implementingClass);
allImplementingClasses.addAll(implementingClass.getReachableClasses(RelType.SUBCLASSES));
}
return allImplementingClasses;
}
}
/**
* Get the names of classes that implement this interface, and the names of their subclasses.
*
* @return the sorted list of names of classes implementing this interface, or the empty list if none.
*/
public List getNamesOfClassesImplementing() {
return getClassNames(getClassesImplementing());
}
/**
* Test whether this class is implemented by the named class, or by one of its superclasses.
*
* @param className
* The name of the class.
* @return true if this class is implemented by the named class, or by one of its superclasses.
*/
public boolean isImplementedByClass(final String className) {
return getNamesOfClassesImplementing().contains(className);
}
// -------------
/**
* Get the classes that directly implement this interface, and their subclasses.
*
* @return the set of classes directly implementing this interface, or the empty set if none.
*/
public Set getClassesDirectlyImplementing() {
return !isImplementedInterface() ? Collections. emptySet()
: filterClassInfo(getDirectlyRelatedClasses(RelType.CLASSES_IMPLEMENTING),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.STANDARD_CLASS);
}
/**
* Get the names of classes that directly implement this interface, and the names of their subclasses.
*
* @return the sorted list of names of classes directly implementing this interface, or the empty list if none.
*/
public List getNamesOfClassesDirectlyImplementing() {
return getClassNames(getClassesDirectlyImplementing());
}
/**
* Test whether this class is directly implemented by the named class, or by one of its superclasses.
*
* @param className
* The name of the class.
* @return true if this class is directly implemented by the named class, or by one of its superclasses.
*/
public boolean isDirectlyImplementedByClass(final String className) {
return getNamesOfClassesDirectlyImplementing().contains(className);
}
// -------------------------------------------------------------------------------------------------------------
// Annotations
/**
* Get the names of all annotation classes found during the scan.
*
* @return the sorted list of names of annotation classes found during the scan, or the empty list if none.
*/
static List getNamesOfAllAnnotationClasses(final ScanSpec scanSpec, final Set allClassInfo) {
return getClassNames(filterClassInfo(allClassInfo, /* removeExternalClassesIfStrictWhitelist = */ true,
scanSpec, ClassType.ANNOTATION));
}
// -------------
/**
* Get the standard classes and non-annotation interfaces that are annotated by this annotation.
*
* @param direct
* if true, return only directly-annotated classes.
* @return the set of standard classes and non-annotation interfaces that are annotated by the annotation
* corresponding to this ClassInfo class, or the empty set if none.
*/
private Set getClassesWithAnnotation(final boolean direct) {
if (!isAnnotation()) {
return Collections. emptySet();
}
final Set classesWithAnnotation = filterClassInfo(
direct ? getDirectlyRelatedClasses(RelType.CLASSES_WITH_CLASS_ANNOTATION)
: getReachableClasses(RelType.CLASSES_WITH_CLASS_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, //
ClassType.STANDARD_CLASS, ClassType.IMPLEMENTED_INTERFACE);
boolean isInherited = false;
for (final ClassInfo metaAnnotation : getDirectlyRelatedClasses(RelType.CLASS_ANNOTATIONS)) {
if (metaAnnotation.className.equals("java.lang.annotation.Inherited")) {
isInherited = true;
break;
}
}
if (isInherited) {
final Set classesWithAnnotationAndTheirSubclasses = new HashSet<>(classesWithAnnotation);
for (final ClassInfo classWithAnnotation : classesWithAnnotation) {
classesWithAnnotationAndTheirSubclasses.addAll(classWithAnnotation.getSubclasses());
}
return classesWithAnnotationAndTheirSubclasses;
} else {
return classesWithAnnotation;
}
}
/**
* Get the standard classes and non-annotation interfaces that are annotated by this annotation.
*
* @return the set of standard classes and non-annotation interfaces that are annotated by the annotation
* corresponding to this ClassInfo class, or the empty set if none.
*/
public Set getClassesWithAnnotation() {
return getClassesWithAnnotation(/* direct = */ false);
}
/**
* Get the names of standard classes and non-annotation interfaces that are annotated by this annotation. .
*
* @return the sorted list of names of ClassInfo objects for standard classes and non-annotation interfaces that
* are annotated by the annotation corresponding to this ClassInfo class, or the empty list if none.
*/
public List getNamesOfClassesWithAnnotation() {
return getClassNames(getClassesWithAnnotation());
}
/**
* Test whether this class annotates the named class.
*
* @param annotatedClassName
* The name of the annotated class.
* @return True if this class annotates the named class.
*/
public boolean annotatesClass(final String annotatedClassName) {
return getNamesOfClassesWithAnnotation().contains(annotatedClassName);
}
// -------------
/**
* Get the standard classes or non-annotation interfaces that are directly annotated with this annotation.
*
* @return the set of standard classes or non-annotation interfaces that are directly annotated with this
* annotation, or the empty set if none.
*/
public Set getClassesWithDirectAnnotation() {
return getClassesWithAnnotation(/* direct = */ true);
}
/**
* Get the names of standard classes or non-annotation interfaces that are directly annotated with this
* annotation.
*
* @return the sorted list of names of standard classes or non-annotation interfaces that are directly annotated
* with this annotation, or the empty list if none.
*/
public List getNamesOfClassesWithDirectAnnotation() {
return getClassNames(getClassesWithDirectAnnotation());
}
/**
* Test whether this class directly annotates the named class.
*
* @param directlyAnnotatedClassName
* The name of the directly annotated class.
* @return true if this class directly annotates the named class.
*/
public boolean directlyAnnotatesClass(final String directlyAnnotatedClassName) {
return getNamesOfClassesWithDirectAnnotation().contains(directlyAnnotatedClassName);
}
// -------------
/**
* Get the annotations and meta-annotations on this class. This is equivalent to the reflection call
* Class#getAnnotations(), except that it does not require calling the classloader, and it returns
* meta-annotations as well as annotations.
*
* @return the set of annotations and meta-annotations on this class or interface, or meta-annotations if this
* is an annotation. Returns the empty set if none.
*/
public Set getAnnotations() {
return filterClassInfo(getReachableClasses(RelType.CLASS_ANNOTATIONS),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of annotations and meta-annotations on this class. This is equivalent to the reflection call
* Class#getAnnotations(), except that it does not require calling the classloader, and it returns
* meta-annotations as well as annotations.
*
* @return the sorted list of names of annotations and meta-annotations on this class or interface, or
* meta-annotations if this is an annotation. Returns the empty list if none.
*/
public List getNamesOfAnnotations() {
return getClassNames(getAnnotations());
}
/**
* Test whether this class, interface or annotation has the named class annotation or meta-annotation.
*
* @param annotationName
* The name of the annotation.
* @return true if this class, interface or annotation has the named class annotation or meta-annotation.
*/
public boolean hasAnnotation(final String annotationName) {
return getNamesOfAnnotations().contains(annotationName);
}
/**
* Get a list of annotations on this method, along with any annotation parameter values, wrapped in
* {@link AnnotationInfo} objects, or the empty list if none.
*
* @return A list of {@link AnnotationInfo} objects for the annotations on this method, or the empty list if
* none.
*/
public List getAnnotationInfo() {
return annotationInfo == null ? Collections. emptyList() : annotationInfo;
}
/**
* Get a list of the default parameter values, if this is an annotation, and it has default parameter values.
* Otherwise returns the empty list.
*
* @return If this is an annotation class, the list of {@link AnnotationParamValue} objects for each of the
* default parameter values for this annotation, otherwise the empty list.
*/
public List getAnnotationDefaultParamValues() {
return annotationDefaultParamValues == null ? Collections. emptyList()
: annotationDefaultParamValues;
}
// -------------
/**
* Get the direct annotations and meta-annotations on this class. This is equivalent to the reflection call
* Class#getAnnotations(), except that it does not require calling the classloader, and it returns
* meta-annotations as well as annotations.
*
* @return the set of direct annotations and meta-annotations on this class, or the empty set if none.
*/
public Set getDirectAnnotations() {
return filterClassInfo(getDirectlyRelatedClasses(RelType.CLASS_ANNOTATIONS),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of direct annotations and meta-annotations on this class. This is equivalent to the reflection
* call Class#getAnnotations(), except that it does not require calling the classloader, and it returns
* meta-annotations as well as annotations.
*
* @return the sorted list of names of direct annotations and meta-annotations on this class, or the empty list
* if none.
*/
public List getNamesOfDirectAnnotations() {
return getClassNames(getDirectAnnotations());
}
/**
* Test whether this class has the named direct annotation or meta-annotation. (This is equivalent to the
* reflection call Class#hasAnnotation(), except that it does not require calling the classloader, and it works
* for meta-annotations as well as Annotatinons.)
*
* @param directAnnotationName
* The name of the direct annotation.
* @return true if this class has the named direct annotation or meta-annotation.
*/
public boolean hasDirectAnnotation(final String directAnnotationName) {
return getNamesOfDirectAnnotations().contains(directAnnotationName);
}
// -------------
/**
* Get the annotations and meta-annotations on this annotation class.
*
* @return the set of annotations and meta-annotations, if this is an annotation class, or the empty set if none
* (or if this is not an annotation class).
*/
public Set getMetaAnnotations() {
return !isAnnotation() ? Collections. emptySet()
: filterClassInfo(getReachableClasses(RelType.CLASS_ANNOTATIONS),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of annotations and meta-annotations on this annotation class.
*
* @return the set of annotations and meta-annotations, if this is an annotation class, or the empty list if
* none (or if this is not an annotation class).
*/
public List getNamesOfMetaAnnotations() {
return getClassNames(getMetaAnnotations());
}
/**
* Test whether this is an annotation class and it has the named meta-annotation.
*
* @param metaAnnotationName
* The meta-annotation name.
* @return true if this is an annotation class and it has the named meta-annotation.
*/
public boolean hasMetaAnnotation(final String metaAnnotationName) {
return getNamesOfMetaAnnotations().contains(metaAnnotationName);
}
// -------------
/**
* Get the annotations that have this meta-annotation.
*
* @return the set of annotations that have this meta-annotation, or the empty set if none.
*/
public Set getAnnotationsWithMetaAnnotation() {
return !isAnnotation() ? Collections. emptySet()
: filterClassInfo(getReachableClasses(RelType.CLASSES_WITH_CLASS_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ANNOTATION);
}
/**
* Get the names of annotations that have this meta-annotation.
*
* @return the sorted list of names of annotations that have this meta-annotation, or the empty list if none.
*/
public List getNamesOfAnnotationsWithMetaAnnotation() {
return getClassNames(getAnnotationsWithMetaAnnotation());
}
/**
* Test whether this annotation has the named meta-annotation.
*
* @param annotationName
* The annotation name.
* @return true if this annotation has the named meta-annotation.
*/
public boolean metaAnnotatesAnnotation(final String annotationName) {
return getNamesOfAnnotationsWithMetaAnnotation().contains(annotationName);
}
// -------------
/**
* Get the annotations that have this direct meta-annotation.
*
* @return the set of annotations that have this direct meta-annotation, or the empty set if none.
*/
public Set getAnnotationsWithDirectMetaAnnotation() {
return !isAnnotation() ? Collections. emptySet()
: filterClassInfo(getDirectlyRelatedClasses(RelType.CLASSES_WITH_CLASS_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ANNOTATION);
}
/**
* Get the names of annotations that have this direct meta-annotation.
*
* @return the sorted list of names of annotations that have this direct meta-annotation, or the empty list if
* none.
*/
public List getNamesOfAnnotationsWithDirectMetaAnnotation() {
return getClassNames(getAnnotationsWithDirectMetaAnnotation());
}
/**
* Test whether this annotation is directly meta-annotated with the named annotation.
*
* @param directMetaAnnotationName
* The direct meta-annotation name.
* @return true if this annotation is directly meta-annotated with the named annotation.
*/
public boolean hasDirectMetaAnnotation(final String directMetaAnnotationName) {
return getNamesOfAnnotationsWithDirectMetaAnnotation().contains(directMetaAnnotationName);
}
// -------------------------------------------------------------------------------------------------------------
// Methods
/**
* Returns information on visible methods of the class that are not constructors. There may be more than one
* method of a given name with different type signatures, due to overloading.
*
*
* Requires that FastClasspathScanner#enableMethodInfo() be called before scanning, otherwise throws
* IllegalArgumentException.
*
*
* By default only returns information for public methods, unless FastClasspathScanner#ignoreMethodVisibility()
* was called before the scan. If method visibility is ignored, the result may include a reference to a private
* static class initializer block, with a method name of {@code ""}.
*
* @return the list of MethodInfo objects for visible methods of this class, or the empty list if no methods
* were found or visible.
* @throws IllegalArgumentException
* if FastClasspathScanner#enableMethodInfo() was not called prior to initiating the scan.
*/
public List getMethodInfo() {
if (!scanSpec.enableMethodInfo) {
throw new IllegalArgumentException("Cannot get method info without calling "
+ "FastClasspathScanner#enableMethodInfo() before starting the scan");
}
if (methodInfo == null) {
return Collections. emptyList();
} else {
final List nonConstructorMethods = new ArrayList<>();
for (final MethodInfo mi : methodInfo) {
final String methodName = mi.getMethodName();
if (!methodName.equals("") && !methodName.equals("")) {
nonConstructorMethods.add(mi);
}
}
return nonConstructorMethods;
}
}
/**
* Returns information on visible constructors of the class. Constructors have the method name of
* {@code ""}. There may be more than one constructor of a given name with different type signatures, due
* to overloading.
*
*
* Requires that FastClasspathScanner#enableMethodInfo() be called before scanning, otherwise throws
* IllegalArgumentException.
*
*
* By default only returns information for public constructors, unless
* FastClasspathScanner#ignoreMethodVisibility() was called before the scan.
*
* @return the list of MethodInfo objects for visible constructors of this class, or the empty list if no
* constructors were found or visible.
* @throws IllegalArgumentException
* if FastClasspathScanner#enableMethodInfo() was not called prior to initiating the scan.
*/
public List getConstructorInfo() {
if (!scanSpec.enableMethodInfo) {
throw new IllegalArgumentException("Cannot get method info without calling "
+ "FastClasspathScanner#enableMethodInfo() before starting the scan");
}
if (methodInfo == null) {
return Collections. emptyList();
} else {
final List nonConstructorMethods = new ArrayList<>();
for (final MethodInfo mi : methodInfo) {
final String methodName = mi.getMethodName();
if (methodName.equals("")) {
nonConstructorMethods.add(mi);
}
}
return nonConstructorMethods;
}
}
/**
* Returns information on visible methods and constructors of the class. There may be more than one method or
* constructor or method of a given name with different type signatures, due to overloading. Constructors have
* the method name of {@code ""}.
*
*
* Requires that FastClasspathScanner#enableMethodInfo() be called before scanning, otherwise throws
* IllegalArgumentException.
*
*
* By default only returns information for public methods and constructors, unless
* FastClasspathScanner#ignoreMethodVisibility() was called before the scan. If method visibility is ignored,
* the result may include a reference to a private static class initializer block, with a method name of
* {@code ""}.
*
* @return the list of MethodInfo objects for visible methods and constructors of this class, or the empty list
* if no methods or constructors were found or visible.
* @throws IllegalArgumentException
* if FastClasspathScanner#enableMethodInfo() was not called prior to initiating the scan.
*/
public List getMethodAndConstructorInfo() {
if (!scanSpec.enableMethodInfo) {
throw new IllegalArgumentException("Cannot get method info without calling "
+ "FastClasspathScanner#enableMethodInfo() before starting the scan");
}
return methodInfo == null ? Collections. emptyList() : methodInfo;
}
/**
* Returns information on the method(s) of the class with the given method name. Constructors have the method
* name of {@code ""}.
*
*
* Requires that FastClasspathScanner#enableMethodInfo() be called before scanning, otherwise throws
* IllegalArgumentException.
*
*
* By default only returns information for public methods, unless FastClasspathScanner#ignoreMethodVisibility()
* was called before the scan.
*
*
* May return info for multiple methods with the same name (with different type signatures).
*
* @param methodName
* The method name to query.
* @return a list of MethodInfo objects for the method(s) with the given name, or the empty list if the method
* was not found in this class (or is not visible).
* @throws IllegalArgumentException
* if FastClasspathScanner#enableMethodInfo() was not called prior to initiating the scan.
*/
public List getMethodInfo(final String methodName) {
if (!scanSpec.enableMethodInfo) {
throw new IllegalArgumentException("Cannot get method info without calling "
+ "FastClasspathScanner#enableMethodInfo() before starting the scan");
}
if (methodInfo == null) {
return null;
}
if (methodNameToMethodInfo == null) {
// Lazily build reverse mapping cache
methodNameToMethodInfo = new MultiMapKeyToList<>();
for (final MethodInfo f : methodInfo) {
methodNameToMethodInfo.put(f.getMethodName(), f);
}
}
final List methodList = methodNameToMethodInfo.get(methodName);
return methodList == null ? Collections. emptyList() : methodList;
}
// -------------------------------------------------------------------------------------------------------------
// Method annotations
/**
* Get the direct method direct annotations on this class.
*
* @return the set of method direct annotations on this class, or the empty set if none.
*/
public Set getMethodDirectAnnotations() {
return filterClassInfo(getDirectlyRelatedClasses(RelType.METHOD_ANNOTATIONS),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ANNOTATION);
}
/**
* Get the method annotations or meta-annotations on this class.
*
* @return the set of method annotations or meta-annotations on this class, or the empty set if none.
*/
public Set getMethodAnnotations() {
return filterClassInfo(getReachableClasses(RelType.METHOD_ANNOTATIONS),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ANNOTATION);
}
/**
* Get the names of method direct annotations on this class.
*
* @return the sorted list of names of method direct annotations on this class, or the empty list if none.
*/
public List getNamesOfMethodDirectAnnotations() {
return getClassNames(getMethodDirectAnnotations());
}
/**
* Get the names of method annotations or meta-annotations on this class.
*
* @return the sorted list of names of method annotations or meta-annotations on this class, or the empty list
* if none.
*/
public List getNamesOfMethodAnnotations() {
return getClassNames(getMethodAnnotations());
}
/**
* Test whether this class has a method with the named method direct annotation.
*
* @param annotationName
* The annotation name.
* @return true if this class has a method with the named direct annotation.
*/
public boolean hasMethodWithDirectAnnotation(final String annotationName) {
return getNamesOfMethodDirectAnnotations().contains(annotationName);
}
/**
* Test whether this class has a method with the named method annotation or meta-annotation.
*
* @param annotationName
* The annotation name.
* @return true if this class has a method with the named annotation or meta-annotation.
*/
public boolean hasMethodWithAnnotation(final String annotationName) {
return getNamesOfMethodAnnotations().contains(annotationName);
}
// -------------
/**
* Get the classes that have a method with this direct annotation.
*
* @return the set of classes that have a method with this direct annotation, or the empty set if none.
*/
public Set getClassesWithDirectMethodAnnotation() {
return filterClassInfo(getDirectlyRelatedClasses(RelType.CLASSES_WITH_METHOD_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the classes that have a method with this annotation or meta-annotation.
*
* @return the set of classes that have a method with this annotation or meta-annotation, or the empty set if
* none.
*/
public Set getClassesWithMethodAnnotation() {
return filterClassInfo(getReachableClasses(RelType.CLASSES_WITH_METHOD_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of classes that have a method with this direct annotation.
*
* @return the sorted list of names of classes that have a method with this direct annotation, or the empty list
* if none.
*/
public List getNamesOfClassesWithDirectMethodAnnotation() {
return getClassNames(getClassesWithDirectMethodAnnotation());
}
/**
* Get the names of classes that have a method with this annotation.
*
* @return the sorted list of names of classes that have a method with this annotation, or the empty list if
* none.
*/
public List getNamesOfClassesWithMethodAnnotation() {
return getClassNames(getClassesWithMethodAnnotation());
}
/**
* Test whether this annotation annotates or meta-annotates a method of the named class.
*
* @param className
* The class name.
* @return true if this annotation annotates a method of the named class.
*/
public boolean annotatesMethodOfClass(final String className) {
return getNamesOfClassesWithMethodAnnotation().contains(className);
}
/**
* Return a sorted list of classes that have a method directly annotated with the named annotation.
*
* @return the sorted list of names of classes that have a method with the named direct annotation, or the empty
* list if none.
*/
static List getNamesOfClassesWithDirectMethodAnnotation(final String annotationName,
final Set allClassInfo) {
// This method will not likely be used for a large number of different annotation types, so perform a linear
// search on each invocation, rather than building an index on classpath scan (so we don't slow down more
// common methods).
final ArrayList namesOfClassesWithNamedMethodAnnotation = new ArrayList<>();
for (final ClassInfo classInfo : allClassInfo) {
for (final ClassInfo annotationType : classInfo.getDirectlyRelatedClasses(RelType.METHOD_ANNOTATIONS)) {
if (annotationType.className.equals(annotationName)) {
namesOfClassesWithNamedMethodAnnotation.add(classInfo.className);
break;
}
}
}
if (!namesOfClassesWithNamedMethodAnnotation.isEmpty()) {
Collections.sort(namesOfClassesWithNamedMethodAnnotation);
}
return namesOfClassesWithNamedMethodAnnotation;
}
/**
* Return a sorted list of classes that have a method with the named annotation or meta-annotation.
*
* @return the sorted list of names of classes that have a method with the named annotation or meta-annotation,
* or the empty list if none.
*/
static List getNamesOfClassesWithMethodAnnotation(final String annotationName,
final Set allClassInfo) {
// This method will not likely be used for a large number of different annotation types, so perform a linear
// search on each invocation, rather than building an index on classpath scan (so we don't slow down more
// common methods).
final ArrayList namesOfClassesWithNamedMethodAnnotation = new ArrayList<>();
for (final ClassInfo classInfo : allClassInfo) {
for (final ClassInfo annotationType : classInfo.getReachableClasses(RelType.METHOD_ANNOTATIONS)) {
if (annotationType.className.equals(annotationName)) {
namesOfClassesWithNamedMethodAnnotation.add(classInfo.className);
break;
}
}
}
if (!namesOfClassesWithNamedMethodAnnotation.isEmpty()) {
Collections.sort(namesOfClassesWithNamedMethodAnnotation);
}
return namesOfClassesWithNamedMethodAnnotation;
}
// -------------------------------------------------------------------------------------------------------------
// Fields
/**
* Get the constant initializer value for the named static final field, if present.
*
* @return the constant initializer value for the named static final field, if present.
*/
Object getStaticFinalFieldConstantInitializerValue(final String fieldName) {
return staticFinalFieldNameToConstantInitializerValue == null ? null
: staticFinalFieldNameToConstantInitializerValue.get(fieldName);
}
/**
* Returns information on all visible fields of the class.
*
*
* Requires that FastClasspathScanner#enableFieldInfo() be called before scanning, otherwise throws
* IllegalArgumentException.
*
*
* By default only returns information for public methods, unless FastClasspathScanner#ignoreFieldVisibility()
* was called before the scan.
*
* @return the list of FieldInfo objects for visible fields of this class, or the empty list if no fields were
* found or visible.
* @throws IllegalArgumentException
* if FastClasspathScanner#enableFieldInfo() was not called prior to initiating the scan.
*/
public List getFieldInfo() {
if (!scanSpec.enableFieldInfo) {
throw new IllegalArgumentException("Cannot get field info without calling "
+ "FastClasspathScanner#enableFieldInfo() before starting the scan");
}
return fieldInfo == null ? Collections. emptyList() : fieldInfo;
}
/**
* Returns information on a given visible field of the class.
*
*
* Requires that FastClasspathScanner#enableFieldInfo() be called before scanning, otherwise throws
* IllegalArgumentException.
*
*
* By default only returns information for public fields, unless FastClasspathScanner#ignoreFieldVisibility()
* was called before the scan.
*
* @param fieldName
* The field name to query.
* @return the FieldInfo object for the named field, or null if the field was not found in this class (or is not
* visible).
* @throws IllegalArgumentException
* if FastClasspathScanner#enableFieldInfo() was not called prior to initiating the scan.
*/
public FieldInfo getFieldInfo(final String fieldName) {
if (!scanSpec.enableFieldInfo) {
throw new IllegalArgumentException("Cannot get field info without calling "
+ "FastClasspathScanner#enableFieldInfo() before starting the scan");
}
if (fieldInfo == null) {
return null;
}
if (fieldNameToFieldInfo == null) {
// Lazily build reverse mapping cache
fieldNameToFieldInfo = new HashMap<>();
for (final FieldInfo f : fieldInfo) {
fieldNameToFieldInfo.put(f.getFieldName(), f);
}
}
return fieldNameToFieldInfo.get(fieldName);
}
// -------------------------------------------------------------------------------------------------------------
// Field annotations
/**
* Get the field annotations on this class.
*
* @return the set of field annotations on this class, or the empty set if none.
*/
public Set getFieldAnnotations() {
return filterClassInfo(getDirectlyRelatedClasses(RelType.FIELD_ANNOTATIONS),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ANNOTATION);
}
/**
* Get the names of field annotations on this class.
*
* @return the sorted list of names of field annotations on this class, or the empty list if none.
*/
public List getNamesOfFieldAnnotations() {
return getClassNames(getFieldAnnotations());
}
/**
* Test whether this class has a field with the named field annotation.
*
* @param annotationName
* The annotation name.
* @return true if this class has a field with the named annotation.
*/
public boolean hasFieldWithAnnotation(final String annotationName) {
return getNamesOfFieldAnnotations().contains(annotationName);
}
// -------------
/**
* Get the classes that have a field with this annotation or meta-annotation.
*
* @return the set of classes that have a field with this annotation or meta-annotation, or the empty set if
* none.
*/
public Set getClassesWithFieldAnnotation() {
return filterClassInfo(getReachableClasses(RelType.CLASSES_WITH_FIELD_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of classes that have a field with this annotation or meta-annotation.
*
* @return the sorted list of names of classes that have a field with this annotation or meta-annotation, or the
* empty list if none.
*/
public List getNamesOfClassesWithFieldAnnotation() {
return getClassNames(getClassesWithFieldAnnotation());
}
/**
* Get the classes that have a field with this direct annotation.
*
* @return the set of classes that have a field with this direct annotation, or the empty set if none.
*/
public Set getClassesWithDirectFieldAnnotation() {
return filterClassInfo(getDirectlyRelatedClasses(RelType.CLASSES_WITH_FIELD_ANNOTATION),
/* removeExternalClassesIfStrictWhitelist = */ true, scanSpec, ClassType.ALL);
}
/**
* Get the names of classes that have a field with this direct annotation.
*
* @return the sorted list of names of classes that have a field with thisdirect annotation, or the empty list
* if none.
*/
public List getNamesOfClassesWithDirectFieldAnnotation() {
return getClassNames(getClassesWithDirectFieldAnnotation());
}
/**
* Test whether this annotation annotates a field of the named class.
*
* @param className
* The class name.
* @return true if this annotation annotates a field of the named class.
*/
public boolean annotatesFieldOfClass(final String className) {
return getNamesOfClassesWithFieldAnnotation().contains(className);
}
/**
* Return a sorted list of classes that have a field with the named annotation or meta-annotation.
*
* @return the sorted list of names of classes that have a field with the named annotation or meta-annotation,
* or the empty list if none.
*/
static List getNamesOfClassesWithFieldAnnotation(final String annotationName,
final Set allClassInfo) {
// This method will not likely be used for a large number of different annotation types, so perform a linear
// search on each invocation, rather than building an index on classpath scan (so we don't slow down more
// common methods).
final ArrayList namesOfClassesWithNamedFieldAnnotation = new ArrayList<>();
for (final ClassInfo classInfo : allClassInfo) {
for (final ClassInfo annotationType : classInfo.getReachableClasses(RelType.FIELD_ANNOTATIONS)) {
if (annotationType.className.equals(annotationName)) {
namesOfClassesWithNamedFieldAnnotation.add(classInfo.className);
break;
}
}
}
if (!namesOfClassesWithNamedFieldAnnotation.isEmpty()) {
Collections.sort(namesOfClassesWithNamedFieldAnnotation);
}
return namesOfClassesWithNamedFieldAnnotation;
}
/**
* Return a sorted list of classes that have a field with the named annotation or direct annotation.
*
* @return the sorted list of names of classes that have a field with the named direct annotation, or the empty
* list if none.
*/
static List getNamesOfClassesWithDirectFieldAnnotation(final String annotationName,
final Set allClassInfo) {
// This method will not likely be used for a large number of different annotation types, so perform a linear
// search on each invocation, rather than building an index on classpath scan (so we don't slow down more
// common methods).
final ArrayList namesOfClassesWithNamedFieldAnnotation = new ArrayList<>();
for (final ClassInfo classInfo : allClassInfo) {
for (final ClassInfo annotationType : classInfo.getDirectlyRelatedClasses(RelType.FIELD_ANNOTATIONS)) {
if (annotationType.className.equals(annotationName)) {
namesOfClassesWithNamedFieldAnnotation.add(classInfo.className);
break;
}
}
}
if (!namesOfClassesWithNamedFieldAnnotation.isEmpty()) {
Collections.sort(namesOfClassesWithNamedFieldAnnotation);
}
return namesOfClassesWithNamedFieldAnnotation;
}
}