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

io.github.lukehutch.fastclasspathscanner.scanner.ClasspathFinder 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.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;

import io.github.lukehutch.fastclasspathscanner.classloaderhandler.ClassLoaderHandler;
import io.github.lukehutch.fastclasspathscanner.classloaderhandler.ClassLoaderHandler.DelegationOrder;
import io.github.lukehutch.fastclasspathscanner.classloaderhandler.ClassLoaderHandlerRegistry;
import io.github.lukehutch.fastclasspathscanner.classloaderhandler.ClassLoaderHandlerRegistry.ClassLoaderHandlerRegistryEntry;
import io.github.lukehutch.fastclasspathscanner.scanner.ClassLoaderFinder.EnvClassLoadersAndModules;
import io.github.lukehutch.fastclasspathscanner.utils.AdditionOrderedSet;
import io.github.lukehutch.fastclasspathscanner.utils.FileUtils;
import io.github.lukehutch.fastclasspathscanner.utils.JarUtils;
import io.github.lukehutch.fastclasspathscanner.utils.LogNode;
import io.github.lukehutch.fastclasspathscanner.utils.NestedJarHandler;

/** A class to find the unique ordered classpath elements. */
public class ClasspathFinder {
    static final String currDirPathStr = FileUtils.getCurrDirPathStr();

    private final List rawClasspathElements;
    private final EnvClassLoadersAndModules envClassLoadersAndModules;

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

    /** Add a ClassLoaderHandler, and recurse to parent classloader. */
    private boolean addClassLoaderHandler(final ScanSpec scanSpec, final ClassLoader classLoader,
            final ClassLoaderHandlerRegistryEntry classLoaderHandlerRegistryEntry,
            final AdditionOrderedSet foundClassLoaders,
            final List allClassLoaderHandlerRegistryEntries,
            final List> classLoaderAndHandlerOrderOut,
            final List> ignoredClassLoaderAndHandlerOrderOut,
            final LogNode log) {
        // Instantiate a ClassLoaderHandler for each ClassLoader, in case the ClassLoaderHandler has state
        final ClassLoaderHandler classLoaderHandler = classLoaderHandlerRegistryEntry.instantiate(log);
        if (classLoaderHandler != null) {
            if (log != null) {
                log.log("ClassLoader " + classLoader + " will be handled by " + classLoaderHandler);
            }
            final DelegationOrder delegationOrder = classLoaderHandler.getDelegationOrder(classLoader);
            final ClassLoader parent = classLoader.getParent();
            if (log != null && parent != null) {
                log.log(classLoader + " delegates to parent " + parent + " with order " + delegationOrder);
            }
            switch (delegationOrder) {
            case PARENT_FIRST:
                // Recurse to parent first, then add this ClassLoader to order
                if (parent != null) {
                    findClassLoaderHandlerForClassLoaderAndParents(scanSpec, parent, foundClassLoaders,
                            allClassLoaderHandlerRegistryEntries,
                            scanSpec.ignoreParentClassLoaders ? ignoredClassLoaderAndHandlerOrderOut
                                    : classLoaderAndHandlerOrderOut,
                            ignoredClassLoaderAndHandlerOrderOut, log);
                }
                classLoaderAndHandlerOrderOut.add(new SimpleEntry<>(classLoader, classLoaderHandler));
                return true;
            case PARENT_LAST:
                // Add this ClassLoader to order, then recurse to parent
                classLoaderAndHandlerOrderOut.add(new SimpleEntry<>(classLoader, classLoaderHandler));
                if (parent != null) {
                    findClassLoaderHandlerForClassLoaderAndParents(scanSpec, parent, foundClassLoaders,
                            allClassLoaderHandlerRegistryEntries,
                            scanSpec.ignoreParentClassLoaders ? ignoredClassLoaderAndHandlerOrderOut
                                    : classLoaderAndHandlerOrderOut,
                            ignoredClassLoaderAndHandlerOrderOut, log);
                }
                return true;
            default:
                throw new RuntimeException("Unknown delegation order");
            }
        }
        return false;
    }

    /**
     * Recursively find the ClassLoaderHandler that can handle each ClassLoader and its parent(s), correctly
     * observing parent delegation order (PARENT_FIRST or PARENT_LAST).
     */
    private void findClassLoaderHandlerForClassLoaderAndParents(final ScanSpec scanSpec,
            final ClassLoader classLoader, final AdditionOrderedSet foundClassLoaders,
            final List allClassLoaderHandlerRegistryEntries,
            final List> classLoaderAndHandlerOrderOut,
            final List> ignoredClassLoaderAndHandlerOrderOut,
            final LogNode log) {
        // Don't handle ClassLoaders twice (so that any shared parent ClassLoaders get handled only once)
        if (foundClassLoaders.add(classLoader)) {
            boolean foundMatch = false;
            // Iterate through each ClassLoader superclass name
            for (Class c = classLoader.getClass(); c != null; c = c.getSuperclass()) {
                // Compare against the class names handled by each ClassLoaderHandler
                for (final ClassLoaderHandlerRegistryEntry classLoaderHandlerRegistryEntry : //
                allClassLoaderHandlerRegistryEntries) {
                    for (final String handledClassLoaderName : //
                    classLoaderHandlerRegistryEntry.handledClassLoaderNames) {
                        if (handledClassLoaderName.equals(c.getName())) {
                            // This ClassLoaderHandler can handle this class --
                            // instantiate the ClassLoaderHandler for this ClassLoader
                            if (addClassLoaderHandler(scanSpec, classLoader, classLoaderHandlerRegistryEntry,
                                    foundClassLoaders, allClassLoaderHandlerRegistryEntries,
                                    classLoaderAndHandlerOrderOut, ignoredClassLoaderAndHandlerOrderOut, log)) {
                                foundMatch = true;
                            }
                            break;
                        }
                    }
                    if (foundMatch) {
                        break;
                    }
                }
                if (foundMatch) {
                    break;
                }
            }
            if (!foundMatch) {
                if (log != null) {
                    log.log("Could not find a ClassLoaderHandler that can handle " + classLoader + " , trying "
                            + ClassLoaderHandlerRegistry.FALLBACK_CLASS_LOADER_HANDLER.classLoaderHandlerClass
                                    .getName()
                            + " instead. Please report this at: "
                            + "https://github.com/lukehutch/fast-classpath-scanner/issues");
                }
                addClassLoaderHandler(scanSpec, classLoader,
                        ClassLoaderHandlerRegistry.FALLBACK_CLASS_LOADER_HANDLER, foundClassLoaders,
                        allClassLoaderHandlerRegistryEntries, classLoaderAndHandlerOrderOut,
                        ignoredClassLoaderAndHandlerOrderOut, log);
            }
        }
    }

    /**
     * Instantiate a ClassLoaderHandler for a given class, or return an instance of FallbackClassLoaderHandler if no
     * ClassLoaderHandler can handle the class.
     */
    public static ClassLoaderHandler findClassLoaderHandlerForClassLoader(final ScanSpec scanSpec,
            final ClassLoader classLoader, final LogNode log) {
        // Iterate through each ClassLoader superclass name
        for (Class c = classLoader.getClass(); c != null; c = c.getSuperclass()) {
            // Compare against the class names handled by each ClassLoaderHandler
            final List allClassLoaderHandlerRegistryEntries = scanSpec
                    .getAllClassLoaderHandlerRegistryEntries();
            for (final ClassLoaderHandlerRegistryEntry classLoaderHandlerRegistryEntry : //
            allClassLoaderHandlerRegistryEntries) {
                for (final String handledClassLoaderName : classLoaderHandlerRegistryEntry.handledClassLoaderNames) {
                    if (handledClassLoaderName.equals(c.getName())) {
                        // This ClassLoaderHandler can handle this class --
                        // instantiate the ClassLoaderHandler for this ClassLoader
                        return classLoaderHandlerRegistryEntry.instantiate(log);
                    }
                }
            }
        }
        return ClassLoaderHandlerRegistry.FALLBACK_CLASS_LOADER_HANDLER.instantiate(log);
    }

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

