From 4234dcd9a4ad120fd00f48b0a7528df7cb562476 Mon Sep 17 00:00:00 2001 From: Runemoro Date: Mon, 20 Aug 2018 09:23:53 -0400 Subject: [PATCH] Small fixes to mod loader - Load classpath mods first, use LinkedHashMap to preserve order - Use MethodHandles rather than sun.reflect.ConstructorAccessor in ReflectionUtils (Java 9 compatibility) - Move Side class out of ModInfo - Change RiftLoader.isClient to RiftLoader.getSide() --- .../rift/listener/DimensionTypeAdder.java | 2 +- .../client/AmbientMusicTypeProvider.java | 2 +- .../mixin/hook/MixinNetHandlerPlayServer.java | 2 +- .../client/MixinNetHandlerPlayClient.java | 2 +- .../riftloader/DuplicateModException.java | 19 +++ src/main/java/org/dimdev/riftloader/Main.java | 13 +- .../riftloader/ModConflictException.java | 7 - .../java/org/dimdev/riftloader/ModInfo.java | 14 +- .../org/dimdev/riftloader/RiftLoader.java | 120 +++++++++--------- src/main/java/org/dimdev/riftloader/Side.java | 13 ++ .../riftloader/listener/Instantiator.java | 4 +- .../org/dimdev/utils/ReflectionUtils.java | 45 ++++--- 12 files changed, 132 insertions(+), 111 deletions(-) create mode 100644 src/main/java/org/dimdev/riftloader/DuplicateModException.java delete mode 100644 src/main/java/org/dimdev/riftloader/ModConflictException.java create mode 100644 src/main/java/org/dimdev/riftloader/Side.java diff --git a/src/main/java/org/dimdev/rift/listener/DimensionTypeAdder.java b/src/main/java/org/dimdev/rift/listener/DimensionTypeAdder.java index 094f5e2..cd6757a 100644 --- a/src/main/java/org/dimdev/rift/listener/DimensionTypeAdder.java +++ b/src/main/java/org/dimdev/rift/listener/DimensionTypeAdder.java @@ -9,7 +9,7 @@ public interface DimensionTypeAdder { static DimensionType newDimensionType(int id, String name, String suffix, Supplier dimensionSupplier) { - return ReflectionUtils.makeEnumInstance(DimensionType.class, new Object[]{name.toUpperCase(), -1, id, name, suffix, dimensionSupplier}); + return ReflectionUtils.makeEnumInstance(DimensionType.class, name.toUpperCase(), -1, id, name, suffix, dimensionSupplier); } Set getDimensionTypes(); diff --git a/src/main/java/org/dimdev/rift/listener/client/AmbientMusicTypeProvider.java b/src/main/java/org/dimdev/rift/listener/client/AmbientMusicTypeProvider.java index dd9bf47..5646d2b 100644 --- a/src/main/java/org/dimdev/rift/listener/client/AmbientMusicTypeProvider.java +++ b/src/main/java/org/dimdev/rift/listener/client/AmbientMusicTypeProvider.java @@ -8,7 +8,7 @@ public interface AmbientMusicTypeProvider { static MusicType newMusicType(String name, SoundEvent sound, int minDelay, int maxDelay) { - return ReflectionUtils.makeEnumInstance(MusicType.class, new Object[] {name, -1, sound, minDelay, maxDelay }); + return ReflectionUtils.makeEnumInstance(MusicType.class, name, -1, sound, minDelay, maxDelay); } MusicType getAmbientMusicType(Minecraft client); diff --git a/src/main/java/org/dimdev/rift/mixin/hook/MixinNetHandlerPlayServer.java b/src/main/java/org/dimdev/rift/mixin/hook/MixinNetHandlerPlayServer.java index 92ef43e..5b6e9fb 100644 --- a/src/main/java/org/dimdev/rift/mixin/hook/MixinNetHandlerPlayServer.java +++ b/src/main/java/org/dimdev/rift/mixin/hook/MixinNetHandlerPlayServer.java @@ -40,7 +40,7 @@ private void handleModCustomPayload(CPacketCustomPayload packet, CallbackInfo ci Class messageClass = Message.REGISTRY.getObject(channelName); if (messageClass != null) { try { - Message message = RiftLoader.instance.newInstanceOfClass(messageClass); + Message message = RiftLoader.instance.newInstance(messageClass); message.read(data); message.process(new ServerMessageContext(server, player, netManager)); } catch (ReflectiveOperationException e) { diff --git a/src/main/java/org/dimdev/rift/mixin/hook/client/MixinNetHandlerPlayClient.java b/src/main/java/org/dimdev/rift/mixin/hook/client/MixinNetHandlerPlayClient.java index f4467d1..23731f4 100644 --- a/src/main/java/org/dimdev/rift/mixin/hook/client/MixinNetHandlerPlayClient.java +++ b/src/main/java/org/dimdev/rift/mixin/hook/client/MixinNetHandlerPlayClient.java @@ -58,7 +58,7 @@ private void handleModCustomPayload(SPacketCustomPayload packet, CallbackInfo ci Class messageClass = Message.REGISTRY.getObject(channelName); if (messageClass != null) { try { - Message message = RiftLoader.instance.newInstanceOfClass(messageClass); + Message message = RiftLoader.instance.newInstance(messageClass); message.read(data); message.process(new ClientMessageContext( client, diff --git a/src/main/java/org/dimdev/riftloader/DuplicateModException.java b/src/main/java/org/dimdev/riftloader/DuplicateModException.java new file mode 100644 index 0000000..52d575f --- /dev/null +++ b/src/main/java/org/dimdev/riftloader/DuplicateModException.java @@ -0,0 +1,19 @@ +package org.dimdev.riftloader; + +public class DuplicateModException extends RuntimeException { + private final ModInfo mod1; + private final ModInfo mod2; + + public DuplicateModException(ModInfo mod1, ModInfo mod2) { + if (!mod1.id.equals(mod2.id)) { + throw new IllegalArgumentException(); + } + + this.mod1 = mod1; + this.mod2 = mod2; + } + + public String getMessage() { + return "Duplicate mod " + mod1.id + ":\r\n - " + mod1.source + "\r\n - " + mod2.source; + } +} diff --git a/src/main/java/org/dimdev/riftloader/Main.java b/src/main/java/org/dimdev/riftloader/Main.java index ba6c550..a3899ed 100644 --- a/src/main/java/org/dimdev/riftloader/Main.java +++ b/src/main/java/org/dimdev/riftloader/Main.java @@ -1,12 +1,11 @@ package org.dimdev.riftloader; import net.minecraft.launchwrapper.Launch; +import org.dimdev.utils.ReflectionUtils; import javax.swing.*; import java.io.*; -import java.lang.reflect.Method; import java.net.URL; -import java.net.URLClassLoader; import java.nio.channels.Channels; import java.nio.file.Files; import java.nio.file.Paths; @@ -51,10 +50,10 @@ public static void main(String... args) throws Throwable { new FileOutputStream(serverJar).getChannel().transferFrom(Channels.newChannel(url.openStream()), 0, Long.MAX_VALUE); } - addURLToClasspath(serverJar.toURI().toURL()); + ReflectionUtils.addURLToClasspath(serverJar.toURI().toURL()); for (String url : LIBRARIES) { - addURLToClasspath(getOrDownload(new File("libs"), new URL(url)).toURI().toURL()); + ReflectionUtils.addURLToClasspath(getOrDownload(new File("libs"), new URL(url)).toURI().toURL()); } List argsList = new ArrayList<>(Arrays.asList(args).subList(1, args.length)); @@ -80,12 +79,6 @@ private static File getOrDownload(File directory, URL url) throws IOException { return target; } - private static void addURLToClasspath(URL url) throws Exception { - Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - method.setAccessible(true); - method.invoke(ClassLoader.getSystemClassLoader(), url); - } - public static void runClientInstaller() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); diff --git a/src/main/java/org/dimdev/riftloader/ModConflictException.java b/src/main/java/org/dimdev/riftloader/ModConflictException.java deleted file mode 100644 index 36de01a..0000000 --- a/src/main/java/org/dimdev/riftloader/ModConflictException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.dimdev.riftloader; - -public class ModConflictException extends RuntimeException { - public ModConflictException(String s) { - super(s); - } -} diff --git a/src/main/java/org/dimdev/riftloader/ModInfo.java b/src/main/java/org/dimdev/riftloader/ModInfo.java index 4921b3e..b8d7bc4 100644 --- a/src/main/java/org/dimdev/riftloader/ModInfo.java +++ b/src/main/java/org/dimdev/riftloader/ModInfo.java @@ -10,7 +10,7 @@ public class ModInfo { public static Gson GSON = new GsonBuilder() .registerTypeAdapter(Listener.class, (JsonSerializer) (listener, type, context) -> { - if (listener.priority == 0 && listener.sides == Sides.BOTH) { + if (listener.priority == 0 && listener.side == Side.BOTH) { return new JsonPrimitive(listener.className); } @@ -25,20 +25,10 @@ public class ModInfo { }) .create(); - public enum Sides { - @SerializedName("client") CLIENT, - @SerializedName("server") SERVER, - @SerializedName("both") BOTH; - - public boolean includes(Sides sides) { - return this == BOTH || this == sides; - } - } - public static class Listener { @SerializedName("class") public String className; public int priority = 0; - @SerializedName("side") public Sides sides = Sides.BOTH; + public Side side = Side.BOTH; public Listener(String className) { this.className = className; diff --git a/src/main/java/org/dimdev/riftloader/RiftLoader.java b/src/main/java/org/dimdev/riftloader/RiftLoader.java index e49394f..b5a4fe0 100644 --- a/src/main/java/org/dimdev/riftloader/RiftLoader.java +++ b/src/main/java/org/dimdev/riftloader/RiftLoader.java @@ -10,17 +10,16 @@ import org.dimdev.riftloader.listener.Instantiator; import org.dimdev.utils.InstanceListMap; import org.dimdev.utils.InstanceMap; +import org.dimdev.utils.ReflectionUtils; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Constructor; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLClassLoader; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -32,17 +31,23 @@ public class RiftLoader { public final File modsDir = new File(Launch.minecraftHome, "mods"); public final File configDir = new File(Launch.minecraftHome, "config"); - public boolean isClient; + private Side side; + private boolean loaded; public AccessTransformer accessTransformer; - private Map modInfoMap = new HashMap<>(); + private Map modInfoMap = new LinkedHashMap<>(); private List> listenerClasses = new ArrayList<>(); private InstanceMap listenerInstanceMap = new InstanceMap(); private InstanceListMap listeners = new InstanceListMap(); private InstanceListMap customListenerInstances = new InstanceListMap(); public void load(boolean isClient) { - this.isClient = isClient; + if (loaded) { + throw new IllegalStateException("Already loaded"); + } + loaded = true; + + side = isClient ? Side.CLIENT : Side.SERVER; findMods(modsDir); sortMods(); @@ -55,7 +60,40 @@ public void load(boolean isClient) { * the 'modsDir' directory (creating it if it doesn't exist) and loads them * into the 'modInfoMap'. **/ - public void findMods(File modsDir) { + private void findMods(File modsDir) { + // Load classpath mods + log.info("Searching mods on classpath"); + try { + Enumeration urls = ClassLoader.getSystemResources("riftmod.json"); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + InputStream in = url.openStream(); + + // Convert jar utls to file urls (from JarUrlConnection.parseSpecs) + switch (url.getProtocol()) { + case "jar": + String spec = url.getFile(); + + int separator = spec.indexOf("!/"); + if (separator == -1) { + throw new MalformedURLException("no !/ found in url spec:" + spec); + } + + url = new URL(spec.substring(0, separator)); + + loadModFromJson(in, new File(url.toURI())); + break; + case "file": + loadModFromJson(in, new File(url.toURI()).getParentFile()); + break; + default: + throw new RuntimeException("Unsupported protocol: " + url); + } + } + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } + // Load jar mods log.info("Searching for mods in " + modsDir); modsDir.mkdirs(); @@ -90,36 +128,6 @@ public void findMods(File modsDir) { } } - // Load classpath mods - log.info("Searching mods on classpath"); - try { - Enumeration urls = ClassLoader.getSystemResources("riftmod.json"); - while (urls.hasMoreElements()) { - URL url = urls.nextElement(); - InputStream in = url.openStream(); - - // Convert jar utls to file urls (from JarUrlConnection.parseSpecs) - if (url.getProtocol().equals("jar")) { - String spec = url.getFile(); - - int separator = spec.indexOf("!/"); - if (separator == -1) { - throw new MalformedURLException("no !/ found in url spec:" + spec); - } - - url = new URL(spec.substring(0, separator)); - - loadModFromJson(in, new File(url.toURI())); - } else if (url.getProtocol().equals("file")) { - loadModFromJson(in, new File(url.toURI()).getParentFile()); - } else { - throw new RuntimeException("Unsupported protocol: " + url); - } - } - } catch (IOException | URISyntaxException e) { - throw new RuntimeException(e); - } - log.info("Loaded " + modInfoMap.size() + " mods"); } @@ -134,7 +142,7 @@ private void loadModFromJson(InputStream in, File source) { log.error("Mod file " + modInfo.source + "'s riftmod.json is missing a 'id' field"); return; } else if (modInfoMap.containsKey(modInfo.id)) { - throw new ModConflictException("Duplicate mod '" + modInfo.id + "': " + modInfoMap.get(modInfo.id).source + ", " + modInfo.source); + throw new DuplicateModException(modInfo, modInfoMap.get(modInfo.id)); } // Add the mod to the 'id -> mod info' map @@ -145,11 +153,11 @@ private void loadModFromJson(InputStream in, File source) { } } - public void sortMods() { - log.debug("Sorting mods"); // TODO + private void sortMods() { + log.debug("Sorting mods"); } - public void initMods() { + private void initMods() { log.info("Initializing mods"); // Load all the mod jars for (ModInfo modInfo : modInfoMap.values()) { @@ -164,7 +172,7 @@ public void initMods() { for (ModInfo modInfo : modInfoMap.values()) { if (modInfo.listeners != null) { for (ModInfo.Listener listener : modInfo.listeners) { - if (listener.sides.includes(isClient ? ModInfo.Sides.CLIENT : ModInfo.Sides.SERVER)) { + if (listener.side.includes(side)) { Class listenerClass; try { listenerClass = Launch.classLoader.findClass(listener.className); @@ -185,17 +193,11 @@ public void initMods() { } private static void addURLToClasspath(URL url) { - try { - Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - method.setAccessible(true); - method.invoke(ClassLoader.getSystemClassLoader(), url); - Launch.classLoader.addURL(url); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } + ReflectionUtils.addURLToClasspath(url); + Launch.classLoader.addURL(url); } - public void initAccessTransformer() { + private void initAccessTransformer() { try { AccessTransformationSet transformations = new AccessTransformationSet(); @@ -216,14 +218,14 @@ public void initAccessTransformer() { } } - public void addMod(ModInfo mod) { - modInfoMap.put(mod.id, mod); - } - public Collection getMods() { return modInfoMap.values(); } + public Side getSide() { + return side; + } + public List getListeners(Class listenerInterface) { List listenerInstances = listeners.get(listenerInterface); @@ -245,7 +247,7 @@ public void loadListeners(Class listenerInterface) { T listenerInstance = listenerInterface.cast(listenerInstanceMap.get(listenerClass)); if (listenerInstance == null) { try { - listenerInstance = listenerInterface.cast(newInstanceOfClass(listenerClass)); + listenerInstance = listenerInterface.cast(newInstance(listenerClass)); listenerInstanceMap.castAndPut(listenerClass, listenerInstance); } catch (ReflectiveOperationException e) { throw new RuntimeException("Failed to create listener instance", e); @@ -262,16 +264,16 @@ public void loadListeners(Class listenerInterface) { } } - public T newInstanceOfClass(Class listenerClass) throws ReflectiveOperationException { - for (Constructor constructor : listenerClass.getConstructors()) { + public T newInstance(Class clazz) throws ReflectiveOperationException { + for (Constructor constructor : clazz.getConstructors()) { if (constructor.getParameterCount() == 0) { - return listenerClass.cast(constructor.newInstance()); + return clazz.cast(constructor.newInstance()); } } // No no-args constructor found, ask mod instantiators to build an instance for (Instantiator instantiator : getListeners(Instantiator.class)) { - T instance = instantiator.newInstance(listenerClass); + T instance = instantiator.newInstance(clazz); if (instance != null) { return instance; } diff --git a/src/main/java/org/dimdev/riftloader/Side.java b/src/main/java/org/dimdev/riftloader/Side.java new file mode 100644 index 0000000..30621f2 --- /dev/null +++ b/src/main/java/org/dimdev/riftloader/Side.java @@ -0,0 +1,13 @@ +package org.dimdev.riftloader; + +import com.google.gson.annotations.SerializedName; + +public enum Side { + @SerializedName("client") CLIENT, + @SerializedName("server") SERVER, + @SerializedName("both") BOTH; + + public boolean includes(Side side) { + return this == BOTH || this == side; + } +} diff --git a/src/main/java/org/dimdev/riftloader/listener/Instantiator.java b/src/main/java/org/dimdev/riftloader/listener/Instantiator.java index 3284e0d..9103aa1 100644 --- a/src/main/java/org/dimdev/riftloader/listener/Instantiator.java +++ b/src/main/java/org/dimdev/riftloader/listener/Instantiator.java @@ -13,10 +13,10 @@ public interface Instantiator { * Creates an instance of a certain class, or null if the instantiator * can't handle it. * - * @param listenerClass class to instantiate + * @param clazz class to instantiate * @return an instance of listenerClass, or null to skip this instantiator * @throws ReflectiveOperationException if the instantiator can handle this type of class, * but an error occured during instantiation */ - T newInstance(Class listenerClass) throws ReflectiveOperationException; + T newInstance(Class clazz) throws ReflectiveOperationException; } diff --git a/src/main/java/org/dimdev/utils/ReflectionUtils.java b/src/main/java/org/dimdev/utils/ReflectionUtils.java index 959fd37..c295451 100644 --- a/src/main/java/org/dimdev/utils/ReflectionUtils.java +++ b/src/main/java/org/dimdev/utils/ReflectionUtils.java @@ -1,31 +1,42 @@ package org.dimdev.utils; -import sun.reflect.ConstructorAccessor; - +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; -import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; public class ReflectionUtils { - public static T makeEnumInstance(Class enumClass, Object[] constructorArgs) { + private static final MethodHandle addURLHandle; + + static { + try { + Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); + method.setAccessible(true); + addURLHandle = MethodHandles.lookup().unreflect(method); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } + } + + public static T makeEnumInstance(Class enumClass, Object... constructorArgs) { try { Constructor constructor = enumClass.getDeclaredConstructors()[0]; constructor.setAccessible(true); - Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor"); - constructorAccessorField.setAccessible(true); - ConstructorAccessor constructorAccessor = (ConstructorAccessor) constructorAccessorField.get(constructor); - - if (constructorAccessor == null) { - Method acquireConstructorAccessor = Constructor.class.getDeclaredMethod("acquireConstructorAccessor"); - acquireConstructorAccessor.setAccessible(true); - constructorAccessor = (ConstructorAccessor) acquireConstructorAccessor.invoke(constructor); - } - //noinspection unchecked - return (T) constructorAccessor.newInstance(constructorArgs); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); + return (T) MethodHandles.lookup().unreflectConstructor(constructor).invokeWithArguments(constructorArgs); + } catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t); + } + } + + public static void addURLToClasspath(URL url) { + try { + addURLHandle.invoke(ClassLoader.getSystemClassLoader(), url); + } catch (Throwable t) { + throw t instanceof RuntimeException ? (RuntimeException) t : new RuntimeException(t); } } }