All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.lukehutch.fastclasspathscanner.scanner.ClassInfo Maven / Gradle / Ivy

Go to download

Uber-fast, ultra-lightweight Java classpath scanner. Scans the classpath by parsing the classfile binary format directly rather than by using reflection. See https://github.com/lukehutch/fast-classpath-scanner

There is a newer version: 4.0.0-beta-7
Show newest version
/*
 * 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy