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

com.hazelcast.shaded.io.github.classgraph.ClasspathElementModule Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of ClassGraph.
 *
 * Author: Luke Hutchison
 *
 * Hosted at: https://github.com/classgraph/classgraph
 *
 * --
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 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 com.hazelcast.shaded.io.github.classgraph;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import com.hazelcast.shaded.io.github.classgraph.Scanner.ClasspathEntryWorkUnit;
import com.hazelcast.shaded.nonapi.io.github.classgraph.concurrency.SingletonMap;
import com.hazelcast.shaded.nonapi.io.github.classgraph.concurrency.SingletonMap.NewInstanceException;
import com.hazelcast.shaded.nonapi.io.github.classgraph.concurrency.SingletonMap.NullSingletonException;
import com.hazelcast.shaded.nonapi.io.github.classgraph.concurrency.WorkQueue;
import com.hazelcast.shaded.nonapi.io.github.classgraph.fastzipfilereader.LogicalZipFile;
import com.hazelcast.shaded.nonapi.io.github.classgraph.fileslice.reader.ClassfileReader;
import com.hazelcast.shaded.nonapi.io.github.classgraph.recycler.RecycleOnClose;
import com.hazelcast.shaded.nonapi.io.github.classgraph.recycler.Recycler;
import com.hazelcast.shaded.nonapi.io.github.classgraph.scanspec.ScanSpec;
import com.hazelcast.shaded.nonapi.io.github.classgraph.scanspec.ScanSpec.ScanSpecPathMatch;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.CollectionUtils;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.LogNode;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.ProxyingInputStream;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.VersionFinder;

/**
 * A module classpath element.
 *
 * @author luke
 */
class ClasspathElementModule extends ClasspathElement {

    /** The module ref. */
    final ModuleRef moduleRef;

    /** A singleton map from a {@link ModuleRef} to a {@link ModuleReaderProxy} recycler for the module. */
    SingletonMap, IOException> //
    moduleRefToModuleReaderProxyRecyclerMap;

    /** The module reader proxy recycler. */
    private Recycler moduleReaderProxyRecycler;

    /** All resource paths. */
    private final Set allResourcePaths = new HashSet<>();

    /**
     * A zip/jarfile classpath element.
     *
     * @param moduleRef
     *            the module ref
     * @param workUnit
     *            the work unit
     * @param moduleRefToModuleReaderProxyRecyclerMap
     *            the module ref to module reader proxy recycler map
     * @param scanSpec
     *            the scan spec
     */
    ClasspathElementModule(final ModuleRef moduleRef,
            final SingletonMap, IOException> // 
            moduleRefToModuleReaderProxyRecyclerMap, final ClasspathEntryWorkUnit workUnit,
            final ScanSpec scanSpec) {
        super(workUnit, scanSpec);
        this.moduleRefToModuleReaderProxyRecyclerMap = moduleRefToModuleReaderProxyRecyclerMap;
        this.moduleRef = moduleRef;
    }

    /* (non-Javadoc)
     * @see io.github.classgraph.ClasspathElement#open(
     * nonapi.io.github.classgraph.concurrency.WorkQueue, com.hazelcast.shaded.nonapi.io.github.classgraph.utils.LogNode)
     */
    @Override
    void open(final WorkQueue workQueueIgnored, final LogNode log)
            throws InterruptedException {
        if (!scanSpec.scanModules) {
            if (log != null) {
                log(classpathElementIdx, "Skipping module, since module scanning is disabled: " + getModuleName(),
                        log);
            }
            skipClasspathElement = true;
            return;
        }
        try {
            moduleReaderProxyRecycler = moduleRefToModuleReaderProxyRecyclerMap.get(moduleRef, log);
        } catch (final IOException | NullSingletonException | NewInstanceException e) {
            if (log != null) {
                log(classpathElementIdx, "Skipping invalid module " + getModuleName() + " : "
                        + (e.getCause() == null ? e : e.getCause()), log);
            }
            skipClasspathElement = true;
            return;
        }
    }

    /**
     * Create a new {@link Resource} object for a resource or classfile discovered while scanning paths.
     *
     * @param resourcePath
     *            the resource path
     * @return the resource
     */
    private Resource newResource(final String resourcePath) {
        return new Resource(this, /* length unknown */ -1L) {
            /** The module reader proxy. */
            private ModuleReaderProxy moduleReaderProxy;

            /** True if the resource is open. */
            private final AtomicBoolean isOpen = new AtomicBoolean();

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

            @Override
            public long getLastModified() {
                return 0L; // Unknown
            }

            @Override
            public Set getPosixFilePermissions() {
                return null; // N/A
            }

            @Override
            public ByteBuffer read() throws IOException {
                if (skipClasspathElement) {
                    // Shouldn't happen
                    throw new IOException("Module could not be opened");
                }
                if (isOpen.getAndSet(true)) {
                    throw new IOException(
                            "Resource is already open -- cannot open it again without first calling close()");
                }
                try {
                    moduleReaderProxy = moduleReaderProxyRecycler.acquire();
                    // ModuleReader#read(String name) internally calls:
                    // InputStream is = open(name); return ByteBuffer.wrap(is.readAllBytes());
                    byteBuffer = moduleReaderProxy.read(resourcePath);
                    length = byteBuffer.remaining();
                    return byteBuffer;

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

            @Override
            ClassfileReader openClassfile() throws IOException {
                return new ClassfileReader(open(), this);
            }

            @Override
            public URI getURI() {
                try {
                    final ModuleReaderProxy localModuleReaderProxy = moduleReaderProxyRecycler.acquire();
                    try {
                        return localModuleReaderProxy.find(resourcePath);
                    } finally {
                        moduleReaderProxyRecycler.recycle(localModuleReaderProxy);
                    }
                } catch (final IOException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public InputStream open() throws IOException {
                if (skipClasspathElement) {
                    // Shouldn't happen
                    throw new IOException("Module could not be opened");
                }
                if (isOpen.getAndSet(true)) {
                    throw new IOException(
                            "Resource is already open -- cannot open it again without first calling close()");
                }
                try {
                    final Resource thisResource = this;
                    moduleReaderProxy = moduleReaderProxyRecycler.acquire();
                    inputStream = new ProxyingInputStream(moduleReaderProxy.open(resourcePath)) {
                        @Override
                        public void close() throws IOException {
                            // Close the wrapped InputStream obtained from moduleReaderProxy
                            super.close();
                            try {
                                // Close the Resource, releasing any underlying ByteBuffer and recycling
                                // the moduleReaderProxy
                                thisResource.close();
                            } catch (final Exception e) {
                                // Ignore
                            }
                        }
                    };
                    // Length cannot be obtained from ModuleReader
                    length = -1L;
                    return inputStream;

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

            @Override
            public byte[] load() throws IOException {
                try (Resource res = this) { // Close this after use
                    read(); // Fill byteBuffer
                    final byte[] byteArray;
                    if (res.byteBuffer.hasArray() && res.byteBuffer.position() == 0
                            && res.byteBuffer.limit() == res.byteBuffer.capacity()) {
                        byteArray = res.byteBuffer.array();
                    } else {
                        byteArray = new byte[res.byteBuffer.remaining()];
                        res.byteBuffer.get(byteArray);
                    }
                    res.length = byteArray.length;
                    return byteArray;
                }
            }

            @Override
            public void close() {
                if (isOpen.getAndSet(false)) {
                    if (moduleReaderProxy != null) {
                        if (byteBuffer != null) {
                            // Release any open ByteBuffer
                            moduleReaderProxy.release(byteBuffer);
                            byteBuffer = 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;
                    }

                    // Close inputStream
                    super.close();
                }
            }
        };
    }

    /**
     * Get the {@link Resource} for a given relative path.
     *
     * @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.
     *
     * @param log
     *            the log
     */
    @Override
    void scanPaths(final LogNode log) {
        if (skipClasspathElement) {
            return;
        }
        if (scanned.getAndSet(true)) {
            // Should not happen
            throw new IllegalArgumentException("Already scanned classpath element " + this);
        }

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

        // Determine whether this is a modular jar running under JRE 9+
        final boolean isModularJar = VersionFinder.JAVA_MAJOR_VERSION >= 9 && getModuleName() != null;

        try (RecycleOnClose moduleReaderProxyRecycleOnClose //
                = moduleReaderProxyRecycler.acquireRecycleOnClose()) {
            // Look for accepted files in the module.
            List resourceRelativePaths;
            try {
                resourceRelativePaths = moduleReaderProxyRecycleOnClose.get().list();
            } catch (final SecurityException e) {
                if (subLog != null) {
                    subLog.log("Could not get resource list for module " + moduleRef.getName(), e);
                }
                return;
            }
            CollectionUtils.sortIfNotEmpty(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;
                }

                // Paths in modules should never start with "META-INF/versions/{version}/", because the module
                // system should already strip these prefixes away. If they are found, then the jarfile must
                // contain a path like "META-INF/versions/{version}/META-INF/versions/{version}/", which cannot
                // be valid (META-INF should only ever exist in the module root), and the nested versioned section
                // should be ignored.
                if (!scanSpec.enableMultiReleaseVersions
                        && relativePath.startsWith(LogicalZipFile.MULTI_RELEASE_PATH_PREFIX)) {
                    if (subLog != null) {
                        subLog.log(
                                "Found unexpected nested versioned entry in module -- skipping: " + relativePath);
                    }
                    continue;
                }

                // If this is a modular jar, ignore all classfiles other than "module-info.class" in the
                // default package, since these are disallowed.
                if (isModularJar && relativePath.indexOf('/') < 0 && relativePath.endsWith(".class")
                        && !relativePath.equals("module-info.class")) {
                    continue;
                }

                // Accept/reject classpath elements based on file resource paths
                if (!checkResourcePathAcceptReject(relativePath, log)) {
                    continue;
                }

                // 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.dirAcceptMatchStatus(parentRelativePath)
                                : prevParentMatchStatus;
                prevParentRelativePath = parentRelativePath;
                prevParentMatchStatus = parentMatchStatus;

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

                // Found non-rejected relative path
                if (allResourcePaths.add(relativePath)) {
                    // If resource is accepted
                    if (parentMatchStatus == ScanSpecPathMatch.HAS_ACCEPTED_PATH_PREFIX
                            || parentMatchStatus == ScanSpecPathMatch.AT_ACCEPTED_PATH
                            || (parentMatchStatus == ScanSpecPathMatch.AT_ACCEPTED_CLASS_PACKAGE
                                    && scanSpec.classfileIsSpecificallyAccepted(relativePath))) {
                        // Add accepted resource
                        addAcceptedResource(newResource(relativePath), parentMatchStatus,
                                /* isClassfileOnly = */ false, subLog);
                    } else if (scanSpec.enableClassInfo && relativePath.equals("module-info.class")) {
                        // Add module descriptor as an accepted classfile resource, so that it is scanned,
                        // but don't add it to the list of resources in the ScanResult, since it is not
                        // in an accepted package (#352)
                        addAcceptedResource(newResource(relativePath), parentMatchStatus,
                                /* isClassfileOnly = */ true, 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;
        }

        finishScanPaths(subLog);
    }

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

    /**
     * Get the module name from the module reference or the module descriptor.
     *
     * @return the module name, or null if the module does not have a name.
     */
    @Override
    public String getModuleName() {
        String moduleName = moduleRef.getName();
        if (moduleName == null || moduleName.isEmpty()) {
            moduleName = moduleNameFromModuleDescriptor;
        }
        return moduleName == null || moduleName.isEmpty() ? null : moduleName;
    }

    /**
     * Get the module name from the module reference or the module descriptor.
     *
     * @return the module name, or the empty string if the module does not have a name.
     */
    private String getModuleNameOrEmpty() {
        final String moduleName = getModuleName();
        return moduleName == null ? "" : moduleName;
    }

    /* (non-Javadoc)
     * @see io.github.classgraph.ClasspathElement#getURI()
     */
    @Override
    URI getURI() {
        final URI uri = moduleRef.getLocation();
        if (uri == null) {
            // Some modules have no known module location (ModuleReference#location() can return null)
            throw new IllegalArgumentException("Module " + getModuleName() + " has a null location");
        }
        return uri;
    }

    @Override
    List getAllURIs() {
        return Collections.singletonList(getURI());
    }

    /* (non-Javadoc)
     * @see io.github.classgraph.ClasspathElement#getFile()
     */
    @Override
    File getFile() {
        try {
            final URI uri = moduleRef.getLocation();
            if (uri != null && !uri.getScheme().equals("jrt")) {
                final File file = new File(uri);
                if (file.exists()) {
                    return file;
                }
            }
        } catch (final Exception e) {
            // Invalid "file:" URI
        }
        return null;
    }

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

    /**
     * Equals.
     *
     * @param obj
     *            the obj
     * @return true, if successful
     */
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof ClasspathElementModule)) {
            return false;
        }
        final ClasspathElementModule other = (ClasspathElementModule) obj;
        return this.getModuleNameOrEmpty().equals(other.getModuleNameOrEmpty());
    }

    /**
     * Hash code.
     *
     * @return the int
     */
    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        return getModuleNameOrEmpty().hashCode();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy