added library toc file

This commit is contained in:
2022-06-30 20:41:47 +08:00
parent 6f294d4dd7
commit 2b8e150949
34 changed files with 4249 additions and 59 deletions

View File

@@ -0,0 +1,5 @@
module net.woggioni.envelope.loader {
requires java.logging;
requires static lombok;
exports net.woggioni.envelope.loader;
}

View File

@@ -0,0 +1,217 @@
package net.woggioni.envelope.loader;
import lombok.SneakyThrows;
import net.woggioni.envelope.loader.JarFile;
import net.woggioni.envelope.loader.Handler;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleFinder;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.net.URL;
import java.net.URLStreamHandler;
import java.nio.file.Paths;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.Attributes.Name;
import java.util.jar.JarEntry;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JarFileModuleFinder implements ModuleFinder {
private static final String MODULE_DESCRIPTOR_ENTRY = "module-info.class";
private static final Name AUTOMATIC_MODULE_NAME_MANIFEST_ENTRY = new Name("Automatic-Module-Name");
private static class Patterns {
static final Pattern DASH_VERSION = Pattern.compile("-(\\d+(\\.|$))");
static final Pattern NON_ALPHANUM = Pattern.compile("[^A-Za-z0-9]");
static final Pattern REPEATING_DOTS = Pattern.compile("(\\.)(\\1)+");
static final Pattern LEADING_DOTS = Pattern.compile("^\\.");
static final Pattern TRAILING_DOTS = Pattern.compile("\\.$");
}
private final Map<String, Map.Entry<ModuleReference, Handler>> modules;
@SneakyThrows
private static final URI toURI(URL url) {
return url.toURI();
}
public final URLStreamHandler getStreamHandlerForModule(String moduleName) {
return modules.get(moduleName).getValue();
}
private static String cleanModuleName(String mn) {
// replace non-alphanumeric
mn = Patterns.NON_ALPHANUM.matcher(mn).replaceAll(".");
// collapse repeating dots
mn = Patterns.REPEATING_DOTS.matcher(mn).replaceAll(".");
// drop leading dots
if (!mn.isEmpty() && mn.charAt(0) == '.')
mn = Patterns.LEADING_DOTS.matcher(mn).replaceAll("");
// drop trailing dots
int len = mn.length();
if (len > 0 && mn.charAt(len-1) == '.')
mn = Patterns.TRAILING_DOTS.matcher(mn).replaceAll("");
return mn;
}
@SneakyThrows
private static String moduleNameFromURI(URI uri) {
String fn = null;
URI tmp = uri;
while(true) {
String schemeSpecificPart = tmp.getSchemeSpecificPart();
if(tmp.getPath() != null) {
String path = tmp.getPath();
int end = path.lastIndexOf("!");
if(end == -1) end = path.length();
int start = path.lastIndexOf("!", end - 1);
if(start == -1) start = 0;
fn = Paths.get(path.substring(start, end)).getFileName().toString();
break;
} else {
tmp = new URI(schemeSpecificPart);
}
}
// Derive the version, and the module name if needed, from JAR file name
int i = fn.lastIndexOf(File.separator);
if (i != -1)
fn = fn.substring(i + 1);
// drop ".jar"
String name = fn.substring(0, fn.length() - 4);
String vs = null;
// find first occurrence of -${NUMBER}. or -${NUMBER}$
Matcher matcher = Patterns.DASH_VERSION.matcher(name);
if (matcher.find()) {
int start = matcher.start();
// attempt to parse the tail as a version string
try {
String tail = name.substring(start + 1);
ModuleDescriptor.Version.parse(tail);
vs = tail;
} catch (IllegalArgumentException ignore) { }
name = name.substring(0, start);
}
return cleanModuleName(name);
}
public JarFileModuleFinder(JarFile ...jarFiles) {
this(Arrays.asList(jarFiles));
}
private static final String META_INF_VERSION_PREFIX = "META-INF/versions/";
private static Set<String> collectPackageNames(JarFile jarFile) {
Set<String> result = jarFile
.stream()
.filter(entry -> entry.getName().endsWith(".class"))
.map(entry -> {
String entryName = entry.getName();
int lastSlash = entryName.lastIndexOf('/');
if(lastSlash < 0) return null;
else return entryName.substring(0, lastSlash).replace('/', '.');
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return Collections.unmodifiableSet(result);
}
@SneakyThrows
public JarFileModuleFinder(Iterable<JarFile> jarFiles) {
TreeMap<String, Map.Entry<ModuleReference, Handler>> modules = new TreeMap<>();
for(JarFile jarFile : jarFiles) {
URI uri = jarFile.getUrl().toURI();
String moduleName = null;
ModuleDescriptor moduleDescriptor;
JarEntry moduleDescriptorEntry = jarFile.getJarEntry(MODULE_DESCRIPTOR_ENTRY);
if (moduleDescriptorEntry != null) {
try(InputStream is = jarFile.getInputStream(moduleDescriptorEntry)) {
moduleDescriptor = ModuleDescriptor.read(is, () -> collectPackageNames(jarFile));
}
} else {
Manifest mf = jarFile.getManifest();
moduleName = mf.getMainAttributes().getValue(AUTOMATIC_MODULE_NAME_MANIFEST_ENTRY);
if(moduleName == null) {
moduleName = moduleNameFromURI(uri);
}
ModuleDescriptor.Builder mdb = ModuleDescriptor.newAutomaticModule(moduleName);
mdb.packages(collectPackageNames(jarFile));
// Main-Class attribute if it exists
String mainClass = mf.getMainAttributes().getValue(Name.MAIN_CLASS);
if (mainClass != null) {
mdb.mainClass(mainClass);
}
moduleDescriptor = mdb.build();
}
modules.put(moduleDescriptor.name(),
new AbstractMap.SimpleEntry<>(new ModuleReference(moduleDescriptor, uri) {
@Override
public ModuleReader open() throws IOException {
return new ModuleReader() {
@Override
public Optional<URI> find(String name) throws IOException {
JarEntry jarEntry = jarFile.getJarEntry(name);
if(jarEntry == null) return Optional.empty();
return Optional.of(uri.resolve('!' + name));
}
@Override
public Optional<InputStream> open(String name) throws IOException {
JarEntry jarEntry = jarFile.getJarEntry(name);
if(jarEntry == null) return Optional.empty();
return Optional.of(jarFile.getInputStream(jarEntry));
}
@Override
public Stream<String> list() throws IOException {
return jarFile.stream().map(JarEntry::getName);
}
@Override
public void close() throws IOException {}
};
}
}, new Handler(jarFile)));
}
this.modules = Collections.unmodifiableMap(modules);
}
@Override
public Optional<ModuleReference> find(String name) {
return Optional.ofNullable(modules.get(name)).map(Map.Entry::getKey);
}
@Override
public Set<ModuleReference> findAll() {
return Collections.unmodifiableSet(modules.values()
.stream().map(Map.Entry::getKey)
.collect(Collectors.toSet()));
}
}

View File

@@ -0,0 +1,158 @@
package net.woggioni.envelope.loader;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import java.net.URI;
import java.net.URL;
import java.net.URLStreamHandler;
import java.nio.ByteBuffer;
import java.lang.module.ResolvedModule;
import java.lang.module.Configuration;
import java.security.CodeSource;
import java.security.SecureClassLoader;
import java.security.CodeSigner;
@RequiredArgsConstructor
public final class ModuleClassLoader extends SecureClassLoader {
static {
registerAsParallelCapable();
}
private static String className2ResourceName(String className) {
return className.replace('.', '/') + ".class";
}
private static String packageName(String cn) {
int pos = cn.lastIndexOf('.');
return (pos < 0) ? "" : cn.substring(0, pos);
}
private final Map<String, ClassLoader> packageMap;
private final ModuleReference moduleReference;
private final Function<URI, URL> urlConverter;
@Override
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(className)) {
Class<?> result = findLoadedClass(className);
if (result == null) {
result = findClass(className);
if(result == null) {
ClassLoader classLoader = packageMap.get(packageName(className));
if (classLoader != null && classLoader != this) {
result = classLoader.loadClass(className);
}
if(result == null) {
result = super.loadClass(className, resolve);
}
} else if(resolve) {
resolveClass(result);
}
}
return result;
}
}
@Override
protected URL findResource(String moduleName, String name) throws IOException {
if (Objects.equals(moduleReference.descriptor().name(), moduleName)) {
return findResource(name);
} else {
return null;
}
}
@Override
@SneakyThrows
protected URL findResource(String resource) {
try(ModuleReader reader = moduleReference.open()) {
Optional<ByteBuffer> byteBufferOptional = reader.read(resource);
if (byteBufferOptional.isPresent()) {
ByteBuffer byteBuffer = byteBufferOptional.get();
try {
return moduleReference.location()
.map(new Function<URI, URI>() {
@SneakyThrows
public URI apply(URI uri) {
return new URI(uri.toString() + resource);
}
}).map(urlConverter).orElse(null);
} finally {
reader.release(byteBuffer);
}
} else {
return null;
}
}
}
@Override
protected Enumeration<URL> findResources(final String resource) throws IOException {
return new Enumeration<URL>() {
private URL url = findResource(resource);
public boolean hasMoreElements() {
return url != null;
}
public URL nextElement() {
URL result = url;
url = null;
return result;
}
};
}
@Override
@SneakyThrows
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class<?> result = findClass(moduleReference.descriptor().name(), className);
return result;
}
@Override
@SneakyThrows
protected Class<?> findClass(String moduleName, String className) {
if (Objects.equals(moduleReference.descriptor().name(), moduleName)) {
String resource = className.replace('.', '/').concat(".class");
Optional<ByteBuffer> byteBufferOptional;
try(ModuleReader reader = moduleReference.open()) {
byteBufferOptional = reader.read(resource);
if (byteBufferOptional.isPresent()) {
ByteBuffer byteBuffer = byteBufferOptional.get();
try {
URL location = moduleReference
.location()
.map(urlConverter)
.orElse(null);
CodeSource codeSource = new CodeSource(location, (CodeSigner[]) null);
return defineClass(className, byteBuffer, codeSource);
} finally {
reader.release(byteBuffer);
}
} else {
return null;
}
}
} else {
return null;
}
}
}