summaryrefslogtreecommitdiff
path: root/forgewrapper/jigsaw/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'forgewrapper/jigsaw/src/main/java')
-rw-r--r--forgewrapper/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java211
1 files changed, 211 insertions, 0 deletions
diff --git a/forgewrapper/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java b/forgewrapper/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java
new file mode 100644
index 0000000000..09792b1624
--- /dev/null
+++ b/forgewrapper/jigsaw/src/main/java/io/github/zekerzhayard/forgewrapper/installer/util/ModuleUtil.java
@@ -0,0 +1,211 @@
+package io.github.zekerzhayard.forgewrapper.installer.util;
+
+import java.io.File;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.module.Configuration;
+import java.lang.module.ModuleFinder;
+import java.lang.module.ModuleReference;
+import java.lang.module.ResolvedModule;
+import java.lang.reflect.Field;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import sun.misc.Unsafe;
+
+public class ModuleUtil {
+ private final static MethodHandles.Lookup IMPL_LOOKUP = getImplLookup();
+
+ private static MethodHandles.Lookup getImplLookup() {
+ try {
+ // Get theUnsafe
+ Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
+ unsafeField.setAccessible(true);
+ Unsafe unsafe = (Unsafe) unsafeField.get(null);
+
+ // Get IMPL_LOOKUP
+ Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
+ return (MethodHandles.Lookup) unsafe.getObject(unsafe.staticFieldBase(implLookupField), unsafe.staticFieldOffset(implLookupField));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ /**
+ * add module-path at runtime
+ */
+ @SuppressWarnings("unchecked")
+ public static void addModules(String modulePath) throws Throwable {
+ // Find all extra modules, exclude all existing modules
+ ModuleFinder finder = ModuleFinder.of(Stream.of(modulePath.split(File.pathSeparator)).map(Paths::get).filter(p -> ModuleFinder.of(p).findAll().stream().noneMatch(mref -> ModuleLayer.boot().findModule(mref.descriptor().name()).isPresent())).toArray(Path[]::new));
+ MethodHandle loadModuleMH = IMPL_LOOKUP.findVirtual(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "loadModule", MethodType.methodType(void.class, ModuleReference.class));
+
+ // Resolve modules to a new config and load all extra modules in system class loader (unnamed modules for now)
+ List<String> roots = new ArrayList<>();
+ for (ModuleReference mref : finder.findAll()) {
+ String name = mref.descriptor().name();
+ if (!ModuleLayer.boot().findModule(name).isPresent()) {
+ loadModuleMH.invokeWithArguments(ClassLoader.getSystemClassLoader(), mref);
+ roots.add(name);
+ }
+ }
+ Configuration config = Configuration.resolveAndBind(finder, List.of(ModuleLayer.boot().configuration()), finder, roots);
+
+ // Copy the new config graph to boot module layer config
+ MethodHandle graphGetter = IMPL_LOOKUP.findGetter(Configuration.class, "graph", Map.class);
+ HashMap<ResolvedModule, Set<ResolvedModule>> graphMap = new HashMap<>((Map<ResolvedModule, Set<ResolvedModule>>) graphGetter.invokeWithArguments(config));
+ MethodHandle cfSetter = IMPL_LOOKUP.findSetter(ResolvedModule.class, "cf", Configuration.class);
+ // Reset all extra resolved modules config to boot module layer config
+ for (Map.Entry<ResolvedModule, Set<ResolvedModule>> entry : graphMap.entrySet()) {
+ cfSetter.invokeWithArguments(entry.getKey(), ModuleLayer.boot().configuration());
+ for (ResolvedModule resolvedModule : entry.getValue()) {
+ cfSetter.invokeWithArguments(resolvedModule, ModuleLayer.boot().configuration());
+ }
+ }
+ graphMap.putAll((Map<ResolvedModule, Set<ResolvedModule>>) graphGetter.invokeWithArguments(ModuleLayer.boot().configuration()));
+ IMPL_LOOKUP.findSetter(Configuration.class, "graph", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(graphMap));
+
+ // Reset boot module layer resolved modules as new config resolved modules to prepare define modules
+ Set<ResolvedModule> oldBootModules = ModuleLayer.boot().configuration().modules();
+ MethodHandle modulesSetter = IMPL_LOOKUP.findSetter(Configuration.class, "modules", Set.class);
+ HashSet<ResolvedModule> modulesSet = new HashSet<>(config.modules());
+ modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet));
+
+ // Prepare to add all the new config "nameToModule" to boot module layer config
+ MethodHandle nameToModuleGetter = IMPL_LOOKUP.findGetter(Configuration.class, "nameToModule", Map.class);
+ HashMap<String, ResolvedModule> nameToModuleMap = new HashMap<>((Map<String, ResolvedModule>) nameToModuleGetter.invokeWithArguments(ModuleLayer.boot().configuration()));
+ nameToModuleMap.putAll((Map<String, ResolvedModule>) nameToModuleGetter.invokeWithArguments(config));
+ IMPL_LOOKUP.findSetter(Configuration.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot().configuration(), new HashMap<>(nameToModuleMap));
+
+ // Define all extra modules and add all the new config "nameToModule" to boot module layer config
+ ((Map<String, Module>) IMPL_LOOKUP.findGetter(ModuleLayer.class, "nameToModule", Map.class).invokeWithArguments(ModuleLayer.boot())).putAll((Map<String, Module>) IMPL_LOOKUP.findStatic(Module.class, "defineModules", MethodType.methodType(Map.class, Configuration.class, Function.class, ModuleLayer.class)).invokeWithArguments(ModuleLayer.boot().configuration(), (Function<String, ClassLoader>) name -> ClassLoader.getSystemClassLoader(), ModuleLayer.boot()));
+
+ // Add all of resolved modules
+ modulesSet.addAll(oldBootModules);
+ modulesSetter.invokeWithArguments(ModuleLayer.boot().configuration(), new HashSet<>(modulesSet));
+
+ // Reset cache of boot module layer
+ IMPL_LOOKUP.findSetter(ModuleLayer.class, "modules", Set.class).invokeWithArguments(ModuleLayer.boot(), null);
+ IMPL_LOOKUP.findSetter(ModuleLayer.class, "servicesCatalog", Class.forName("jdk.internal.module.ServicesCatalog")).invokeWithArguments(ModuleLayer.boot(), null);
+
+ // Add reads from extra modules to jdk modules
+ MethodHandle implAddReadsMH = IMPL_LOOKUP.findVirtual(Module.class, "implAddReads", MethodType.methodType(void.class, Module.class));
+ for (ResolvedModule resolvedModule : config.modules()) {
+ Module module = ModuleLayer.boot().findModule(resolvedModule.name()).orElse(null);
+ if (module != null) {
+ for (ResolvedModule bootResolvedModule : oldBootModules) {
+ Module bootModule = ModuleLayer.boot().findModule(bootResolvedModule.name()).orElse(null);
+ if (bootModule != null) {
+ implAddReadsMH.invokeWithArguments(module, bootModule);
+ }
+ }
+ }
+ }
+ }
+
+ public static void addExports(List<String> exports) {
+ TypeToAdd.EXPORTS.implAdd(exports);
+ }
+
+ public static void addOpens(List<String> opens) {
+ TypeToAdd.OPENS.implAdd(opens);
+ }
+
+ public static ClassLoader getPlatformClassLoader() {
+ return ClassLoader.getPlatformClassLoader();
+ }
+
+ private enum TypeToAdd {
+ EXPORTS("Exports"),
+ OPENS("Opens");
+
+ private final MethodHandle implAddMH;
+ private final MethodHandle implAddToAllUnnamedMH;
+
+ TypeToAdd(String name) {
+ try {
+ this.implAddMH = IMPL_LOOKUP.findVirtual(Module.class, "implAdd" + name, MethodType.methodType(void.class, String.class, Module.class));
+ this.implAddToAllUnnamedMH = IMPL_LOOKUP.findVirtual(Module.class, "implAdd" + name + "ToAllUnnamed", MethodType.methodType(void.class, String.class));
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+
+ void implAdd(List<String> extras) {
+ for (String extra : extras) {
+ ParserData data = ModuleUtil.parseModuleExtra(extra).orElse(null);
+ if (data != null) {
+ Module module = ModuleLayer.boot().findModule(data.module).orElse(null);
+ if (module != null) {
+ try {
+ if ("ALL-UNNAMED".equals(data.target)) {
+ this.implAddToAllUnnamedMH.invokeWithArguments(module, data.packages);
+ } else {
+ Module targetModule = ModuleLayer.boot().findModule(data.target).orElse(null);
+ if (targetModule != null) {
+ this.implAddMH.invokeWithArguments(module, data.packages, targetModule);
+ }
+ }
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // <module>/<package>=<target>
+ private static Optional<ParserData> parseModuleExtra(String extra) {
+ String[] all = extra.split("=", 2);
+ if (all.length < 2) {
+ return Optional.empty();
+ }
+
+ String[] source = all[0].split("/", 2);
+ if (source.length < 2) {
+ return Optional.empty();
+ }
+ return Optional.of(new ParserData(source[0], source[1], all[1]));
+ }
+
+ private static class ParserData {
+ final String module;
+ final String packages;
+ final String target;
+
+ ParserData(String module, String packages, String target) {
+ this.module = module;
+ this.packages = packages;
+ this.target = target;
+ }
+ }
+
+ public static void setupClassPath(Path libraryDir, List<String> paths) throws Throwable {
+ Class<?> urlClassPathClass = Class.forName("jdk.internal.loader.URLClassPath");
+ Object ucp = IMPL_LOOKUP.findGetter(Class.forName("jdk.internal.loader.BuiltinClassLoader"), "ucp", urlClassPathClass).invokeWithArguments(ClassLoader.getSystemClassLoader());
+ MethodHandle addURLMH = IMPL_LOOKUP.findVirtual(urlClassPathClass, "addURL", MethodType.methodType(void.class, URL.class));
+ for (String path : paths) {
+ addURLMH.invokeWithArguments(ucp, libraryDir.resolve(path).toUri().toURL());
+ }
+ }
+
+ // ForgeWrapper need some extra settings to invoke BootstrapLauncher.
+ public static Class<?> setupBootstrapLauncher(Class<?> mainClass) throws Throwable {
+ if (!mainClass.getModule().isOpen(mainClass.getPackageName(), ModuleUtil.class.getModule())) {
+ TypeToAdd.OPENS.implAddMH.invokeWithArguments(mainClass.getModule(), mainClass.getPackageName(), ModuleUtil.class.getModule());
+ }
+ return mainClass;
+ }
+}