Skip to content
This repository has been archived by the owner on Apr 28, 2020. It is now read-only.

Commit

Permalink
Small fixes to mod loader
Browse files Browse the repository at this point in the history
 - 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()
  • Loading branch information
Runemoro committed Aug 20, 2018
1 parent 1dd6c5a commit 4234dcd
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

public interface DimensionTypeAdder {
static DimensionType newDimensionType(int id, String name, String suffix, Supplier<? extends Dimension> 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<? extends DimensionType> getDimensionTypes();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private void handleModCustomPayload(CPacketCustomPayload packet, CallbackInfo ci
Class<? extends Message> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private void handleModCustomPayload(SPacketCustomPayload packet, CallbackInfo ci
Class<? extends Message> 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,
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/dimdev/riftloader/DuplicateModException.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
13 changes: 3 additions & 10 deletions src/main/java/org/dimdev/riftloader/Main.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String> argsList = new ArrayList<>(Arrays.asList(args).subList(1, args.length));
Expand All @@ -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());
Expand Down
7 changes: 0 additions & 7 deletions src/main/java/org/dimdev/riftloader/ModConflictException.java

This file was deleted.

14 changes: 2 additions & 12 deletions src/main/java/org/dimdev/riftloader/ModInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
public class ModInfo {
public static Gson GSON = new GsonBuilder()
.registerTypeAdapter(Listener.class, (JsonSerializer<Listener>) (listener, type, context) -> {
if (listener.priority == 0 && listener.sides == Sides.BOTH) {
if (listener.priority == 0 && listener.side == Side.BOTH) {
return new JsonPrimitive(listener.className);
}

Expand All @@ -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;
Expand Down
120 changes: 61 additions & 59 deletions src/main/java/org/dimdev/riftloader/RiftLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<String, ModInfo> modInfoMap = new HashMap<>();
private Map<String, ModInfo> modInfoMap = new LinkedHashMap<>();
private List<Class<?>> 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();
Expand All @@ -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<URL> 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();
Expand Down Expand Up @@ -90,36 +128,6 @@ public void findMods(File modsDir) {
}
}

// Load classpath mods
log.info("Searching mods on classpath");
try {
Enumeration<URL> 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");
}

Expand All @@ -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
Expand All @@ -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()) {
Expand All @@ -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);
Expand All @@ -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();

Expand All @@ -216,14 +218,14 @@ public void initAccessTransformer() {
}
}

public void addMod(ModInfo mod) {
modInfoMap.put(mod.id, mod);
}

public Collection<ModInfo> getMods() {
return modInfoMap.values();
}

public Side getSide() {
return side;
}

public <T> List<T> getListeners(Class<T> listenerInterface) {
List<T> listenerInstances = listeners.get(listenerInterface);

Expand All @@ -245,7 +247,7 @@ public <T> void loadListeners(Class<T> 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);
Expand All @@ -262,16 +264,16 @@ public <T> void loadListeners(Class<T> listenerInterface) {
}
}

public <T> T newInstanceOfClass(Class<T> listenerClass) throws ReflectiveOperationException {
for (Constructor<?> constructor : listenerClass.getConstructors()) {
public <T> T newInstance(Class<T> 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;
}
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/org/dimdev/riftloader/Side.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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> T newInstance(Class<T> listenerClass) throws ReflectiveOperationException;
<T> T newInstance(Class<T> clazz) throws ReflectiveOperationException;
}
Loading

0 comments on commit 4234dcd

Please sign in to comment.