Inceptum/wrapper/src/main/java/io/gitlab/jfronny/inceptum/wrapper/NioClassLoader.java

102 lines
3.1 KiB
Java

package io.gitlab.jfronny.inceptum.wrapper;
import io.gitlab.jfronny.commons.io.JFiles;
import org.jetbrains.annotations.Nullable;
import java.io.Closeable;
import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.*;
import java.util.stream.Stream;
/**
* A ClassLoader that supports loading classes from Paths, which may be directories or jars
* Does not support modules
*/
public class NioClassLoader extends ClassLoader implements Closeable {
private final Set<Path> sources = new LinkedHashSet<>();
private final Set<Closeable> closeables = new HashSet<>();
static {
registerAsParallelCapable();
}
public NioClassLoader(Iterable<Path> jars, ClassLoader parent) throws IOException, URISyntaxException {
super(parent);
for (Path jar : jars) {
if (Files.isDirectory(jar)) {
sources.add(jar);
} else {
FileSystem fs = JFiles.openZipFile(jar, false, parent);
closeables.add(fs);
sources.add(fs.getPath(""));
}
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Path path = findSource(name.replace('.', '/') + ".class");
if (path == null) throw new ClassNotFoundException(name);
byte[] clazz;
try {
clazz = Files.readAllBytes(path);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
CodeSource cs = new CodeSource(toUrl(path), (Certificate[]) null);
ProtectionDomain pd = new ProtectionDomain(cs, null);
return defineClass(name, clazz, 0, clazz.length, pd);
}
@Override
protected URL findResource(String name) {
return toUrl(findSource(name));
}
@Override
protected Enumeration<URL> findResources(String name) {
return Collections.enumeration(findPaths(name).map(this::toUrl).toList());
}
private URL toUrl(Path path) {
if (path == null) return null;
try {
return path.toUri().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("Could not get resource", e);
}
}
private @Nullable Path findSource(String path) {
return findPaths(path).findFirst().orElse(null);
}
private Stream<Path> findPaths(String path) {
return sources.stream().map(s -> s.resolve(path)).filter(Files::exists);
}
@Override
public void close() {
List<IOException> exceptions = new LinkedList<>();
for (Closeable closeable : closeables) {
try {
closeable.close();
} catch (IOException e) {
exceptions.add(e);
}
}
if (!exceptions.isEmpty()) {
RuntimeException ex = new RuntimeException("Could not properly close WrapperClassLoader");
for (IOException suppressed : exceptions) {
ex.addSuppressed(suppressed);
}
throw ex;
}
}
}