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

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

/*
 * This file is part of ClassGraph.
 *
 * Author: Luke Hutchison
 *
 * Hosted at: https://github.com/lukehutch/fast-classpath-scanner
 *
 * --
 *
 * 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.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
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 io.github.classgraph.utils.ClassLoaderAndModuleFinder;
import io.github.classgraph.utils.ClasspathFinder;
import io.github.classgraph.utils.ClasspathOrModulePathEntry;
import io.github.classgraph.utils.InterruptionChecker;
import io.github.classgraph.utils.JarUtils;
import io.github.classgraph.utils.LogNode;
import io.github.classgraph.utils.NestedJarHandler;
import io.github.classgraph.utils.SingletonMap;
import io.github.classgraph.utils.WorkQueue;
import io.github.classgraph.utils.WorkQueue.WorkQueuePreStartHook;
import io.github.classgraph.utils.WorkQueue.WorkUnitProcessor;

/** 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;
    private NestedJarHandler nestedJarHandler;

    /**
     * The number of files within a given classpath element (directory or zipfile) to send in a chunk to the workers
     * that are calling the classfile binary parser. The smaller this number is, the better the load leveling at the
     * end of the scan, but the higher the overhead in re-opening the same ZipFile or module in different worker
     * threads.
     */
    private static final int NUM_FILES_PER_CHUNK = 32;

    /** 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);
    }

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

    /** A map from relative path to classpath element singleton. */
    private static class ClasspathOrModulePathEntryToClasspathElementMap
            extends SingletonMap {
        private final ScanSpec scanSpec;
        private final NestedJarHandler nestedJarHandler;
        private WorkQueue workQueue;

        /** A map from relative path to classpath element singleton. */
        ClasspathOrModulePathEntryToClasspathElementMap(final ScanSpec scanSpec,
                final NestedJarHandler nestedJarHandler) {
            this.scanSpec = scanSpec;
            this.nestedJarHandler = nestedJarHandler;
        }

        /**
         * Work queue -- needs to be set for zipfiles, but not for directories, since zipfiles can contain
         * Class-Path manifest entries, which require the adding of additional work units to the scanning work
         * queue.
         */
        void setWorkQueue(final WorkQueue workQueue) {
            this.workQueue = workQueue;
        }

        /** Create a new classpath element singleton instance. */
        @Override
        public ClasspathElement newInstance(final ClasspathOrModulePathEntry classpathElt, final LogNode log) {
            final LogNode jarLog = log == null ? null : log.log("Reading " + classpathElt.getResolvedPath());
            if (classpathElt.isValidClasspathElement(scanSpec, jarLog)) {
                try {
                    final boolean isModule = classpathElt.getModuleRef() != null;
                    final boolean isFile = !isModule && classpathElt.isFile(jarLog);
                    final boolean isDir = !isModule && classpathElt.isDirectory(jarLog);
                    if (isFile && !scanSpec.scanJars) {
                        if (jarLog != null) {
                            jarLog.log("Skipping because jar scanning has been disabled: " + classpathElt);
                        }
                    } else if (isFile && !scanSpec.jarWhiteBlackList
                            .isWhitelistedAndNotBlacklisted(classpathElt.getCanonicalPath(jarLog))) {
                        if (jarLog != null) {
                            jarLog.log("Skipping jarfile that is blacklisted or not whitelisted: " + classpathElt);
                        }
                    } else if (isDir && !scanSpec.scanDirs) {
                        if (jarLog != null) {
                            jarLog.log("Skipping because directory scanning has been disabled: " + classpathElt);
                        }
                    } else if (isModule && !scanSpec.scanModules) {
                        if (jarLog != null) {
                            jarLog.log("Skipping because module scanning has been disabled: " + classpathElt);
                        }
                    } else {
                        // Classpath element is valid => add as a singleton. This will trigger
                        // calling the ClasspathElementZip constructor in the case of jarfiles,
                        // which will check the manifest file for Class-Path entries, and if
                        // any are found, additional work units will be added to the work queue
                        // to scan those jarfiles too. If Class-Path entries are found, they
                        // are added as child elements of the current classpath element, so
                        // that they can be inserted at the correct location in the classpath
                        // order.
                        return ClasspathElement.newInstance(classpathElt, scanSpec, nestedJarHandler, workQueue,
                                jarLog);
                    }
                } catch (final Exception e) {
                    if (jarLog != null) {
                        // Could not create singleton, possibly due to canonicalization problem
                        jarLog.log("Skipping invalid classpath element " + classpathElt + " : " + e);
                    }
                }
            }
            // Return null if classpath element is not valid.
            // This will cause SingletonMap to throw IlegalArgumentException.
            return null;
        }
    }

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

    /**
     * Recursively perform a depth-first search of jar interdependencies, breaking cycles if necessary, to determine
     * the final classpath element order.
     */
    private static void findClasspathOrder(final ClasspathElement currClasspathElement,
            final ClasspathOrModulePathEntryToClasspathElementMap classpathElementMap,
            final HashSet visitedClasspathElts, final ArrayList order)
            throws InterruptedException {
        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)
            if (currClasspathElement.childClasspathElts != null) {
                for (final ClasspathOrModulePathEntry childClasspathElt : currClasspathElement.childClasspathElts) {
                    final ClasspathElement childSingleton = classpathElementMap.get(childClasspathElt);
                    if (childSingleton != null) {
                        findClasspathOrder(childSingleton, classpathElementMap, visitedClasspathElts, order);
                    }
                }
            }
            if (currClasspathElement.skipClasspathElement) {
                // If classpath element is marked to be skipped, close it (it will not be used again).
                currClasspathElement.closeRecyclers();
            }
        }
    }

    /**
     * Recursively perform a depth-first search of jar interdependencies, breaking cycles if necessary, to determine
     * the final classpath element order.
     */
    private static List findClasspathOrder(
            final List rawClasspathElements,
            final ClasspathOrModulePathEntryToClasspathElementMap classpathElementMap) throws InterruptedException {
        // Recurse from toplevel classpath elements to determine a total ordering of classpath elements (jars with
        // Class-Path entries in their manifest file should have those child resources included in-place in the
        // classpath).
        final HashSet visitedClasspathElts = new HashSet<>();
        final ArrayList order = new ArrayList<>();
        for (final ClasspathOrModulePathEntry toplevelClasspathElt : rawClasspathElements) {
            final ClasspathElement toplevelSingleton = classpathElementMap.get(toplevelClasspathElt);
            if (toplevelSingleton != null) {
                findClasspathOrder(toplevelSingleton, classpathElementMap, visitedClasspathElts, order);
            }
        }
        return order;
    }

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

    /**
     * Holds range limits for chunks of classpath files that need to be scanned in a given classpath element.
     */
    private static class ClassfileParserChunk {
        private final ClasspathElement classpathElement;
        private final int classfileStartIdx;
        private final int classfileEndIdx;

        public ClassfileParserChunk(final ClasspathElement classpathElementSingleton, final int classfileStartIdx,
                final int classfileEndIdx) {
            this.classpathElement = classpathElementSingleton;
            this.classfileStartIdx = classfileStartIdx;
            this.classfileEndIdx = classfileEndIdx;
        }
    }

    /**
     * Break the classfiles that need to be scanned in each classpath element into chunks of approximately
     * NUM_FILES_PER_CHUNK files. This helps with load leveling so that the worker threads all complete their work
     * at approximately the same time.
     */
    private static List getClassfileParserChunks(
            final List classpathOrder) {
        LinkedList> chunks = new LinkedList<>();
        for (final ClasspathElement classpathElement : classpathOrder) {
            final LinkedList chunksForClasspathElt = new LinkedList<>();
            final int numClassfileMatches = classpathElement.getNumClassfileMatches();
            if (numClassfileMatches > 0) {
                final int numChunks = (int) Math.ceil((float) numClassfileMatches / (float) NUM_FILES_PER_CHUNK);
                final float filesPerChunk = (float) numClassfileMatches / (float) numChunks;
                for (int i = 0; i < numChunks; i++) {
                    final int classfileStartIdx = (int) (i * filesPerChunk);
                    final int classfileEndIdx = i < numChunks - 1 ? (int) ((i + 1) * filesPerChunk)
                            : numClassfileMatches;
                    if (classfileEndIdx > classfileStartIdx) {
                        chunksForClasspathElt.add(
                                new ClassfileParserChunk(classpathElement, classfileStartIdx, classfileEndIdx));
                    }
                }
            }
            chunks.add(chunksForClasspathElt);
        }
        // There should be no overlap between the relative paths in any of the chunks, because classpath masking has
        // already been applied, so these chunks can be scanned in any order. But since a ZipFile instance can only
        // be used by one thread at a time, we want to space the chunks for a given ZipFile as far apart as possible
        // in the work queue to minimize the chance that two threads will try to open the same ZipFile at the same
        // time, as this will cause a second copy of the ZipFile to have to be opened by the ZipFile recycler. The
        // combination of chunking and interleaving therefore lets us achieve load leveling without work stealing or
        // other more complex mechanism.
        final List interleavedChunks = new ArrayList<>();
        while (!chunks.isEmpty()) {
            final LinkedList> nextChunks = new LinkedList<>();
            for (final LinkedList chunksForClasspathElt : chunks) {
                if (!chunksForClasspathElt.isEmpty()) {
                    final ClassfileParserChunk head = chunksForClasspathElt.remove();
                    interleavedChunks.add(head);
                    if (!chunksForClasspathElt.isEmpty()) {
                        nextChunks.add(chunksForClasspathElt);
                    }
                }
            }
            chunks = nextChunks;
        }
        return interleavedChunks;
    }

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

    /**
     * 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");
        this.nestedJarHandler = new NestedJarHandler(scanSpec, classpathFinderLog);
        final ClasspathOrModulePathEntryToClasspathElementMap classpathElementMap = //
                new ClasspathOrModulePathEntryToClasspathElementMap(scanSpec, nestedJarHandler);
        try {
            final long scanStart = System.nanoTime();

            // Get classpath finder
            final LogNode getRawElementsLog = classpathFinderLog == null ? null
                    : classpathFinderLog.log("Getting raw classpath elements");
            final ClasspathFinder classpathFinder = new ClasspathFinder(scanSpec, nestedJarHandler,
                    getRawElementsLog);
            final ClassLoaderAndModuleFinder classLoaderAndModuleFinder = classpathFinder
                    .getClassLoaderAndModuleFinder();
            final ClassLoader[] classLoaderOrder = classLoaderAndModuleFinder.getClassLoaders();
            final List rawClasspathEltOrder = new ArrayList<>();

            if (scanSpec.overrideClasspath == null && scanSpec.overrideClassLoaders == null) {
                // Add modules to start of classpath order, before traditional classpath
                final List systemModules = classLoaderAndModuleFinder.getSystemModuleRefs();
                if (systemModules != null) {
                    for (final ModuleRef systemModule : systemModules) {
                        final String moduleName = systemModule.getName();
                        if (!scanSpec.blacklistSystemJarsOrModules
                                || !JarUtils.isInSystemPackageOrModule(moduleName)) {
                            if (scanSpec.moduleWhiteBlackList.isWhitelistedAndNotBlacklisted(moduleName)) {
                                rawClasspathEltOrder.add(new ClasspathOrModulePathEntry(systemModule,
                                        nestedJarHandler, getRawElementsLog));
                            } else {
                                if (classpathFinderLog != null) {
                                    classpathFinderLog.log(
                                            "Skipping non-whitelisted or blacklisted system module: " + moduleName);
                                }
                            }
                        } else {
                            if (classpathFinderLog != null) {
                                classpathFinderLog.log("Skipping system module: " + moduleName);
                            }
                        }
                    }
                }
                final List nonSystemModules = classLoaderAndModuleFinder.getNonSystemModuleRefs();
                if (nonSystemModules != null) {
                    for (final ModuleRef nonSystemModule : nonSystemModules) {
                        final String moduleName = nonSystemModule.getName();
                        if (scanSpec.moduleWhiteBlackList.isWhitelistedAndNotBlacklisted(moduleName)) {
                            rawClasspathEltOrder.add(new ClasspathOrModulePathEntry(nonSystemModule,
                                    nestedJarHandler, getRawElementsLog));
                        } else {
                            if (classpathFinderLog != null) {
                                classpathFinderLog
                                        .log("Skipping non-whitelisted or blacklisted module: " + moduleName);
                            }
                        }
                    }
                }
            }

            // Add traditional classpath entries to the classpath order
            rawClasspathEltOrder.addAll(classpathFinder.getRawClasspathElements());

            final List rawClasspathEltOrderStrs = new ArrayList<>(rawClasspathEltOrder.size());
            for (final ClasspathOrModulePathEntry entry : rawClasspathEltOrder) {
                rawClasspathEltOrderStrs.add(entry.getResolvedPath());
            }

            // In parallel, resolve raw classpath elements to canonical paths, creating a ClasspathElement singleton
            // for each unique canonical path. Also check jars against jar whitelist/blacklist.
            final LogNode preScanLog = classpathFinderLog == null ? null
                    : classpathFinderLog.log("Reading jarfile metadata");
            WorkQueue.runWorkQueue(rawClasspathEltOrder, executorService, numParallelTasks,
                    new WorkUnitProcessor() {
                        @Override
                        public void processWorkUnit(final ClasspathOrModulePathEntry rawClasspathEltPath)
                                throws Exception {
                            try {
                                // Add the classpath element as a singleton. This will trigger calling
                                // the ClasspathElementZip constructor in the case of jarfiles, which
                                // will check the manifest file for Class-Path entries, and if any are
                                // found, additional work units will be added to the work queue to scan
                                // those jarfiles too. If Class-Path entries are found, they are added
                                // as child elements of the current classpath element, so that they can
                                // be inserted at the correct location in the classpath order.
                                classpathElementMap.getOrCreateSingleton(rawClasspathEltPath, preScanLog);
                            } catch (final IllegalArgumentException e) {
                                // Thrown if classpath element is invalid (already logged)
                            }
                        }
                    }, new WorkQueuePreStartHook() {
                        @Override
                        public void processWorkQueueRef(final WorkQueue workQueue) {
                            // Store a ref back to the work queue in the classpath element map, because some
                            // classpath elements will need to schedule additional classpath elements for scanning,
                            // e.g. "Class-Path:" refs in jar manifest files
                            classpathElementMap.setWorkQueue(workQueue);
                        }
                    }, 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 classpathOrder = findClasspathOrder(rawClasspathEltOrder,
                    classpathElementMap);

            // Print final classpath element order, after inserting Class-Path entries from manifest files
            if (classpathFinderLog != null) {
                final LogNode logNode = classpathFinderLog.log("Final classpath element order:");
                for (int i = 0; i < classpathOrder.size(); i++) {
                    final ClasspathElement classpathElt = classpathOrder.get(i);
                    final ModuleRef classpathElementModuleRef = classpathElt.getClasspathElementModuleRef();
                    if (classpathElementModuleRef != null) {
                        logNode.log(i + ": module " + classpathElementModuleRef.getName() + " ; module location: "
                                + classpathElementModuleRef.getLocationStr());
                    } else {
                        final String classpathEltStr = classpathElt.toString();
                        final String classpathEltFileStr = "" + classpathElt.getClasspathElementFile(logNode);
                        final String packageRoot = classpathElt.getJarfilePackageRoot();
                        logNode.log(i + ": " + (classpathEltStr.equals(classpathEltFileStr) && packageRoot.isEmpty()
                                ? classpathEltStr
                                : classpathElt + " -> " + classpathEltFileStr
                                        + (packageRoot.isEmpty() ? "" : " ; package root: " + packageRoot)));
                    }
                }
            }

            ScanResult scanResult;
            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.
                scanResult = new ScanResult(scanSpec, classpathOrder, rawClasspathEltOrderStrs, classLoaderOrder,
                        /* classNameToClassInfo = */ null, /* fileToLastModified = */ null, nestedJarHandler,
                        topLevelLog);

            } else {
                // Perform scan of the classpath

                // Find classpath elements that are path prefixes of other classpath elements
                final List> classpathEltResolvedPathToElement = //
                        new ArrayList<>();
                for (int i = 0; i < classpathOrder.size(); i++) {
                    final ClasspathElement classpathElement = classpathOrder.get(i);
                    classpathEltResolvedPathToElement.add(new SimpleEntry<>(
                            classpathElement.classpathEltPath.getResolvedPath(), classpathElement));
                }
                Collections.sort(classpathEltResolvedPathToElement,
                        new Comparator>() {
                            // Sort classpath elements into lexicographic order
                            @Override
                            public int compare(final SimpleEntry o1,
                                    final SimpleEntry o2) {
                                // Path strings will all be unique
                                return o1.getKey().compareTo(o2.getKey());
                            }
                        });
                LogNode nestedClasspathRootNode = null;
                for (int i = 0; i < classpathEltResolvedPathToElement.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 = classpathEltResolvedPathToElement.get(i);
                    final String basePath = ei.getKey();
                    final int basePathLen = basePath.length();
                    for (int j = i + 1; j < classpathEltResolvedPathToElement.size(); j++) {
                        final SimpleEntry ej = classpathEltResolvedPathToElement.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 (classpathFinderLog != null) {
                                        if (nestedClasspathRootNode == null) {
                                            nestedClasspathRootNode = classpathFinderLog
                                                    .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;
                        }
                    }
                }

                // Scan for matching classfiles / files, looking only at filenames / file paths, and not contents
                final LogNode pathScanLog = classpathFinderLog == null ? null
                        : classpathFinderLog.log("Scanning filenames within classpath elements");
                WorkQueue.runWorkQueue(classpathOrder, executorService, numParallelTasks,
                        new WorkUnitProcessor() {
                            @Override
                            public void processWorkUnit(final ClasspathElement classpathElement) throws Exception {
                                // Scan the paths within a directory or jar
                                classpathElement.scanPaths(pathScanLog);
                            }
                        }, interruptionChecker, pathScanLog);

                // 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 classpath files");
                final HashSet classpathRelativePathsFound = new HashSet<>();
                for (int classpathIdx = 0; classpathIdx < classpathOrder.size(); classpathIdx++) {
                    final ClasspathElement classpathElement = classpathOrder.get(classpathIdx);
                    classpathElement.maskFiles(classpathIdx, classpathRelativePathsFound, 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 : classpathOrder) {
                    fileToLastModified.putAll(classpathElement.fileToLastModified);
                }

                final Map classNameToClassInfo = new HashMap<>();
                if (!scanSpec.enableClassInfo) {
                    if (topLevelLog != null) {
                        topLevelLog.log("Classfile scanning is disabled");
                    }
                } else {
                    // Scan classfile binary headers in parallel
                    final ConcurrentLinkedQueue classInfoUnlinked = //
                            new ConcurrentLinkedQueue<>();
                    final LogNode classfileScanLog = topLevelLog == null ? null
                            : topLevelLog.log("Scanning classfile binary headers");
                    final List classfileParserChunks = getClassfileParserChunks(
                            classpathOrder);
                    WorkQueue.runWorkQueue(classfileParserChunks, executorService, numParallelTasks,
                            new WorkUnitProcessor() {
                                @Override
                                public void processWorkUnit(final ClassfileParserChunk chunk) throws Exception {
                                    chunk.classpathElement.parseClassfiles(chunk.classfileStartIdx,
                                            chunk.classfileEndIdx, classInfoUnlinked, classfileScanLog);
                                    interruptionChecker.check();
                                }
                            }, 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 : classInfoUnlinked) {
                        c.link(scanSpec, classNameToClassInfo, 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.getClassNamesFromTypeDescriptors(referencedClassNames);
                    //    }
                    //    for (final String referencedClass : referencedClassNames) {
                    //        ClassInfo.getOrCreateClassInfo(referencedClass, /* modifiers = */ 0, scanSpec,
                    //                classNameToClassInfo);
                    //    }

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

                // Create ScanResult
                scanResult = new ScanResult(scanSpec, classpathOrder, rawClasspathEltOrderStrs, classLoaderOrder,
                        classNameToClassInfo, 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
            if (this.nestedJarHandler != null) {
                this.nestedJarHandler.close(topLevelLog);
            }
            if (topLevelLog != null) {
                topLevelLog.log(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);
            } else {
                // Don't delete temporary files yet, but close zipfile/module recyclers
                nestedJarHandler.closeRecyclers();
            }

            // Close ClasspathElement recyclers
            for (final ClasspathElement elt : classpathElementMap.values()) {
                if (elt != null) {
                    elt.closeRecyclers();
                }
            }

            if (topLevelLog != null) {
                topLevelLog.flush();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy