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

io.github.classgraph.ClasspathElementModule 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;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import io.github.classgraph.Scanner.RawClasspathElementWorkUnit;
import nonapi.io.github.classgraph.ScanSpec;
import nonapi.io.github.classgraph.ScanSpec.ScanSpecPathMatch;
import nonapi.io.github.classgraph.concurrency.WorkQueue;
import nonapi.io.github.classgraph.fastzipfilereader.NestedJarHandler;
import nonapi.io.github.classgraph.recycler.RecycleOnClose;
import nonapi.io.github.classgraph.recycler.Recycler;
import nonapi.io.github.classgraph.utils.InputStreamOrByteBufferAdapter;
import nonapi.io.github.classgraph.utils.LogNode;
import nonapi.io.github.classgraph.utils.URLPathEncoder;

/** A module classpath element. */
class ClasspathElementModule extends ClasspathElement {
    private final ModuleRef moduleRef;
    private final NestedJarHandler nestedJarHandler;
    private Recycler moduleReaderProxyRecycler;
    private final Set allResourcePaths = new HashSet<>();

    /** A zip/jarfile classpath element. */
    ClasspathElementModule(final ModuleRef moduleRef, final ClassLoader[] classLoaders,
            final NestedJarHandler nestedJarHandler, final ScanSpec scanSpec) {
        super(classLoaders, scanSpec);
        this.moduleRef = moduleRef;
        this.nestedJarHandler = nestedJarHandler;
        if (scanSpec.performScan) {
            whitelistedResources = new ArrayList<>();
            whitelistedClassfileResources = new ArrayList<>();
            fileToLastModified = new HashMap<>();
        }
    }

    @Override
    void open(final WorkQueue workQueueIgnored, final LogNode log) {
        try {
            moduleReaderProxyRecycler = nestedJarHandler.moduleRefToModuleReaderProxyRecyclerMap.get(moduleRef,
                    log);
        } catch (final IOException | IllegalArgumentException e) {
            if (log != null) {
                log.log("Exception while creating ModuleReaderProxy recycler for " + moduleRef.getName() + " : "
                        + e);
            }
            skipClasspathElement = true;
            return;
        } catch (final Exception e) {
            if (log != null) {
                log.log("Exception while creating ModuleReaderProxy recycler for " + moduleRef.getName(), e);
            }
            skipClasspathElement = true;
            return;
        }
    }

