Skip to content

Commit f0af43b

Browse files
committed
auto generate sideOnly config
1 parent bb54335 commit f0af43b

7 files changed

Lines changed: 181 additions & 28 deletions

File tree

examples/sideOnly.json

Lines changed: 0 additions & 10 deletions
This file was deleted.

examples/sideOnlyGenerated.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"ATTENTION": "This file is auto generated. Manual editing is not advised.",
3+
"common": {},
4+
"client": {
5+
"net.minecraftforge.common.config.Configuration": [
6+
"setCategoryConfigEntryClass()"
7+
]
8+
},
9+
"server": {}
10+
}

src/main/java/com/cleanroommc/groovyscript/core/SideOnlyConfig.java

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
import com.cleanroommc.groovyscript.GroovyScript;
44
import com.cleanroommc.groovyscript.sandbox.SandboxData;
5-
import com.google.gson.JsonElement;
6-
import com.google.gson.JsonObject;
7-
import com.google.gson.JsonParser;
5+
import com.google.gson.*;
86
import com.google.gson.stream.JsonReader;
97
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
108
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
9+
import net.minecraftforge.fml.relauncher.FMLLaunchHandler;
1110
import net.minecraftforge.fml.relauncher.Side;
11+
import org.jetbrains.annotations.ApiStatus;
1212

13-
import java.io.File;
14-
import java.io.FileInputStream;
15-
import java.io.InputStreamReader;
16-
import java.io.Reader;
13+
import java.io.*;
1714
import java.nio.charset.StandardCharsets;
15+
import java.nio.file.Files;
16+
import java.util.Collections;
17+
import java.util.List;
1818
import java.util.Map;
1919
import java.util.function.Function;
2020

@@ -27,37 +27,102 @@ public class SideOnlyConfig {
2727
};
2828
private static final Map<String, MethodSet> clientRemovals = new Object2ObjectOpenHashMap<>();
2929
private static final Map<String, MethodSet> serverRemovals = new Object2ObjectOpenHashMap<>();
30+
private static JsonObject generatedCfg;
31+
private static JsonObject generatedClient;
32+
private static JsonObject generatedServer;
3033

3134
public static void clientOnly(String className, String member) {
32-
serverRemovals.computeIfAbsent(className, DEFAULT_METHOD_SET).add(member);
35+
addAutoDetectedFailingMembers(generatedClient, className, Collections.singletonList(member));
3336
}
3437

3538
public static void serverOnly(String className, String member) {
36-
clientRemovals.computeIfAbsent(className, DEFAULT_METHOD_SET).add(member);
39+
addAutoDetectedFailingMembers(generatedServer, className, Collections.singletonList(member));
3740
}
3841

3942
static void init() {
43+
generatedCfg = readFile(SandboxData.getSideOnlyGeneratedFile());
44+
if (generatedCfg == null) {
45+
createGeneratedConfig();
46+
} else {
47+
generatedClient = generatedCfg.getAsJsonObject("client");
48+
generatedServer = generatedCfg.getAsJsonObject("server");
49+
}
50+
readFile(new File(SandboxData.getScriptFile(), "sideOnly.json"));
51+
}
52+
53+
private static void createGeneratedConfig() {
54+
generatedCfg = new JsonObject();
55+
generatedClient = new JsonObject();
56+
generatedServer = new JsonObject();
57+
generatedCfg.addProperty("ATTENTION", "This file is auto generated. Manual editing is not advised.");
58+
generatedCfg.add("common", new JsonObject());
59+
generatedCfg.add("client", generatedClient);
60+
generatedCfg.add("server", generatedServer);
61+
4062
clientOnly("net.minecraftforge.common.config.Configuration", "setCategoryConfigEntryClass()");
63+
writeGeneratedConfig();
64+
}
4165

42-
initConfig();
66+
@ApiStatus.Internal
67+
public static void addAutoDetectedFailingMembers(Class<?> c, List<String> members) {
68+
// if the current side is server, it means its likely meant for client
69+
addAutoDetectedFailingMembers(FMLLaunchHandler.side().isServer() ? generatedClient : generatedServer, c.getName(), members);
70+
}
71+
72+
private static void addAutoDetectedFailingMembers(JsonObject sideJson, String className, List<String> members) {
73+
JsonArray classJson = sideJson.getAsJsonArray(className);
74+
if (classJson == null) {
75+
classJson = new JsonArray();
76+
sideJson.add(className, classJson);
77+
}
78+
main:
79+
for (String member : members) {
80+
for (JsonElement e : classJson) {
81+
if (e.isJsonPrimitive() && e.getAsString().equals(member)) {
82+
continue main;
83+
}
84+
}
85+
classJson.add(member);
86+
}
87+
}
88+
89+
public static void writeGeneratedConfig() {
90+
File file = SandboxData.getSideOnlyGeneratedFile();
91+
try {
92+
if (file.exists()) {
93+
file.delete();
94+
} else {
95+
file.getParentFile().mkdirs();
96+
}
97+
98+
if (!file.getParentFile().isDirectory()) {
99+
if (!file.getParentFile().mkdirs()) {
100+
GroovyScriptCore.LOG.error("Failed to create file dirs on path {}", file);
101+
}
102+
}
103+
Writer writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8);
104+
writer.write(new GsonBuilder().setPrettyPrinting().create().toJson(generatedCfg));
105+
writer.close();
106+
} catch (Exception e) {
107+
GroovyScriptCore.LOG.error("Failed to save file on path {}", file, e);
108+
}
43109
}
44110

45-
private static void initConfig() {
46-
File sideOnlyConfig = new File(SandboxData.getScriptFile(), "sideOnly.json");
111+
private static JsonObject readFile(File sideOnlyConfig) {
47112
JsonObject json;
48113
try {
49-
if (!sideOnlyConfig.isFile()) return;
114+
if (!sideOnlyConfig.isFile()) return null;
50115
Reader reader = new InputStreamReader(new FileInputStream(sideOnlyConfig), StandardCharsets.UTF_8);
51116
JsonElement jsonElement = new JsonParser().parse(new JsonReader(reader));
52117
reader.close();
53118
if (jsonElement instanceof JsonObject jsonObject) {
54119
json = jsonObject;
55120
} else {
56-
return;
121+
return null;
57122
}
58123
} catch (Exception e) {
59124
GroovyScript.LOGGER.error("Failed to read file on path {}", sideOnlyConfig, e);
60-
return;
125+
return null;
61126
}
62127
if (json.has("client")) {
63128
readConfig(serverRemovals, json.getAsJsonObject("client"));
@@ -81,6 +146,7 @@ private static void initConfig() {
81146
break;
82147
}
83148
}
149+
return json;
84150
}
85151

86152
private static void readConfig(Map<String, MethodSet> removals, JsonObject json) {

src/main/java/com/cleanroommc/groovyscript/core/mixin/groovy/MetaClassImplMixin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ public void addProperties(Operation<Void> original) {
149149
try {
150150
original.call();
151151
} catch (Throwable t) {
152-
throw new JavaBeanException(t, (MetaClassImpl) (Object) this);
152+
throw JavaBeanException.of(t, (MetaClassImpl) (Object) this);
153153
}
154154
}
155155
}

src/main/java/com/cleanroommc/groovyscript/sandbox/GroovyScriptSandbox.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ public void run(LoadStage currentLoadStage) {
129129
try {
130130
load();
131131
} catch (ScriptRunException e) {
132-
GroovyLog.get().exception("Script '" + e.script.name + "' ran into an issue while executing.", e.parent);
132+
if (e.parent instanceof JavaBeanException jbe) {
133+
jbe.logError(e.script.name);
134+
} else {
135+
GroovyLog.get().exception("Script '" + e.script.name + "' ran into an issue while executing.", e.parent);
136+
}
133137
} catch (IOException | ScriptException | ResourceException e) {
134138
GroovyLog.get().exception("An exception occurred while trying to run groovy code! This is might be a internal groovy issue.", e);
135139
} catch (Throwable t) {
Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,91 @@
11
package com.cleanroommc.groovyscript.sandbox;
22

3+
import com.cleanroommc.groovyscript.api.GroovyLog;
4+
import com.cleanroommc.groovyscript.core.SideOnlyConfig;
35
import groovy.lang.MetaClassImpl;
6+
import io.github.classgraph.*;
7+
import net.minecraft.launchwrapper.Launch;
8+
9+
import java.util.ArrayList;
10+
import java.util.Collections;
11+
import java.util.List;
412

513
public class JavaBeanException extends RuntimeException {
614

715
public final Throwable parent;
816
public final MetaClassImpl metaClass;
17+
public final List<String> failingMembers;
918

10-
public JavaBeanException(Throwable parent, MetaClassImpl metaClass) {
19+
private JavaBeanException(Throwable parent, MetaClassImpl metaClass, List<String> failingMembers) {
1120
super("An error occurred while trying to gather java properties for class " + metaClass.getTheClass().getName());
1221
this.parent = parent;
1322
this.metaClass = metaClass;
23+
this.failingMembers = failingMembers;
24+
}
25+
26+
public void logError(String script) {
27+
GroovyLog.Msg msg = GroovyLog.msg("Script '{}' ran into an error while loading class '{}'", script, this.metaClass.getTheClass().getName())
28+
.error();
29+
if (this.failingMembers.isEmpty()) {
30+
msg.add("no failing members could be found");
31+
} else {
32+
msg.add("Found {} failing members. GroovyScript will add these to sideOnlyGenerated.json to attempt to fix it.", this.failingMembers.size());
33+
msg.add("Please restart the game. If the EXACT SAME ERROR still appears, report the issue to the mod authors.");
34+
SideOnlyConfig.addAutoDetectedFailingMembers(this.metaClass.getTheClass(), this.failingMembers);
35+
SideOnlyConfig.writeGeneratedConfig();
36+
}
37+
msg.post();
38+
}
39+
40+
public static JavaBeanException of(Throwable parent, MetaClassImpl metaClass) {
41+
return new JavaBeanException(parent, metaClass, findFailingMembers(metaClass.getTheClass()));
42+
}
43+
44+
private static List<String> findFailingMembers(Class<?> c) {
45+
ScanResult scanResult = new ClassGraph()
46+
.enableClassInfo()
47+
.enableMethodInfo()
48+
.enableFieldInfo()
49+
.enableSystemJarsAndModules()
50+
.overrideClassLoaders(Launch.classLoader)
51+
.acceptClasses(c.getName())
52+
.scan();
53+
ClassInfo ci = scanResult.getClassInfo(c.getName());
54+
55+
try {
56+
ci.loadClass();
57+
} catch (Throwable e) {
58+
scanResult.close();
59+
return Collections.emptyList();
60+
}
61+
62+
List<String> failingMembers = new ArrayList<>();
63+
64+
for (MethodInfo mi : ci.getDeclaredConstructorInfo()) {
65+
try {
66+
mi.loadClassAndGetConstructor();
67+
} catch (Throwable t) {
68+
failingMembers.add(mi.getName() + "()");
69+
}
70+
}
71+
72+
for (MethodInfo mi : ci.getDeclaredMethodInfo()) {
73+
try {
74+
mi.loadClassAndGetMethod();
75+
} catch (Throwable t) {
76+
failingMembers.add(mi.getName() + "()");
77+
}
78+
}
79+
80+
for (FieldInfo fi : ci.getDeclaredFieldInfo()) {
81+
try {
82+
fi.loadClassAndGetField();
83+
} catch (Throwable t) {
84+
failingMembers.add(fi.getName());
85+
}
86+
}
87+
88+
scanResult.close();
89+
return failingMembers;
1490
}
1591
}

src/main/java/com/cleanroommc/groovyscript/sandbox/SandboxData.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class SandboxData {
3838
private static File runConfigFile;
3939
private static File resourcesFile;
4040
private static File cachePath;
41+
private static File sideOnlyGeneratedFile;
4142
private static URL rootUrl;
4243
private static URL[] rootUrls;
4344
private static boolean initialised = false;
@@ -69,6 +70,7 @@ public static void initialize(File minecraftHome, Logger log) {
6970
}
7071
runConfigFile = new File(scriptPath, "runConfig.json");
7172
resourcesFile = new File(scriptPath, "assets");
73+
sideOnlyGeneratedFile = new File(scriptPath, "sideOnlyGenerated.json");
7274
try {
7375
rootUrl = scriptPath.toURI().toURL();
7476
rootUrls = new URL[]{
@@ -109,6 +111,11 @@ public static void initialize(File minecraftHome, Logger log) {
109111
return cachePath;
110112
}
111113

114+
public static File getSideOnlyGeneratedFile() {
115+
ensureLoaded();
116+
return sideOnlyGeneratedFile;
117+
}
118+
112119
public static @NotNull URL getRootUrl() {
113120
ensureLoaded();
114121
return rootUrl;

0 commit comments

Comments
 (0)