
com.hazelcast.shaded.io.github.classgraph.Resource 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) 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.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import java.util.zip.ZipEntry;
import com.hazelcast.shaded.nonapi.io.github.classgraph.fileslice.reader.ClassfileReader;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.LogNode;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.URLPathEncoder;
/**
* A classpath or module path resource (i.e. file) that was found in an accepted/non-rejected package inside a
* classpath element or module.
*/
public abstract class Resource implements Closeable, Comparable {
/** The classpath element this resource was obtained from. */
private final ClasspathElement classpathElement;
/** The input stream, or null. */
protected InputStream inputStream;
/** The byte buffer, or null. */
protected ByteBuffer byteBuffer;
/** The length, or -1L for unknown. */
protected long length;
/** The cached result of toString(). */
private String toString;
/**
* The {@link LogNode} used to log that the resource was found when classpath element paths are scanned. In the
* case of accepted classfile resources, sublog entries are added when the classfile's contents are scanned.
*/
LogNode scanLog;
// -------------------------------------------------------------------------------------------------------------
/**
* Constructor.
*
* @param classpathElement
* the classpath element this resource was obtained from.
* @param length
* the length the length of the resource.
*/
public Resource(final ClasspathElement classpathElement, final long length) {
this.classpathElement = classpathElement;
this.length = length;
}
// -------------------------------------------------------------------------------------------------------------
/**
* Convert a URI to URL, catching "jrt:" URIs as invalid.
*
* @param uri
* the uri
* @return the URL.
* @throws IllegalArgumentException
* if the URI could not be converted to a URL, or the URI had "jrt:" scheme.
*/
private static URL uriToURL(final URI uri) {
try {
return uri.toURL();
} catch (final IllegalArgumentException | MalformedURLException e) {
if (uri.getScheme().equals("jrt")) {
// Currently URL cannot handle the "jrt:" scheme, used by system modules.
throw new IllegalArgumentException("Could not create URL from URI with \"jrt:\" scheme "
+ "(\"jrt:\" is not supported by the URL class without a custom URL protocol handler): "
+ uri);
} else {
throw new IllegalArgumentException("Could not create URL from URI: " + uri + " -- " + e);
}
}
}
/**
* Get the {@link URI} representing the resource's location.
*
* @return A {@link URI} representing the resource's location.
* @throws IllegalArgumentException
* the resource was obtained from a module and the module's location URI is null.
*/
public URI getURI() {
final URI locationURI = getClasspathElementURI();
final String locationURIStr = locationURI.toString();
final String resourcePath = getPathRelativeToClasspathElement();
// Check if this is a directory-based module (location URI will end in "/")
final boolean isDir = locationURIStr.endsWith("/");
try {
return new URI(
(isDir || locationURIStr.startsWith("jar:") || locationURIStr.startsWith("jrt:") ? "" : "jar:")
+ locationURIStr + (isDir ? "" : locationURIStr.startsWith("jrt:") ? "/" : "!/")
+ URLPathEncoder.encodePath(resourcePath));
} catch (final URISyntaxException e) {
throw new IllegalArgumentException("Could not form URL for classpath element: " + locationURIStr
+ " ; path: " + resourcePath + " : " + e);
}
}
/**
* Get the {@link URL} representing the resource's location. Use {@link #getURI()} instead if the resource may
* have come from a system module, or if this is a jlink'd runtime image, since "jrt:" URI schemes used by
* system modules and jlink'd runtime images are not supported by {@link URL}, and this will cause
* {@link IllegalArgumentException} to be thrown.
*
* @return A {@link URL} representing the resource's location.
* @throws IllegalArgumentException
* if the resource was obtained from a system module or jlink'd runtime image with a "jrt:" location
* URI, or the resource was obtained from a module and the module's location URI is null
*/
public URL getURL() {
return uriToURL(getURI());
}
/**
* Get the {@link URI} of the classpath element or module that this resource was obtained from.
*
* @return The {@link URL} of the classpath element or module that this resource was found within.
* @throws IllegalArgumentException
* if the classpath element does not have a valid URI (e.g. for modules whose location URI is null).
*/
public URI getClasspathElementURI() {
return classpathElement.getURI();
}
/**
* Get the {@link URL} of the classpath element or module that this resource was obtained from. Use
* {@link #getClasspathElementURI()} instead if the resource may have come from a system module, or if this is a
* jlink'd runtime image, since "jrt:" URI schemes used by system modules and jlink'd runtime images are not
* supported by {@link URL}, and this will cause {@link IllegalArgumentException} to be thrown.
*
* @return The {@link URL} of the classpath element or module that this resource was found within.
* @throws IllegalArgumentException
* if the resource was obtained from a system module or jlink'd runtime image with a "jrt:" location
* URI, or the resource was obtained from a module and the module's location URI is null.
*/
public URL getClasspathElementURL() {
return uriToURL(getClasspathElementURI());
}
/**
* Get the classpath element {@link File}.
*
* @return The {@link File} for the classpath element package root dir or jar that this {@link Resource} was
* found within, or null if this {@link Resource} was found in a module backed by a "jrt:" URI, or a
* module with an unknown location. May also return null if the classpath element was an http/https URL,
* and the jar was downloaded directly to RAM, rather than to a temp file on disk (e.g. if the temp dir
* is not writeable).
*/
public File getClasspathElementFile() {
return classpathElement.getFile();
}
/**
* Get the The {@link ModuleRef} for the module that this {@link Resource} was found within.
*
* @return The {@link ModuleRef} for the module that this {@link Resource} was found within, as a
* {@link ModuleRef}, or null if this {@link Resource} was found in a directory or jar in the classpath.
*/
public ModuleRef getModuleRef() {
return classpathElement instanceof ClasspathElementModule
? ((ClasspathElementModule) classpathElement).moduleRef
: null;
}
/**
* Convenience method to get the content of this {@link Resource} as a String. Assumes UTF8 encoding. (Calls
* {@link #close()} after completion.)
*
* @return the content of this {@link Resource} as a String.
* @throws IOException
* If an I/O exception occurred.
*/
public String getContentAsString() throws IOException {
final String content = new String(load(), StandardCharsets.UTF_8);
close();
return content;
}
// -------------------------------------------------------------------------------------------------------------
/**
* Get the path of this classpath resource relative to the package root.
*
* @return the path of this classpath resource relative to the package root. For example, for a resource path of
* {@code "BOOT-INF/classes/com/xyz/resource.xml"} and a package root of {@code "BOOT-INF/classes/"},
* returns {@code "com/xyz/resource.xml"}. Also drops version prefixes for multi-version jars, for
* example for a resource path of {@code "META-INF/versions/11/com/xyz/resource.xml"} while running on
* JDK 9+, returns {@code "com/xyz/resource.xml"}.
*/
public abstract String getPath();
/**
* Get the full path of this classpath resource relative to the root of the classpath element.
*
* @return the full path of this classpath resource within the classpath element. For example, will return the
* full path of {@code "BOOT-INF/classes/com/xyz/resource.xml"} or
* {@code "META-INF/versions/11/com/xyz/resource.xml"}, not {@code "com/xyz/resource.xml"}.
*/
public String getPathRelativeToClasspathElement() {
// Only overridden for jars
return getPath();
}
// -------------------------------------------------------------------------------------------------------------
/**
* Open an {@link InputStream} for a classpath resource. Make sure you call {@link Resource#close()} when you
* are finished with the {@link InputStream}, so that the {@link InputStream} is closed.
*
* @return The opened {@link InputStream}.
* @throws IOException
* If the {@link InputStream} could not be opened.
*/
public abstract InputStream open() throws IOException;
/**
* Open a {@link ByteBuffer} for a classpath resource. Make sure you call {@link Resource#close()} when you are
* finished with the {@link ByteBuffer}, so that the {@link ByteBuffer} is released or unmapped. See also
* {@link #readCloseable()}.
*
* @return The allocated or mapped {@link ByteBuffer} for the resource file content.
* @throws IOException
* If the resource could not be read.
*/
public abstract ByteBuffer read() throws IOException;
/**
* Open a {@link ByteBuffer} for a classpath resource, and wrap it in a {@link CloseableByteBuffer} instance,
* which implements the {@link Closeable#close()} method to free the underlying {@link ByteBuffer} when
* {@link CloseableByteBuffer#close()} is called, by automatically calling {@link Resource#close()}.
*
*
* Call {@link CloseableByteBuffer#getByteBuffer()} on the returned instance to access the underlying
* {@link ByteBuffer}.
*
* @return The allocated or mapped {@link ByteBuffer} for the resource file content.
* @throws IOException
* If the resource could not be read.
*/
public CloseableByteBuffer readCloseable() throws IOException {
return new CloseableByteBuffer(read(), new Runnable() {
@Override
public void run() {
close();
}
});
}
/**
* Load a classpath resource and return its content as a byte array. Automatically calls
* {@link Resource#close()} after loading the byte array and before returning it, so that the underlying
* InputStream is closed or the underlying ByteBuffer is released or unmapped.
*
* @return The contents of the resource file.
* @throws IOException
* If the file contents could not be loaded in their entirety.
*/
public abstract byte[] load() throws IOException;
/**
* Open a {@link ClassfileReader} on the resource (for reading classfiles).
*
* @return the {@link ClassfileReader}.
* @throws IOException
* if an I/O exception occurs.
*/
abstract ClassfileReader openClassfile() throws IOException;
/**
* Get the length of the resource.
*
* @return The length of the resource. This only reliably returns a valid value after calling {@link #open()},
* {@link #read()}, or {@link #load()} (and for {@link #open()}, only if the underlying jarfile has
* length information for corresponding {@link ZipEntry} -- some jarfiles may not have length
* information in their zip entries). Returns -1L if the length is unknown.
*/
public long getLength() {
return length;
}
/**
* Get the last modified time for the resource, in milliseconds since the epoch. This time is obtained from the
* directory entry, if this resource is a file on disk, or from the zipfile central directory, if this resource
* is a zipfile entry. Timestamps are not available for resources obtained from system modules or jlink'd
* modules.
*
*
* Note: The ZIP format has no notion of timezone, so timestamps are only meaningful if it is known what
* timezone they were created in. We arbitrarily assume that zipfile timestamps are in the UTC timezone. This
* may be a wrong assumption, so you may need to apply a timezone correction if you know the timezone used by
* the zipfile creator.
*
* @return The millis since the epoch indicating the date / time that this file resource was last modified.
* Returns 0L if the last modified date is unknown.
*/
public abstract long getLastModified();
/**
* Get the POSIX file permissions for the resource. POSIX file permissions are obtained from the directory
* entry, if this resource is a file on disk, or from the zipfile central directory, if this resource is a
* zipfile entry. POSIX file permissions are not available for resources obtained from system modules or jlink'd
* modules, and may not be available on non-POSIX-compliant operating systems or non-POSIX filesystems.
*
* @return The set of {@link PosixFilePermission} permission flags for the resource, or null if unknown.
*/
public abstract Set getPosixFilePermissions();
// -------------------------------------------------------------------------------------------------------------
/**
* Get a string representation of the resource's location (as a URL string).
*
* @return the resource location as a URL String.
*/
@Override
public String toString() {
if (toString != null) {
return toString;
} else {
return toString = getURI().toString();
}
}
/**
* Hash code.
*
* @return the int
*/
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return toString().hashCode();
}
/**
* 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 Resource)) {
return false;
}
return this.toString().equals(obj.toString());
}
/**
* Compare to.
*
* @param o
* the o
* @return the int
*/
/* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(final Resource o) {
return toString().compareTo(o.toString());
}
// -------------------------------------------------------------------------------------------------------------
/** Close the underlying InputStream, or release/unmap the underlying ByteBuffer. */
@Override
public void close() {
// Override in subclasses, and call super.close(), then at end, markAsClosed()
if (inputStream != null) {
try {
inputStream.close();
} catch (final IOException e) {
// Ignore
}
inputStream = null;
}
}
}