    /** Create a new {@link Resource} object for a resource or classfile discovered while scanning paths. */
    private Resource newResource(final String moduleResourcePath) {
        return new Resource() {
            private ModuleReaderProxy moduleReaderProxy;

            @Override
            public String getPath() {
                return moduleResourcePath;
            }

            @Override
            public String getPathRelativeToClasspathElement() {
                return moduleResourcePath;
            }

            @Override
            public URL getURL() {
                try {
                    if (moduleRef.getLocationStr() != null) {
                        // Use module location string as URL base, if present
                        return URLPathEncoder.urlPathToURL(moduleRef.getLocationStr() + "!/" + moduleResourcePath);
                    } else {
                        // If there is no known module location, just make up a "jrt:" path based on the module
                        // name, so that the user can see something reasonable in the result
                        return URLPathEncoder
                                .urlPathToURL("jrt:/" + moduleRef.getName() + "/" + moduleResourcePath);
                    }
                } catch (final MalformedURLException e) {
                    throw new IllegalArgumentException("Could not form URL for module location: "
                            + moduleRef.getLocationStr() + " ; path: " + moduleResourcePath);
                }
            }

            @Override
            public URL getClasspathElementURL() {
                try {
                    if (moduleRef.getLocation() == null) {
                        // If there is no known module location, just guess a "jrt:" path based on the module
                        // name, so that the user can see something reasonable in the result
                        return new URL(new URL("jrt:/" + moduleRef.getName()).toString());
                    } else {
                        return moduleRef.getLocation().toURL();
                    }
                } catch (final MalformedURLException e) {
                    throw new IllegalArgumentException(
                            "Could not form URL for module classpath element: " + moduleRef.getLocationStr());
                }
            }

            @Override
            public File getClasspathElementFile() {
                return null;
            }

            @Override
            public ModuleRef getModuleRef() {
                return moduleRef;
            }

            @Override
            public synchronized ByteBuffer read() throws IOException {
                if (skipClasspathElement) {
                    // Shouldn't happen
                    throw new IOException("Module could not be opened");
                }
                markAsOpen();
                try {
                    moduleReaderProxy = moduleReaderProxyRecycler.acquire();
                    // ModuleReader#read(String name) internally calls:
                    // InputStream is = open(name); return ByteBuffer.wrap(is.readAllBytes());
                    byteBuffer = moduleReaderProxy.read(moduleResourcePath);
                    length = byteBuffer.remaining();
                    return byteBuffer;

                } catch (final Exception e) {
                    close();
                    throw new IOException("Could not open " + this, e);
                }
            }

            @Override
            synchronized InputStreamOrByteBufferAdapter openOrRead() throws IOException {
                return new InputStreamOrByteBufferAdapter(open());
            }

            @Override
            public synchronized InputStream open() throws IOException {
                if (skipClasspathElement) {
                    // Shouldn't happen
                    throw new IOException("Module could not be opened");
                }
                markAsOpen();
                try {
                    moduleReaderProxy = moduleReaderProxyRecycler.acquire();
                    inputStream = new InputStreamResourceCloser(this, moduleReaderProxy.open(moduleResourcePath));
                    // Length cannot be obtained from ModuleReader
                    length = -1L;
                    return inputStream;

                } catch (final Exception e) {
                    close();
                    throw new IOException("Could not open " + this, e);
                }
            }

            @Override
            public synchronized byte[] load() throws IOException {
                try {
                    read();
                    final byte[] byteArray = byteBufferToByteArray();
                    length = byteArray.length;
                    return byteArray;
                } finally {
                    close();
                }
            }

            @Override
            public synchronized void close() {
                super.close(); // Close inputStream
                if (byteBuffer != null) {
                    if (moduleReaderProxy != null) {
                        try {
                            // Release any open ByteBuffer
                            moduleReaderProxy.release(byteBuffer);
                        } catch (final Exception e) {
                            // Ignore
                        }
                    }
                    byteBuffer = null;
                }
                if (moduleReaderProxy != null) {
                    // Recycle the (open) ModuleReaderProxy instance.
                    moduleReaderProxyRecycler.recycle(moduleReaderProxy);
                    // Don't call ModuleReaderProxy#close(), leave the ModuleReaderProxy open in the recycler.
                    // Just set the ref to null here. The ModuleReaderProxy will be closed by
                    // ClasspathElementModule#close().
                    moduleReaderProxy = null;
                }
                markAsClosed();
            }
        };
    }

    /**
     * @param relativePath
     *            The relative path of the {@link Resource} to return.
     * @return The {@link Resource} for the given relative path, or null if relativePath does not exist in this
     *         classpath element.
     */
    @Override
    Resource getResource(final String relativePath) {
        return allResourcePaths.contains(relativePath) ? newResource(relativePath) : null;
    }

    /** Scan for package matches within module */
    @Override
    void scanPaths(final LogNode log) {
        if (skipClasspathElement) {
            return;
        }
        if (scanned.getAndSet(true)) {
            // Should not happen
            throw new IllegalArgumentException("Already scanned classpath element " + toString());
        }

        final String moduleLocationStr = moduleRef.getLocationStr();
        final LogNode subLog = log == null ? null
                : log.log(moduleLocationStr, "Scanning module " + moduleRef.getName());

        try (final RecycleOnClose moduleReaderProxyRecycleOnClose //
                = moduleReaderProxyRecycler.acquireRecycleOnClose()) {
            // Look for whitelisted files in the module.
            List resourceRelativePaths;
            try {
                resourceRelativePaths = moduleReaderProxyRecycleOnClose.get().list();
            } catch (final Exception e) {
                if (subLog != null) {
                    subLog.log("Could not get resource list for module " + moduleRef.getName(), e);
                }
                return;
            }
            Collections.sort(resourceRelativePaths);

            String prevParentRelativePath = null;
            ScanSpecPathMatch prevParentMatchStatus = null;
            for (final String relativePath : resourceRelativePaths) {
                // From ModuleReader#find(): "If the module reader can determine that the name locates a
                // directory then the resulting URI will end with a slash ('/')."  But from the documentation
                // for ModuleReader#list(): "Whether the stream of elements includes names corresponding to
                // directories in the module is module reader specific."  We don't have a way of checking if
                // a resource is a directory without trying to open it, unless ModuleReader#list() also decides
                // to put a "/" on the end of resource paths corresponding to directories. Skip directories if
                // they are found, but if they are not able to be skipped, we will have to settle for having
                // some IOExceptions thrown when directories are mistaken for resource files.
                if (relativePath.endsWith("/")) {
                    continue;
                }

                // Whitelist/blacklist classpath elements based on file resource paths
                if (!scanSpec.classpathElementResourcePathWhiteBlackList.whitelistAndBlacklistAreEmpty()) {
                    if (scanSpec.classpathElementResourcePathWhiteBlackList.isBlacklisted(relativePath)) {
                        if (subLog != null) {
                            subLog.log("Reached blacklisted classpath element resource path, stopping scanning: "
                                    + relativePath);
                        }
                        skipClasspathElement = true;
                        return;
                    }
                    if (scanSpec.classpathElementResourcePathWhiteBlackList
                            .isSpecificallyWhitelisted(relativePath)) {
                        if (subLog != null) {
                            subLog.log("Reached specifically whitelisted classpath element resource path: "
                                    + relativePath);
                        }
                        containsSpecificallyWhitelistedClasspathElementResourcePath = true;
                    }
                }

                // Get match status of the parent directory of this resource's relative path (or reuse the last
                // match status for speed, if the directory name hasn't changed).
                final int lastSlashIdx = relativePath.lastIndexOf("/");
                final String parentRelativePath = lastSlashIdx < 0 ? "/"
                        : relativePath.substring(0, lastSlashIdx + 1);
                final boolean parentRelativePathChanged = !parentRelativePath.equals(prevParentRelativePath);
                final ScanSpecPathMatch parentMatchStatus = //
                        prevParentRelativePath == null || parentRelativePathChanged
                                ? scanSpec.dirWhitelistMatchStatus(parentRelativePath)
                                : prevParentMatchStatus;
                prevParentRelativePath = parentRelativePath;
                prevParentMatchStatus = parentMatchStatus;

                if (parentMatchStatus == ScanSpecPathMatch.HAS_BLACKLISTED_PATH_PREFIX) {
                    // The parent dir or one of its ancestral dirs is blacklisted
                    if (subLog != null) {
                        subLog.log("Skipping blacklisted path: " + relativePath);
                    }
                    continue;
                }

                // Found non-blacklisted relative path
                allResourcePaths.add(relativePath);

                // If resource is whitelisted
                if (parentMatchStatus == ScanSpecPathMatch.HAS_WHITELISTED_PATH_PREFIX
                        || parentMatchStatus == ScanSpecPathMatch.AT_WHITELISTED_PATH
                        || (parentMatchStatus == ScanSpecPathMatch.AT_WHITELISTED_CLASS_PACKAGE
                                && scanSpec.classfileIsSpecificallyWhitelisted(relativePath))) {
                    // Add whitelisted resource
                    final Resource resource = newResource(relativePath);
                    addWhitelistedResource(resource, parentMatchStatus, subLog);
                }
            }

            // Save last modified time for the module file
            final File moduleFile = moduleRef.getLocationFile();
            if (moduleFile != null && moduleFile.exists()) {
                fileToLastModified.put(moduleFile, moduleFile.lastModified());
            }

        } catch (final IOException e) {
            if (subLog != null) {
                subLog.log("Exception opening module " + moduleRef.getName(), e);
            }
            skipClasspathElement = true;
        }

        if (subLog != null) {
            if (whitelistedResources.isEmpty() && whitelistedClassfileResources.isEmpty()) {
                subLog.log("No whitelisted classfiles or resources found");
            } else if (whitelistedResources.isEmpty()) {
                subLog.log("No whitelisted resources found");
            } else if (whitelistedClassfileResources.isEmpty()) {
                subLog.log("No whitelisted classfiles found");
            }
        }

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

    /** Get the ModuleRef for this classpath element. */
    ModuleRef getModuleRef() {
        return moduleRef;
    }

    /** Return the module reference. */
    @Override
    public String toString() {
        return moduleRef.toString();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy