
org.codehaus.commons.compiler.lang.ClassLoaders Maven / Gradle / Ivy
/*
* Janino - An embedded Java[TM] compiler
*
* Copyright (c) 2019 Arno Unkrig. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.commons.compiler.lang;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.codehaus.commons.compiler.java8.java.util.function.Consumer;
import org.codehaus.commons.compiler.java9.java.lang.module.ModuleFinder;
import org.codehaus.commons.compiler.java9.java.lang.module.ModuleReference;
import org.codehaus.commons.compiler.util.resource.LocatableResource;
import org.codehaus.commons.compiler.util.resource.Resource;
import org.codehaus.commons.compiler.util.resource.ResourceFinder;
import org.codehaus.commons.nullanalysis.NotNullByDefault;
import org.codehaus.commons.nullanalysis.Nullable;
/**
* Utility methods around the {@link java.lang.ClassLoader}.
*/
public final
class ClassLoaders {
/**
* The {@link ClassLoader} that loads the classes on the currently executing JVM's "class path", i.e. the JARs in
* the JRE's "lib" and "lib/ext" directories, and the JARs and class directories specified through the class path.
*/
public static final ClassLoader CLASSPATH_CLASS_LOADER = ClassLoader.getSystemClassLoader();
/**
* The {@link ClassLoader} that loads the classes on the currently executing JVM's "boot class path", i.e. the JARs
* in the JRE's "lib" and "lib/ext" directories, but not the JARs and class directories specified through the
* {@code --classpath} command line option.
*/
public static final ClassLoader BOOTCLASSPATH_CLASS_LOADER = ClassLoader.getSystemClassLoader().getParent();
private ClassLoaders() {}
/**
* Creates and returns a {@link ClassLoader} that implements {@link ClassLoader#getResourceAsStream(String)} via a
* {@link ResourceFinder}.
*
* {@link ClassLoader#getResource(String)} returns a non-{@code null} value iff then resoure finder finds a
* {@link LocatableResource}.
*
*
* Notice that {@link ClassLoader#getResources(String)} is not overridden.
*
*/
public static ClassLoader
getsResourceAsStream(final ResourceFinder finder, @Nullable ClassLoader parent) {
return new ClassLoader(parent) {
@Override @NotNullByDefault(false) public URL
getResource(String resourceName) {
{
URL result = super.getResource(resourceName);
if (result != null) return result;
}
Resource r = finder.findResource(resourceName);
if (r == null) return null;
if (r instanceof LocatableResource) {
try {
return ((LocatableResource) r).getLocation();
} catch (IOException ioe) {
return null;
}
}
return null;
}
@Override @NotNullByDefault(false) public InputStream
getResourceAsStream(String resourceName) {
{
InputStream result = super.getResourceAsStream(resourceName);
if (result != null) return result;
}
try {
return finder.findResourceAsStream(resourceName);
} catch (IOException ioe) {
return null;
}
}
};
}
/**
* Returns a name-to-URL mapping of all resources "under" a given directory name.
*
* Iff the name does not end with a slash, then calling this method is equivalent with calling
* {@link ClassLoader#getResource(String)}.
*
*
* Otherwise, if the name does end with a slash, then this method returns a name-to-URL
* mapping of all content resources who's names begin with the given name.
* Iff recurse is {@code false}, then only immediate subresources are included.
* Iff includeDirectories is {@code true}, then also directory resources are included in the result
* set; their names all ending with a slash.
*
*
* If multiple resources have the name, then the resources are retrieved from the first
* occurrence.
*
*
* @param classLoader The class loader to use; {@code null} means use the system class loader
* @param name No leading slash
* @return Keys ending with a slash map to "directory resources", the other keys map to "content
* resources"
*/
public static Map
getSubresources(
@Nullable ClassLoader classLoader,
final String name,
boolean includeDirectories,
final boolean recurse
) throws IOException {
if (classLoader == null) classLoader = ClassLoader.getSystemClassLoader();
assert classLoader != null;
HashMap result = new HashMap<>();
// See if there is a "directory resource" with the given name. This will work for
// * file-based classpath entries (e.g. "file:./target/classes/"),
// * jar-based classpath entries (e.g. "jar:./path/to/my.jar"), iff the .jar file has directory entries
for (URL r : Collections.list(classLoader.getResources(name))) {
result.putAll(ClassLoaders.getSubresourcesOf(r, name, includeDirectories, recurse));
}
// Optimization: Iff resources were found on the CLASSPATH, then don't check the BOOTCLASSPATH.
if (!result.isEmpty()) return result;
// The .jar files on the BOOTCLASSPATH lack directory entries.
result.putAll(ClassLoaders.getBootclasspathSubresourcesOf(name, includeDirectories, recurse));
return result;
}
/**
* Finds subresources on the JVM's bootstrap classpath. This is kind of tricky because the .jar files on
* the BOOTCLASSPATH don't contain "directory entries".
*/
private static Map extends String, ? extends URL>
getBootclasspathSubresourcesOf(final String name, boolean includeDirectories, final boolean recurse)
throws IOException {
return ClassLoaders.BOOTCLASSPATH_SUBRESOURCES_OF.get(name, includeDirectories, recurse);
}
/**
* @see SubresourceGetter#get(String, boolean, boolean)
*/
interface SubresourceGetter {
/**
* @return All resources that exist "under" a given resource name
*/
Map extends String, ? extends URL>
get(String name, boolean includeDirectories, boolean recurse) throws IOException;
}
private static final SubresourceGetter BOOTCLASSPATH_SUBRESOURCES_OF;
static {
// To get to the BOOCLASSPATH resources, we have to get a well-known resource. After that, we can scan the
// BOOTCLASSPATH.
URL r = ClassLoader.getSystemClassLoader().getResource("java/lang/Object.class");
assert r != null;
String protocol = r.getProtocol();
if ("jar".equalsIgnoreCase(protocol)) {
// Pre-Java-9 bootstrap classpath.
final URL jarFileURL;
final JarFile jarFile;
try {
JarURLConnection juc = (JarURLConnection) r.openConnection();
juc.setUseCaches(false);
jarFileURL = juc.getJarFileURL();
jarFile = juc.getJarFile();
} catch (IOException ioe) {
throw new AssertionError(ioe);
}
BOOTCLASSPATH_SUBRESOURCES_OF = new SubresourceGetter() {
@Override public Map extends String, ? extends URL>
get(String name, boolean includeDirectories, boolean recurse) {
return ClassLoaders.getSubresources(jarFileURL, jarFile, name, includeDirectories, recurse);
}
};
} else
if ("jrt".equalsIgnoreCase(protocol)) {
// Java-9+ bootstrap classpath.
final Set mrs = ModuleFinder.ofSystem().findAll();
BOOTCLASSPATH_SUBRESOURCES_OF = new SubresourceGetter() {
@Override public Map extends String, ? extends URL>
get(final String name, boolean includeDirectories, final boolean recurse) throws IOException {
final Map result = new HashMap<>();
for (final ModuleReference mr : mrs) {
final URI moduleContentLocation = (URI) mr.location().get();
mr.open().list().forEach(new Consumer