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

io.github.classgraph.Scanner Maven / Gradle / Ivy

Go to download

The uber-fast, ultra-lightweight classpath and module scanner for JVM languages.

There is a newer version: 4.8.179
Show newest version
/*
 * This file is part of ClassGraph.
 *
 * Author: Luke Hutchison
 *
 * Hosted at: https://github.com/classgraph/classgraph
 *
 * --
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 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.classgraph;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;

import io.github.classgraph.ClassGraph.FailureHandler;
import io.github.classgraph.ClassGraph.ScanResultProcessor;
import nonapi.io.github.classgraph.ScanSpec;
import nonapi.io.github.classgraph.classpath.ClassLoaderAndModuleFinder;
import nonapi.io.github.classgraph.classpath.ClasspathFinder;
import nonapi.io.github.classgraph.concurrency.InterruptionChecker;
import nonapi.io.github.classgraph.concurrency.SingletonMap;
import nonapi.io.github.classgraph.concurrency.WorkQueue;
import nonapi.io.github.classgraph.concurrency.WorkQueue.WorkUnitProcessor;
import nonapi.io.github.classgraph.fastzipfilereader.NestedJarHandler;
import nonapi.io.github.classgraph.utils.FastPathResolver;
import nonapi.io.github.classgraph.utils.FileUtils;
import nonapi.io.github.classgraph.utils.JarUtils;
import nonapi.io.github.classgraph.utils.LogNode;

/** The classpath scanner. */
class Scanner implements Callable {
    private final ScanSpec scanSpec;
    private final ExecutorService executorService;
    private final int numParallelTasks;
    private final InterruptionChecker interruptionChecker = new InterruptionChecker();
    private final ScanResultProcessor scanResultProcessor;
    private final FailureHandler failureHandler;
    private final LogNode topLevelLog;

    /** The classpath scanner. */
    Scanner(final ScanSpec scanSpec, final ExecutorService executorService, final int numParallelTasks,
            final ScanResultProcessor scannResultProcessor, final FailureHandler failureHandler,
            final LogNode log) {
        this.scanSpec = scanSpec;
        scanSpec.sortPrefixes();

        this.executorService = executorService;
        this.numParallelTasks = numParallelTasks;
        this.scanResultProcessor = scannResultProcessor;
        this.failureHandler = failureHandler;
        this.topLevelLog = log;

        // Add ScanSpec to beginning of log
        scanSpec.log(log);
    }

    // -------------------------------------------------------------------------------------------------------------

    /**
     * Recursively perform a depth-first search of jar interdependencies, breaking cycles if necessary, to determine
     * the final classpath element order.
     */
    private static void findClasspathOrderRec(final ClasspathElement currClasspathElement,
            final HashSet visitedClasspathElts, final ArrayList order) {
        if (visitedClasspathElts.add(currClasspathElement)) {
            if (!currClasspathElement.skipClasspathElement) {
                // Don't add a classpath element if it is marked to be skipped.
                order.add(currClasspathElement);
            }
            // Whether or not a classpath element should be skipped, add any child classpath elements that are
            // not marked to be skipped (i.e. keep recursing)
            for (final ClasspathElement childClasspathElt : currClasspathElement.childClasspathElementsOrdered) {
                findClasspathOrderRec(childClasspathElt, visitedClasspathElts, order);
            }
        }
    }

    /** Comparator used to sort ClasspathElement values into increasing order of integer index key */
    private static final Comparator> INDEXED_CLASSPATH_ELEMENT_COMPARATOR = //
            new Comparator>() {
                @Override
                public int compare(final Entry o1,
                        final Entry o2) {
                    return o1.getKey() - o2.getKey();
                }
            };

    /** Sort a collection of indexed ClasspathElements into increasing order of integer index key. */
    private static List orderClasspathElements(
            final Collection> classpathEltsIndexed) {
        final List> classpathEltsIndexedOrdered = new ArrayList<>(
                classpathEltsIndexed);
        Collections.sort(classpathEltsIndexedOrdered, INDEXED_CLASSPATH_ELEMENT_COMPARATOR);
        final List classpathEltsOrdered = new ArrayList<>(classpathEltsIndexedOrdered.size());
        for (final Entry ent : classpathEltsIndexedOrdered) {
            classpathEltsOrdered.add(ent.getValue());
        }
        return classpathEltsOrdered;
    }

    /**
     * Recursively perform a depth-first traversal of child classpath elements, breaking cycles if necessary, to
     * determine the final classpath element order. This causes child classpath elements to be inserted in-place in
     * the classpath order, after the parent classpath element that contained them.
     */
    private List findClasspathOrder(final Set openedClasspathElementsSet,
            final Queue> toplevelClasspathEltsIndexed) {
        final List toplevelClasspathEltsOrdered = orderClasspathElements(
                toplevelClasspathEltsIndexed);
        for (final ClasspathElement classpathElt : openedClasspathElementsSet) {
            classpathElt.childClasspathElementsOrdered = orderClasspathElements(
                    classpathElt.childClasspathElementsIndexed);
        }
        final HashSet visitedClasspathElts = new HashSet<>();
        final ArrayList order = new ArrayList<>();
        for (final ClasspathElement toplevelClasspathElt : toplevelClasspathEltsOrdered) {
            findClasspathOrderRec(toplevelClasspathElt, visitedClasspathElts, order);
        }
        return order;
    }

    // -------------------------------------------------------------------------------------------------------------

    /** Used to enqueue classpath elements for opening. */
    static class RawClasspathElementWorkUnit {
        final String rawClasspathEltPath;
        final ClasspathElement parentClasspathElement;
        final int orderWithinParentClasspathElement;

        public RawClasspathElementWorkUnit(final String rawClasspathEltPath,
                final ClasspathElement parentClasspathElement, final int orderWithinParentClasspathElement) {
            this.rawClasspathEltPath = rawClasspathEltPath;
            this.parentClasspathElement = parentClasspathElement;
            this.orderWithinParentClasspathElement = orderWithinParentClasspathElement;
        }
    }

    /** Used to enqueue classfiles for scanning. */
    private static class ClassfileScanWorkUnit {
        final ClasspathElement classpathElement;
        final Resource classfileResource;
        final boolean isExternalClass;

        ClassfileScanWorkUnit(final ClasspathElement classpathElement, final Resource classfileResource,
                final boolean isExternalClass) {
            this.classpathElement = classpathElement;
            this.classfileResource = classfileResource;
            this.isExternalClass = isExternalClass;
        }
    }

    /** WorkUnitProcessor for scanning classfiles. */
    private static class ClassfileScannerWorkUnitProcessor implements WorkUnitProcessor {
        private final ScanSpec scanSpec;
        private final List classpathOrder;
        private final Set scannedClassNames;
        private final ConcurrentLinkedQueue classInfoUnlinkedQueue;
        private final LogNode log;
        private final InterruptionChecker interruptionChecker;

        public ClassfileScannerWorkUnitProcessor(final ScanSpec scanSpec,
                final List classpathOrder, final Set scannedClassNames,
                final ConcurrentLinkedQueue classInfoUnlinkedQueue, final LogNode log,
                final InterruptionChecker interruptionChecker) {
            this.scanSpec = scanSpec;
            this.classpathOrder = classpathOrder;
            this.scannedClassNames = scannedClassNames;
            this.classInfoUnlinkedQueue = classInfoUnlinkedQueue;
            this.log = log;
            this.interruptionChecker = interruptionChecker;
        }

        /** Extend scanning to a superclass, interface or annotation. */
        private List extendScanningUpwards(final String className, final String relationship,
                final ClasspathElement currClasspathElement,
                final List additionalWorkUnitsIn, final LogNode subLog) {
            List additionalWorkUnits = additionalWorkUnitsIn;
            // Don't scan a class more than once 
            if (className != null && scannedClassNames.add(className)) {
                // Search for the named class' classfile among classpath elements, in classpath order (this is O(N)
                // for each class, but there shouldn't be too many cases of extending scanning upwards)
                final String classfilePath = JarUtils.classNameToClassfilePath(className);
                // First check current classpath element, to avoid iterating through other classpath elements
                Resource classResource = currClasspathElement.getResource(classfilePath);
                ClasspathElement foundInClasspathElt = null;
                if (classResource != null) {
                    // Found the classfile in the current classpath element
                    foundInClasspathElt = currClasspathElement;
                } else {
                    // Didn't find the classfile in the current classpath element -- iterate through other elements
                    for (final ClasspathElement classpathElt : classpathOrder) {
                        if (classpathElt != currClasspathElement) {
                            classResource = classpathElt.getResource(classfilePath);
                            if (classResource != null) {
                                foundInClasspathElt = classpathElt;
                                break;
                            }
                        }
                    }
                }
                if (classResource != null) {
                    // Found class resource 
                    if (subLog != null) {
                        subLog.log("Scheduling external class for scanning: " + relationship + " " + className
                                + " -- found in classpath element " + foundInClasspathElt);
                    }
                    if (additionalWorkUnits == null) {
                        additionalWorkUnits = new ArrayList<>();
                    }
                    additionalWorkUnits.add(new ClassfileScanWorkUnit(foundInClasspathElt, classResource,
                            /* isExternalClass = */ true));
                } else {
                    if (subLog != null && !className.equals("java.lang.Object")) {
                        subLog.log("External " + relationship + " " + className + " was not found in "
                                + "non-blacklisted packages -- cannot extend scanning to this class");
                    }
                }
            }
            return additionalWorkUnits;
        }

        /** Check if scanning needs to be extended upwards to an external superclass, interface or annotation. */
        private List extendScanningUpwards(final ClasspathElement classpathElement,
                final ClassInfoUnlinked classInfoUnlinked, final LogNode subLog) {
            // Check superclass
            List additionalWorkUnits = null;
            additionalWorkUnits = extendScanningUpwards(classInfoUnlinked.superclassName, "superclass",
                    classpathElement, additionalWorkUnits, subLog);
            // Check implemented interfaces
            if (classInfoUnlinked.implementedInterfaces != null) {
                for (final String className : classInfoUnlinked.implementedInterfaces) {
                    additionalWorkUnits = extendScanningUpwards(className, "interface", classpathElement,
                            additionalWorkUnits, subLog);
                }
            }
            // Check class annotations
            if (classInfoUnlinked.classAnnotations != null) {
                for (final AnnotationInfo annotationInfo : classInfoUnlinked.classAnnotations) {
                    additionalWorkUnits = extendScanningUpwards(annotationInfo.getName(), "class annotation",
                            classpathElement, additionalWorkUnits, subLog);
                }
            }
            // Check method annotations and method parameter annotations
            if (classInfoUnlinked.methodInfoList != null) {
                for (final MethodInfo methodInfo : classInfoUnlinked.methodInfoList) {
                    if (methodInfo.annotationInfo != null) {
                        for (final AnnotationInfo methodAnnotationInfo : methodInfo.annotationInfo) {
                            additionalWorkUnits = extendScanningUpwards(methodAnnotationInfo.getName(),
                                    "method annotation", classpathElement, additionalWorkUnits, subLog);
                        }
                        if (methodInfo.parameterAnnotationInfo != null
                                && methodInfo.parameterAnnotationInfo.length > 0) {
                            for (final AnnotationInfo[] paramAnns : methodInfo.parameterAnnotationInfo) {
                                if (paramAnns != null && paramAnns.length > 0) {
                                    for (final AnnotationInfo paramAnn : paramAnns) {
                                        additionalWorkUnits = extendScanningUpwards(paramAnn.getName(),
                                                "method parameter annotation", classpathElement,
                                                additionalWorkUnits, subLog);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            // Check field annotations
            if (classInfoUnlinked.fieldInfoList != null) {
                for (final FieldInfo fieldInfo : classInfoUnlinked.fieldInfoList) {
                    if (fieldInfo.annotationInfo != null) {
                        for (final AnnotationInfo fieldAnnotationInfo : fieldInfo.annotationInfo) {
                            additionalWorkUnits = extendScanningUpwards(fieldAnnotationInfo.getName(),
                                    "field annotation", classpathElement, additionalWorkUnits, subLog);
                        }
                    }
                }
            }
            return additionalWorkUnits;
        }

        @Override
        public void processWorkUnit(final ClassfileScanWorkUnit workUnit,
                final WorkQueue workQueue) throws Exception {
            final LogNode subLog = log == null ? null
                    : log.log(workUnit.classfileResource.getPath(),
                            "Parsing classfile " + workUnit.classfileResource);
            try {
                // Parse classfile binary format, creating a ClassInfoUnlinked object
                final ClassInfoUnlinked classInfoUnlinked = new ClassfileBinaryParser()
                        .readClassInfoFromClassfileHeader(workUnit.classpathElement,
                                workUnit.classfileResource.getPath(), workUnit.classfileResource,
                                workUnit.isExternalClass, scanSpec, subLog);

                // If class was successfully read, output new ClassInfoUnlinked object
                if (classInfoUnlinked != null) {
                    classInfoUnlinkedQueue.add(classInfoUnlinked);
                    classInfoUnlinked.logTo(subLog);

                    // Check if any superclasses, interfaces or annotations are external (non-whitelisted) classes
                    if (scanSpec.extendScanningUpwardsToExternalClasses) {
                        final List additionalWorkUnits = extendScanningUpwards(
                                workUnit.classpathElement, classInfoUnlinked, subLog);
                        // If any external classes were found, schedule them for scanning
                        if (additionalWorkUnits != null) {
                            workQueue.addWorkUnits(additionalWorkUnits);
                        }
                    }
                }
                if (subLog != null) {
                    subLog.addElapsedTime();
                }
            } catch (final IOException e) {
                if (subLog != null) {
                    subLog.log("IOException while attempting to read classfile " + workUnit.classfileResource
                            + " : " + e);
                }
            } catch (final IllegalArgumentException e) {
                if (subLog != null) {
                    subLog.log("Corrupt or unsupported classfile " + workUnit.classfileResource + " : " + e);
                }
            } catch (final Throwable e) {
                if (subLog != null) {
                    subLog.log("Exception while parsing classfile " + workUnit.classfileResource, e);
                }
            }
            interruptionChecker.check();
        }
    }

    // -------------------------------------------------------------------------------------------------------------

    /** Find classpath elements whose path is a prefix of another classpath element, and record the nesting. */
    private void findNestedClasspathElements(final List> classpathElts,
            final LogNode log) {
        // Sort classpath elements into lexicographic order
        Collections.sort(classpathElts, new Comparator>() {
            @Override
            public int compare(final SimpleEntry o1,
                    final SimpleEntry o2) {
                return o1.getKey().compareTo(o2.getKey());
            }
        });
        // Find any nesting of elements within other elements
        LogNode nestedClasspathRootNode = null;
        for (int i = 0; i < classpathElts.size(); i++) {
            // See if each classpath element is a prefix of any others (if so, they will immediately follow
            // in lexicographic order)
            final SimpleEntry ei = classpathElts.get(i);
            final String basePath = ei.getKey();
            final int basePathLen = basePath.length();
            for (int j = i + 1; j < classpathElts.size(); j++) {
                final SimpleEntry ej = classpathElts.get(j);
                final String comparePath = ej.getKey();
                final int comparePathLen = comparePath.length();
                boolean foundNestedClasspathRoot = false;
                if (comparePath.startsWith(basePath) && comparePathLen > basePathLen) {
                    // Require a separator after the prefix
                    final char nextChar = comparePath.charAt(basePathLen);
                    if (nextChar == '/' || nextChar == '!') {
                        // basePath is a path prefix of comparePath. Ensure that the nested classpath does
                        // not contain another '!' zip-separator (since classpath scanning does not recurse
                        // to jars-within-jars unless they are explicitly listed on the classpath)
                        final String nestedClasspathRelativePath = comparePath.substring(basePathLen + 1);
                        if (nestedClasspathRelativePath.indexOf('!') < 0) {
                            // Found a nested classpath root
                            foundNestedClasspathRoot = true;
                            // Store link from prefix element to nested elements
                            final ClasspathElement baseElement = ei.getValue();
                            if (baseElement.nestedClasspathRootPrefixes == null) {
                                baseElement.nestedClasspathRootPrefixes = new ArrayList<>();
                            }
                            baseElement.nestedClasspathRootPrefixes.add(nestedClasspathRelativePath + "/");
                            if (log != null) {
                                if (nestedClasspathRootNode == null) {
                                    nestedClasspathRootNode = log.log("Found nested classpath elements");
                                }
                                nestedClasspathRootNode
                                        .log(basePath + " is a prefix of the nested element " + comparePath);
                            }
                        }
                    }
                }
                if (!foundNestedClasspathRoot) {
                    // After the first non-match, there can be no more prefix matches in the sorted order
                    break;
                }
            }
        }
    }

    // -------------------------------------------------------------------------------------------------------------

    /**
     * Determine the unique ordered classpath elements, and run a scan looking for file or classfile matches if
     * necessary.
     */
    @Override
    public ScanResult call() throws InterruptedException, ExecutionException {
        final LogNode classpathFinderLog = topLevelLog == null ? null
                : topLevelLog.log("Finding classpath entries");
        final NestedJarHandler nestedJarHandler = new NestedJarHandler(scanSpec, classpathFinderLog);
        final Map classpathEltPathToClassLoaders = new ConcurrentHashMap<>();
        try {
            final long scanStart = System.nanoTime();

            // Get classpath finder
            final ClasspathFinder classpathFinder = new ClasspathFinder(scanSpec, classpathEltPathToClassLoaders,
                    nestedJarHandler, classpathFinderLog);
            final ClassLoaderAndModuleFinder classLoaderAndModuleFinder = classpathFinder
                    .getClassLoaderAndModuleFinder();
            final ClassLoader[] contextClassLoaders = classLoaderAndModuleFinder.getContextClassLoaders();

            final List moduleClasspathEltOrder = new ArrayList<>();
            if (scanSpec.overrideClasspath == null && scanSpec.overrideClassLoaders == null
                    && scanSpec.scanModules) {
                // Add modules to start of classpath order, before traditional classpath
                final List systemModuleRefs = classLoaderAndModuleFinder.getSystemModuleRefs();
                if (systemModuleRefs != null) {
                    for (final ModuleRef systemModuleRef : systemModuleRefs) {
                        final String moduleName = systemModuleRef.getName();
                        if (
                        // If scanning system packages and modules is enabled and white/blacklist is empty,
                        // then scan all system modules
                        (scanSpec.enableSystemJarsAndModules
                                && scanSpec.moduleWhiteBlackList.whitelistAndBlacklistAreEmpty())
                                // Otherwise only scan specifically whitelisted system modules
                                || scanSpec.moduleWhiteBlackList
                                        .isSpecificallyWhitelistedAndNotBlacklisted(moduleName)) {
                            final ClasspathElementModule classpathElementModule = new ClasspathElementModule(
                                    systemModuleRef, contextClassLoaders, nestedJarHandler, scanSpec);
                            classpathElementModule.open(/* ignored */ null, classpathFinderLog);
                            moduleClasspathEltOrder.add(classpathElementModule);
                        } else {
                            if (classpathFinderLog != null) {
                                classpathFinderLog.log(
                                        "Skipping non-whitelisted or blacklisted system module: " + moduleName);
                            }
                        }
                    }
                }
                final List nonSystemModuleRefs = classLoaderAndModuleFinder.getNonSystemModuleRefs();
                if (nonSystemModuleRefs != null) {
                    for (final ModuleRef nonSystemModuleRef : nonSystemModuleRefs) {
                        final String moduleName = nonSystemModuleRef.getName();
                        if (scanSpec.moduleWhiteBlackList.isWhitelistedAndNotBlacklisted(moduleName)) {
                            final ClasspathElementModule classpathElementModule = new ClasspathElementModule(
                                    nonSystemModuleRef, contextClassLoaders, nestedJarHandler, scanSpec);
                            classpathElementModule.open(/* ignored */ null, classpathFinderLog);
                            moduleClasspathEltOrder.add(classpathElementModule);
                        } else {
                            if (classpathFinderLog != null) {
                                classpathFinderLog
                                        .log("Skipping non-whitelisted or blacklisted module: " + moduleName);
                            }
                        }
                    }
                }
            }

            // Get order of elements in traditional classpath
            final List rawClasspathElementWorkUnits = new ArrayList<>();
            for (final String rawClasspathEltPath : classpathFinder.getClasspathOrder().getOrder()) {
                rawClasspathElementWorkUnits.add(
                        new RawClasspathElementWorkUnit(rawClasspathEltPath, /* parentClasspathElement = */ null,
                                /* orderWithinParentClasspathElement = */ rawClasspathElementWorkUnits.size()));
            }

            // For each classpath element path, canonicalize path, and create a ClasspathElement singleton
            final SingletonMap classpathElementSingletonMap = //
                    new SingletonMap() {
                        @Override
                        public ClasspathElement newInstance(final String classpathEltPath, final LogNode log)
                                throws Exception {
                            final ClassLoader[] classLoaders = classpathEltPathToClassLoaders.get(classpathEltPath);
                            if (classpathEltPath.regionMatches(true, 0, "http://", 0, 7)
                                    || classpathEltPath.regionMatches(true, 0, "https://", 0, 8)) {
                                // For remote URLs, must be a jar
                                return new ClasspathElementZip(classpathEltPath, classLoaders, nestedJarHandler,
                                        scanSpec);
                            }
                            // Normalize path -- strip off any leading "jar:" / "file:", and normalize separators
                            final String pathNormalized = FastPathResolver.resolve(FileUtils.CURR_DIR_PATH,
                                    classpathEltPath);
                            // Strip "jar:" and/or "file:" prefix, if present
                            // Strip everything after first "!", to get path of base jarfile or dir
                            final int plingIdx = pathNormalized.indexOf("!");
                            final String pathToCanonicalize = plingIdx < 0 ? pathNormalized
                                    : pathNormalized.substring(0, plingIdx);
                            // Canonicalize base jarfile or dir (may throw IOException)
                            final File fileCanonicalized = new File(pathToCanonicalize).getCanonicalFile();
                            // Test if base file or dir exists (and is a standard file or dir)
                            if (!fileCanonicalized.exists()) {
                                throw new FileNotFoundException();
                            }
                            if (!FileUtils.canRead(fileCanonicalized)) {
                                throw new IOException("Cannot read file or directory");
                            }
                            boolean isJar = classpathEltPath.regionMatches(true, 0, "jar:", 0, 4) || plingIdx > 0;
                            if (fileCanonicalized.isFile()) {
                                // If a file, must be a jar
                                isJar = true;
                            } else if (fileCanonicalized.isDirectory()) {
                                if (isJar) {
                                    throw new IOException("Expected jar, found directory");
                                }
                            } else {
                                throw new IOException("Not a normal file or directory");
                            }
                            // Check if canonicalized path is the same as pre-canonicalized path
                            final String baseFileCanonicalPathNormalized = FastPathResolver
                                    .resolve(FileUtils.CURR_DIR_PATH, fileCanonicalized.getPath());
                            final String canonicalPathNormalized = plingIdx < 0 ? baseFileCanonicalPathNormalized
                                    : baseFileCanonicalPathNormalized + pathNormalized.substring(plingIdx);
                            if (!canonicalPathNormalized.equals(pathNormalized)) {
                                // If canonicalized path is not the same as pre-canonicalized path, need to recurse
                                // to map non-canonicalized path to singleton for canonicalized path (this should
                                // only recurse once, since File::getCanonicalFile and FastPathResolver::resolve are
                                // idempotent)
                                return this.get(canonicalPathNormalized, log);
                            } else {
                                // Otherwise path is already canonical, and this is the first time this path has
                                // been seen -- instantiate a ClasspathElementZip or ClasspathElementDir singleton
                                // for the classpath element path
                                return isJar
                                        ? new ClasspathElementZip(canonicalPathNormalized, classLoaders,
                                                nestedJarHandler, scanSpec)
                                        : new ClasspathElementDir(fileCanonicalized, classLoaders, scanSpec);
                            }
                        }
                    };

            // In parallel, create a ClasspathElement singleton for each classpath element, then call isValid()
            // on each ClasspathElement object, which in the case of jarfiles will cause LogicalZipFile instances
            // to be created for each (possibly nested) jarfile, then will read the manifest file and zip entries.
            final LogNode preScanLog = classpathFinderLog == null ? null
                    : classpathFinderLog.log("Reading jarfile directories and manifest files");
            final Set openedClasspathElementsSet = Collections
                    .newSetFromMap(new ConcurrentHashMap());
            final Queue> toplevelClasspathEltOrder = new ConcurrentLinkedQueue<>();
            WorkQueue.runWorkQueue(rawClasspathElementWorkUnits, executorService, numParallelTasks,
                    new WorkUnitProcessor() {
                        @Override
                        public void processWorkUnit(final RawClasspathElementWorkUnit workUnit,
                                final WorkQueue workQueue) throws Exception {
                            try {
                                // Create a ClasspathElementZip or ClasspathElementDir for each entry in the
                                // traditional classpath
                                final ClasspathElement classpathElt = classpathElementSingletonMap
                                        .get(workUnit.rawClasspathEltPath, classpathFinderLog);

                                // Only run open() once per ClasspathElement (it is possible for there to be
                                // multiple classpath elements with different non-canonical paths that map to
                                // the same canonical path, i.e. to the same ClasspathElement)
                                if (openedClasspathElementsSet.add(classpathElt)) {
                                    // Check if the classpath element is valid (classpathElt.skipClasspathElement
                                    // will be set if not). In case of ClasspathElementZip, open or extract nested
                                    // jars as LogicalZipFile instances. Read manifest files for jarfiles to look
                                    // for Class-Path manifest entries. Adds extra classpath elements to the work
                                    // queue if they are found.
                                    classpathElt.open(workQueue, preScanLog);

                                    // Create a new tuple consisting of the order of the new classpath element
                                    // within its parent, and the new classpath element
                                    final SimpleEntry classpathEltEntry = //
                                            new SimpleEntry<>(workUnit.orderWithinParentClasspathElement,
                                                    classpathElt);
                                    if (workUnit.parentClasspathElement != null) {
                                        // Link classpath element to its parent, if it is not a toplevel element
                                        workUnit.parentClasspathElement.childClasspathElementsIndexed
                                                .add(classpathEltEntry);
                                    } else {
                                        // Record toplevel elements
                                        toplevelClasspathEltOrder.add(classpathEltEntry);
                                    }
                                }
                            } catch (final IllegalArgumentException e) {
                                if (classpathFinderLog != null) {
                                    classpathFinderLog.log(
                                            "Skipping invalid classpath element " + workUnit.rawClasspathEltPath);
                                }
                            } catch (final IOException e) {
                                if (classpathFinderLog != null) {
                                    classpathFinderLog.log("Skipping invalid classpath element "
                                            + workUnit.rawClasspathEltPath + " : " + e);
                                }
                            } catch (final Exception e) {
                                if (classpathFinderLog != null) {
                                    classpathFinderLog.log("Uncaught exception while processing classpath element "
                                            + workUnit.rawClasspathEltPath + " : " + e);
                                }
                            }
                        }
                    }, interruptionChecker, preScanLog);

            // Determine total ordering of classpath elements, inserting jars referenced in manifest Class-Path
            // entries in-place into the ordering, if they haven't been listed earlier in the classpath already.
            final List finalTraditionalClasspathEltOrder = findClasspathOrder(
                    openedClasspathElementsSet, toplevelClasspathEltOrder);

            // Find classpath elements that are path prefixes of other classpath elements
            {
                final List> classpathEltDirs = new ArrayList<>();
                final List> classpathEltZips = new ArrayList<>();
                for (final ClasspathElement classpathElt : finalTraditionalClasspathEltOrder) {
                    if (classpathElt instanceof ClasspathElementDir) {
                        classpathEltDirs.add(new SimpleEntry<>(
                                ((ClasspathElementDir) classpathElt).getDirFile().getPath(), classpathElt));
                    } else if (classpathElt instanceof ClasspathElementZip) {
                        classpathEltZips.add(new SimpleEntry<>(
                                ((ClasspathElementZip) classpathElt).getZipFilePath(), classpathElt));
                    }
                }
                // Find nested classpath elements (writes to ClasspathElement#nestedClasspathRootPrefixes)
                findNestedClasspathElements(classpathEltDirs, classpathFinderLog);
                findNestedClasspathElements(classpathEltZips, classpathFinderLog);
            }

            // Order modules before classpath elements from traditional classpath 
            final LogNode classpathOrderLog = classpathFinderLog == null ? null
                    : classpathFinderLog.log("Final classpath element order:");
            final int numElts = moduleClasspathEltOrder.size() + finalTraditionalClasspathEltOrder.size();
            final List finalClasspathEltOrder = new ArrayList<>(numElts);
            final List finalClasspathEltOrderStrs = new ArrayList<>(numElts);
            for (final ClasspathElementModule classpathElt : moduleClasspathEltOrder) {
                finalClasspathEltOrder.add(classpathElt);
                finalClasspathEltOrderStrs.add(classpathElt.toString());
                if (classpathOrderLog != null) {
                    final ModuleRef moduleRef = classpathElt.getModuleRef();
                    classpathOrderLog.log(moduleRef.toString());
                }
            }
            for (final ClasspathElement classpathElt : finalTraditionalClasspathEltOrder) {
                finalClasspathEltOrder.add(classpathElt);
                finalClasspathEltOrderStrs.add(classpathElt.toString());
                if (classpathOrderLog != null) {
                    classpathOrderLog.log(classpathElt.toString());
                }
            }

            // If only getting classpath, not performing a scan
            if (!scanSpec.performScan) {
                if (topLevelLog != null) {
                    topLevelLog.log("Only returning classpath elements (not performing a scan)");
                }
                // This is the result of a call to ClassGraph#getUniqueClasspathElements(), so just
                // create placeholder ScanResult to contain classpathElementFilesOrdered.
                final ScanResult scanResult = new ScanResult(scanSpec, finalClasspathEltOrder,
                        finalClasspathEltOrderStrs, contextClassLoaders, /* classNameToClassInfo = */ null,
                        /* packageNameToPackageInfo = */ null, /* moduleNameToModuleInfo = */ null,
                        /* fileToLastModified = */ null, nestedJarHandler, topLevelLog);

                if (topLevelLog != null) {
                    topLevelLog.log("Completed", System.nanoTime() - scanStart);
                }

                // Skip the actual scan
                return scanResult;
            }

            // Scan paths within each classpath element, comparing them against whitelist/blacklist criteria
            final LogNode pathScanLog = classpathFinderLog == null ? null
                    : classpathFinderLog.log("Scanning filenames within classpath elements");
            WorkQueue.runWorkQueue(finalClasspathEltOrder, executorService, numParallelTasks,
                    new WorkUnitProcessor() {
                        @Override
                        public void processWorkUnit(final ClasspathElement classpathElement,
                                final WorkQueue workQueueIgnored) {
                            // Scan the paths within a directory or jar
                            classpathElement.scanPaths(pathScanLog);
                            if (preScanLog != null) {
                                preScanLog.addElapsedTime();
                            }
                        }
                    }, interruptionChecker, pathScanLog);
            if (preScanLog != null) {
                preScanLog.addElapsedTime();
            }

            // Filter out classpath elements that do not contain required whitelisted paths.
            List finalClasspathEltOrderFiltered = finalClasspathEltOrder;
            if (!scanSpec.classpathElementResourcePathWhiteBlackList.whitelistIsEmpty()) {
                finalClasspathEltOrderFiltered = new ArrayList<>(finalClasspathEltOrder.size());
                for (final ClasspathElement classpathElement : finalClasspathEltOrder) {
                    if (classpathElement.containsSpecificallyWhitelistedClasspathElementResourcePath) {
                        finalClasspathEltOrderFiltered.add(classpathElement);
                    }
                }
            }

            // Implement classpath masking -- if the same relative classfile path occurs multiple times in the
            // classpath, ignore (remove) the second and subsequent occurrences. Note that classpath masking is
            // performed whether or not a jar is whitelisted, and whether or not jar or dir scanning is enabled,
            // in order to ensure that class references passed into MatchProcessors are the same as those that
            // would be loaded by standard classloading. (See bug #100.)
            {
                final LogNode maskLog = topLevelLog == null ? null : topLevelLog.log("Masking classfiles");
                final HashSet nonBlacklistedClasspathRelativePathsFound = new HashSet<>();
                final HashSet whitelistedClasspathRelativePathsFound = new HashSet<>();
                for (int classpathIdx = 0; classpathIdx < finalClasspathEltOrderFiltered.size(); classpathIdx++) {
                    finalClasspathEltOrderFiltered.get(classpathIdx).maskClassfiles(classpathIdx,
                            whitelistedClasspathRelativePathsFound, nonBlacklistedClasspathRelativePathsFound,
                            maskLog);
                }
            }

            // Merge the maps from file to timestamp across all classpath elements (there will be no overlap in
            // keyspace, since file masking was already performed)
            final Map fileToLastModified = new HashMap<>();
            for (final ClasspathElement classpathElement : finalClasspathEltOrderFiltered) {
                fileToLastModified.putAll(classpathElement.fileToLastModified);
            }

            final Map classNameToClassInfo = new HashMap<>();
            final Map packageNameToPackageInfo = new HashMap<>();
            final Map moduleNameToModuleInfo = new HashMap<>();
            if (!scanSpec.enableClassInfo) {
                if (topLevelLog != null) {
                    topLevelLog.log("Classfile scanning is disabled");
                }
            } else {
                // Get whitelisted classfile order
                final List classfileScanWorkItems = new ArrayList<>();
                final Set scannedClassNames = Collections
                        .newSetFromMap(new ConcurrentHashMap());
                for (final ClasspathElement classpathElement : finalClasspathEltOrderFiltered) {
                    // Get classfile scan order across all classpath elements
                    for (final Resource resource : classpathElement.whitelistedClassfileResources) {
                        classfileScanWorkItems.add(
                                new ClassfileScanWorkUnit(classpathElement, resource, /* isExternal = */ false));
                        // Pre-seed scanned class names with all whitelisted classes (since these will
                        // be scanned for sure)
                        scannedClassNames.add(JarUtils.classfilePathToClassName(resource.getPath()));
                    }
                }

                // Scan classfiles in parallel
                final ConcurrentLinkedQueue classInfoUnlinkedQueue = //
                        new ConcurrentLinkedQueue<>();
                final LogNode classfileScanLog = topLevelLog == null ? null
                        : topLevelLog.log("Scanning classfiles");
                WorkQueue.runWorkQueue(classfileScanWorkItems, executorService, numParallelTasks,
                        new ClassfileScannerWorkUnitProcessor(scanSpec, finalClasspathEltOrderFiltered,
                                scannedClassNames, classInfoUnlinkedQueue, classfileScanLog, interruptionChecker),
                        interruptionChecker, classfileScanLog);
                if (classfileScanLog != null) {
                    classfileScanLog.addElapsedTime();
                }

                // Build the class graph: convert ClassInfoUnlinked to linked ClassInfo objects.
                final LogNode classGraphLog = topLevelLog == null ? null : topLevelLog.log("Building class graph");
                for (final ClassInfoUnlinked c : classInfoUnlinkedQueue) {
                    c.link(scanSpec, classNameToClassInfo, packageNameToPackageInfo, moduleNameToModuleInfo,
                            classGraphLog);
                }

                // Uncomment the following code to create placeholder external classes for any classes
                // referenced in type descriptors or type signatures, so that a ClassInfo object can be
                // obtained for those class references. This will cause all type descriptors and type
                // signatures to be parsed, and class names extracted from them. This will add some
                // overhead to the scanning time, and the only benefit is that
                // ClassRefTypeSignature.getClassInfo() and AnnotationClassRef.getClassInfo() will never
                // return null, since all external classes found in annotation class refs will have a
                // placeholder ClassInfo object created for them. This is obscure enough that it is
                // probably not worth slowing down scanning for all other usecases, by forcibly parsing
                // all type descriptors and type signatures before returning the ScanResult.
                // With this code commented out, type signatures and type descriptors are only parsed
                // lazily, on demand.

                //    final Set referencedClassNames = new HashSet<>();
                //    for (final ClassInfo classInfo : classNameToClassInfo.values()) {
                //        classInfo.getReferencedClassNames(referencedClassNames);
                //    }
                //    for (final String referencedClass : referencedClassNames) {
                //        ClassInfo.getOrCreateClassInfo(referencedClass, /* modifiers = */ 0, scanSpec,
                //                classNameToClassInfo);
                //    }

                if (classGraphLog != null) {
                    classGraphLog.addElapsedTime();
                }
            }

            // Create ScanResult
            final ScanResult scanResult = new ScanResult(scanSpec, finalClasspathEltOrder,
                    finalClasspathEltOrderStrs, contextClassLoaders, classNameToClassInfo, packageNameToPackageInfo,
                    moduleNameToModuleInfo, fileToLastModified, nestedJarHandler, topLevelLog);

            if (topLevelLog != null) {
                topLevelLog.log("Completed", System.nanoTime() - scanStart);
            }

            // Run scanResultProcessor in the current thread
            if (scanResultProcessor != null) {
                try {
                    scanResultProcessor.processScanResult(scanResult);
                } catch (final Throwable e) {
                    throw new ExecutionException("Exception while calling scan result processor", e);
                }
            }

            // No exceptions were thrown -- return scan result
            return scanResult;

        } catch (final Throwable e) {
            // Remove temporary files if an exception was thrown
            nestedJarHandler.close(topLevelLog);
            if (topLevelLog != null) {
                topLevelLog.log("Exception while scanning", e);
            }
            if (failureHandler != null) {
                try {
                    failureHandler.onFailure(e);
                    // The return value is discarded when using a scanResultProcessor and failureHandler
                    return null;
                } catch (final Throwable t) {
                    throw new ExecutionException("Exception while calling failure handler", t);
                }
            } else {
                throw new ExecutionException("Exception while scanning", e);
            }

        } finally {
            if (scanSpec.removeTemporaryFilesAfterScan) {
                // If requested, remove temporary files and close zipfile/module recyclers
                nestedJarHandler.close(topLevelLog);
            }
            if (topLevelLog != null) {
                topLevelLog.flush();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy