ClassLoaders.java
/*-
* #%L
* io.earcam.instrumental.lade
* %%
* Copyright (C) 2018 earcam
* %%
* SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
*
* You <b>must</b> choose to accept, in full - any individual or combination of
* the following licenses:
* <ul>
* <li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
* <li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
* <li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
* <li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
* </ul>
* #L%
*/
package io.earcam.instrumental.lade;
import static io.earcam.unexceptional.Exceptional.swallow;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import io.earcam.unexceptional.Exceptional;
/**
* <p>
* ClassLoaders class.
* </p>
*
*/
public final class ClassLoaders {
/**
* In case this {@link InMemoryClassLoader} is used as the system classloader (by setting
* the system property "<tt>java.system.class.loader</tt>"), we need to hold onto the
* original system {@link ClassLoader} use to load this {@link InMemoryClassLoader}
*/
private static final ClassLoader JVM_SYSTEM_CLASSLOADER = InMemoryClassLoader.class.getClassLoader();
private static final class SelfFirstUrlClassLoader extends URLClassLoader {
private final Map<String, Class<?>> loaded = new HashMap<>();
public SelfFirstUrlClassLoader(URL[] urls, ClassLoader parent)
{
super(urls, parent);
}
@Override
public void close() throws IOException
{
loaded.clear();
super.close();
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
try {
Class<?> wasLoaded = loaded.get(name);
if(wasLoaded != null) {
return wasLoaded;
}
Class<?> found = findClass(name);
loaded.put(name, found);
return found;
} catch(ClassNotFoundException e) {
swallow(e);
return super.loadClass(name, resolve);
}
}
}
private ClassLoaders()
{}
/**
* Shortcut for loading a single class - curt but expensive.
*
* @param bytes bytecode
* @return the class defined by the bytecode
* @see InMemoryClassLoader#define(byte[])
*/
public static Class<?> load(byte[] bytes)
{
return load(null, bytes, 0, bytes.length);
}
public static Class<?> load(String name, byte[] bytes, int offset, int length)
{
try(InMemoryClassLoader classLoader = inMemoryClassLoader()) {
return classLoader.define(name, bytes, offset, length, null);
}
}
/**
* Runs the {@code runnable} with the {@code loader} set as the current threads'
* context {@link ClassLoader}, and finally resets the context
*
* @param loader the ClassLoader to use in context
* @param runnable the executable to run in context
*
* @see Thread#setContextClassLoader(ClassLoader)
*/
public static void runInContext(ClassLoader loader, Runnable runnable)
{
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(loader);
runnable.run();
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
/**
* <p>
* selfFirstClassLoader.
* </p>
*
* @param jars a {@link java.nio.file.Path} object.
* @return a {@link java.net.URLClassLoader} object.
*/
public static URLClassLoader selfFirstClassLoader(Path... jars)
{
return selfFirstClassLoader(ClassLoader.getSystemClassLoader(), jars);
}
/**
* <p>
* selfFirstClassLoader.
* </p>
*
* @param parent a {@link java.lang.ClassLoader} object.
* @param jars a {@link java.nio.file.Path} object.
* @return a {@link java.net.URLClassLoader} object.
*/
public static URLClassLoader selfFirstClassLoader(ClassLoader parent, Path... jars)
{
return selfFirstClassLoader(parent, Arrays.stream(jars).map(Path::toUri));
}
private static URLClassLoader selfFirstClassLoader(ClassLoader parent, Stream<URI> uris)
{
URL[] jars = uris.map(Exceptional.uncheckFunction(URI::toURL)).toArray(s -> new URL[s]);
return new SelfFirstUrlClassLoader(jars, parent);
}
/**
* Creates a new in-memory {@link java.lang.ClassLoader} with the system class loader as parent (parent-last order).
* To create with no parent, use the single parameter constructor and pass null argument.
*
* @see ClassLoader#getSystemClassLoader()
* @return a {@link io.earcam.instrumental.lade.InMemoryClassLoader} object.
*/
public static InMemoryClassLoader inMemoryClassLoader()
{
return ClassLoaders.inMemoryClassLoader(JVM_SYSTEM_CLASSLOADER);
}
/**
* Creates a new in-memory {@link java.lang.ClassLoader} with provided class loader as parent (parent-last order).
*
* @param parent the parent classloader to delegate to (may be null)
* @return a {@link io.earcam.instrumental.lade.InMemoryClassLoader} object.
*/
public static InMemoryClassLoader inMemoryClassLoader(ClassLoader parent)
{
return new InMemoryClassLoader(parent);
}
/**
* Creates a new in-memory, isolated {@link java.lang.ClassLoader} <b>without</b> parent
* {@link java.lang.ClassLoader}
*
* @return a {@link io.earcam.instrumental.lade.InMemoryClassLoader} object.
*/
public static InMemoryClassLoader orphanedInMemoryClassLoader()
{
return new InMemoryClassLoader(null);
}
}