    /** A class to find the unique ordered classpath elements. */
    ClasspathFinder(final ScanSpec scanSpec, final NestedJarHandler nestedJarHandler, final LogNode log) {
        final LogNode classpathFinderLog = log == null ? null : log.log("Finding ClassLoaders");

        // Get environment ClassLoader order
        envClassLoadersAndModules = ClassLoaderFinder.findEnvClassLoaders(scanSpec, classpathFinderLog);

        final ClasspathOrder classpathOrder = new ClasspathOrder(scanSpec, nestedJarHandler);
        final ClasspathOrder ignoredClasspathOrder = new ClasspathOrder(scanSpec, nestedJarHandler);

        if (scanSpec.overrideClasspath != null) {
            // Manual classpath override
            if (scanSpec.overrideClassLoaders != null) {
                if (classpathFinderLog != null) {
                    classpathFinderLog
                            .log("It is not possible to override both the classpath and the ClassLoaders -- "
                                    + "ignoring the ClassLoader override");
                }
            }
            final LogNode overrideLog = classpathFinderLog == null ? null
                    : classpathFinderLog.log("Overriding classpath with: " + scanSpec.overrideClasspath);
            classpathOrder.addClasspathElements(scanSpec.overrideClasspath, envClassLoadersAndModules.classLoaders,
                    overrideLog);
            if (overrideLog != null) {
                classpathFinderLog
                        .log("WARNING: when the classpath is overridden, there is no guarantee that the classes "
                                + "found by classpath scanning will be the same as the classes loaded by the "
                                + "context classloader");
            }
        } else {
            // If system jars are not blacklisted, need to manually add rt.jar at the beginning of the classpath,
            // because it is included implicitly by the JVM. TODO: this is handled differently in Java 9.
            if (!scanSpec.blacklistSystemJars()) {
                // There should only be zero or one of these.
                final String rtJarPath = JarUtils.getRtJarPath();
                if (log != null) {
                    log.log(rtJarPath == null ? "Could not find path for rt.jar"
                            : "Adding rt.jar as first classpath element to scan: " + rtJarPath);
                }
                if (rtJarPath != null) {
                    // Insert rt.jar as the first entry in the classpath.
                    classpathOrder.addClasspathElement(rtJarPath, envClassLoadersAndModules.classLoaders,
                            classpathFinderLog);
                }
            }

            final List allClassLoaderHandlerRegistryEntries = scanSpec
                    .getAllClassLoaderHandlerRegistryEntries();
            if (classpathFinderLog != null) {
                final LogNode classLoaderHandlerLog = classpathFinderLog.log("ClassLoaderHandlers:");
                for (final ClassLoaderHandlerRegistryEntry classLoaderHandlerEntry : //
                allClassLoaderHandlerRegistryEntries) {
                    classLoaderHandlerLog.log(classLoaderHandlerEntry.classLoaderHandlerClass.getName());
                }
            }

            // Find all unique parent ClassLoaders, and put all ClassLoaders into a single order, according to the
            // delegation order (PARENT_FIRST or PARENT_LAST)
            final List> classLoaderAndHandlerOrder = new ArrayList<>();
            final List> ignoredClassLoaderAndHandlerOrder = //
                    new ArrayList<>();
            for (final ClassLoader envClassLoader : envClassLoadersAndModules.classLoaders) {
                if (!scanSpec.blacklistSystemJars()
                        || !envClassLoader.getClass().getName().startsWith("sun.misc.Launcher$ExtClassLoader")) {
                    findClassLoaderHandlerForClassLoaderAndParents(scanSpec, envClassLoader,
                            /* foundClassLoaders = */ new AdditionOrderedSet(),
                            allClassLoaderHandlerRegistryEntries, classLoaderAndHandlerOrder,
                            ignoredClassLoaderAndHandlerOrder, classpathFinderLog);
                } else if (classpathFinderLog != null) {
                    classpathFinderLog.log("Skipping system classloader " + envClassLoader.getClass().getName());
                }
            }

            // Call each ClassLoaderHandler on its corresponding ClassLoader to get the classpath URLs or paths
            for (final SimpleEntry classLoaderAndHandler : //
            classLoaderAndHandlerOrder) {
                final ClassLoader classLoader = classLoaderAndHandler.getKey();
                final ClassLoaderHandler classLoaderHandler = classLoaderAndHandler.getValue();
                final LogNode classLoaderClasspathLog = classpathFinderLog == null ? null
                        : classpathFinderLog.log("Finding classpath elements in ClassLoader " + classLoader);
                try {
                    classLoaderHandler.handle(scanSpec, classLoader, classpathOrder, classLoaderClasspathLog);
                } catch (final Throwable e) {
                    if (classLoaderClasspathLog != null) {
                        classLoaderClasspathLog.log("Exception in ClassLoaderHandler", e);
                    }
                }
            }
            // Repeat the process for ignored parent ClassLoaders
            for (final SimpleEntry classLoaderAndHandler : //
            ignoredClassLoaderAndHandlerOrder) {
                final ClassLoader classLoader = classLoaderAndHandler.getKey();
                final ClassLoaderHandler classLoaderHandler = classLoaderAndHandler.getValue();
                final LogNode classLoaderClasspathLog = classpathFinderLog == null ? null
                        : classpathFinderLog
                                .log("Will not scan the following classpath elements from ignored ClassLoader "
                                        + classLoader);
                try {
                    classLoaderHandler.handle(scanSpec, classLoader, ignoredClasspathOrder,
                            classLoaderClasspathLog);
                } catch (final Throwable e) {
                    if (classLoaderClasspathLog != null) {
                        classLoaderClasspathLog.log("Exception in ClassLoaderHandler", e);
                    }
                }
            }

            // Get classpath elements from java.class.path, but don't add them if the element is in an ignored
            // parent classloader and not in a child classloader (and don't use java.class.path at all if
            // overrideClassLoaders is true or overrideClasspath is set)
            if (scanSpec.overrideClassLoaders == null && scanSpec.overrideClasspath == null) {
                final String[] pathElements = JarUtils.smartPathSplit(System.getProperty("java.class.path"));
                if (pathElements.length > 0) {
                    final LogNode sysPropLog = classpathFinderLog == null ? null
                            : classpathFinderLog.log("Getting classpath entries from java.class.path");
                    for (final String pathElement : pathElements) {
                        if (!ignoredClasspathOrder.get().contains(new RelativePath(currDirPathStr, pathElement,
                                envClassLoadersAndModules.classLoaders, nestedJarHandler, log))) {
                            // pathElement is not also listed in an ignored parent classloader
                            classpathOrder.addClasspathElement(pathElement, envClassLoadersAndModules.classLoaders,
                                    sysPropLog);
                        } else {
                            // pathElement is also listed in an ignored parent classloader, ignore it (Issue #169)
                            if (sysPropLog != null) {
                                sysPropLog.log("Found classpath element in java.class.path that will be ignored, "
                                        + "since it is also found in an ignored parent classloader: "
                                        + pathElement);
                            }
                        }
                    }
                }
            }
        }
        rawClasspathElements = classpathOrder.get().toList();
    }

    /** Get the raw classpath elements obtained from ClassLoaders. */
    public List getRawClasspathElements() {
        return rawClasspathElements;
    }

    /**
     * Get the order in which ClassLoaders are called to load classes. (Usually consists of one element only.)
     */
    public ClassLoader[] getClassLoaderOrder() {
        return envClassLoadersAndModules.classLoaders;
    }

    /** Get ModuleRefs for the system modules, if running under JDK9+, else null. */
    public List getSystemModuleRefs() {
        return envClassLoadersAndModules.systemModuleRefs;
    }

    /** Get ModuleRefs for the non-system modules, if running under JDK9+, else null. */
    public List getNonSystemModuleRefs() {
        return envClassLoadersAndModules.nonSystemModuleRefs;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy