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

io.github.classgraph.utils.ClasspathFinder Maven / Gradle / Ivy

/*
 * 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.utils;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import io.github.classgraph.ClassLoaderHandler;
import io.github.classgraph.ClassLoaderHandler.DelegationOrder;
import io.github.classgraph.ScanSpec;
import io.github.classgraph.classloaderhandler.ClassLoaderHandlerRegistry;
import io.github.classgraph.classloaderhandler.ClassLoaderHandlerRegistry.ClassLoaderHandlerRegistryEntry;

/** A class to find the unique ordered classpath elements. */
public class ClasspathFinder {
    private final List rawClasspathElements;
    private final ClassLoaderAndModuleFinder classLoaderAndModuleFinder;

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

    /** Add a ClassLoaderHandler, and recurse to parent classloader. */
    private boolean addClassLoaderHandler(final ScanSpec scanSpec, final ClassLoader classLoader,
            final ClassLoaderHandlerRegistryEntry classLoaderHandlerRegistryEntry,
            final LinkedHashSet foundClassLoaders,
            final List allClassLoaderHandlerRegistryEntries,
            final List> classLoaderAndHandlerOrderOut,
            final List> ignoredClassLoaderAndHandlerOrderOut,
            final Set visited, 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 ClassLoader embeddedClassLoader = classLoaderHandler.getEmbeddedClassLoader(classLoader);
            if (embeddedClassLoader != null) {
                if (visited.add(embeddedClassLoader)) {
                    if (log != null) {
                        log.log("Delegating from " + classLoader + " to embedded ClassLoader "
                                + embeddedClassLoader);
                    }
                    return addClassLoaderHandler(scanSpec, embeddedClassLoader, classLoaderHandlerRegistryEntry,
                            foundClassLoaders, allClassLoaderHandlerRegistryEntries, classLoaderAndHandlerOrderOut,
                            ignoredClassLoaderAndHandlerOrderOut, visited, log);
                } else {
                    if (log != null) {
                        log.log("Hit infinite loop when delegating from " + classLoader
                                + " to embedded ClassLoader " + embeddedClassLoader);
                    }
                    return false;
                }
            } else {
                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 LinkedHashSet 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 it
                            if (addClassLoaderHandler(scanSpec, classLoader, classLoaderHandlerRegistryEntry,
                                    foundClassLoaders, allClassLoaderHandlerRegistryEntries,
                                    classLoaderAndHandlerOrderOut, ignoredClassLoaderAndHandlerOrderOut,
                                    new HashSet(), 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/classgraph/classgraph/issues");
                }
                addClassLoaderHandler(scanSpec, classLoader,
                        ClassLoaderHandlerRegistry.FALLBACK_CLASS_LOADER_HANDLER, foundClassLoaders,
                        allClassLoaderHandlerRegistryEntries, classLoaderAndHandlerOrderOut,
                        ignoredClassLoaderAndHandlerOrderOut, new HashSet(), log);
            }
        }
    }

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

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

        // Get environment ClassLoader order
        classLoaderAndModuleFinder = new ClassLoaderAndModuleFinder(scanSpec, classpathFinderLog);

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

        final ClassLoader[] classLoaders = classLoaderAndModuleFinder.getClassLoaders();
        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, classLoaders, overrideLog);
            if (overrideLog != null) {
                overrideLog.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, add JRE jars to the beginning of the classpath
            if (!scanSpec.blacklistSystemJarsOrModules) {
                final List jreJarPaths = JarUtils.getJreJarPaths();
                if (log != null) {
                    log.log("Adding JRE/JDK jars to classpath:").log(jreJarPaths);
                }
                for (final String jreJarPath : jreJarPaths) {
                    classpathOrder.addClasspathElement(jreJarPath, classLoaders, scanSpec, classpathFinderLog);
                }
            }

            // Get all manually-added ClassLoaderHandlers (these are added before the default ClassLoaderHandlers,
            // so that the behavior of the defaults can be overridden)
            List allClassLoaderHandlerRegistryEntries;
            if (scanSpec.extraClassLoaderHandlers.isEmpty()) {
                allClassLoaderHandlerRegistryEntries = ClassLoaderHandlerRegistry.DEFAULT_CLASS_LOADER_HANDLERS;
            } else {
                allClassLoaderHandlerRegistryEntries = new ArrayList<>(scanSpec.extraClassLoaderHandlers);
                allClassLoaderHandlerRegistryEntries
                        .addAll(ClassLoaderHandlerRegistry.DEFAULT_CLASS_LOADER_HANDLERS);
            }

            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 : classLoaders) {
                findClassLoaderHandlerForClassLoaderAndParents(scanSpec, envClassLoader,
                        /* foundClassLoaders = */ new LinkedHashSet(),
                        allClassLoaderHandlerRegistryEntries, classLoaderAndHandlerOrder,
                        ignoredClassLoaderAndHandlerOrder, classpathFinderLog);
            }

            // 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 ClasspathOrModulePathEntry(FileUtils.CURR_DIR_PATH, pathElement,
                                        classLoaders, nestedJarHandler, scanSpec, log))) {
                            // pathElement is not also listed in an ignored parent classloader
                            classpathOrder.addClasspathElement(pathElement, classLoaders, scanSpec, 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 = new ArrayList<>(classpathOrder.get());
    }

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

    /**
     * @return The {@link ClassLoaderAndModuleFinder}.
     */
    public ClassLoaderAndModuleFinder getClassLoaderAndModuleFinder() {
        return classLoaderAndModuleFinder;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy