Skip to content

Commit a7d319a

Browse files
committed
Update MC compatibility checks
1 parent 70afe1f commit a7d319a

File tree

9 files changed

+154
-104
lines changed

9 files changed

+154
-104
lines changed

src/main/java/com/laytonsmith/abstraction/Implementation.java

Lines changed: 98 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import com.laytonsmith.PureUtilities.ClassLoading.ClassDiscovery;
44
import com.laytonsmith.PureUtilities.Common.ReflectionUtils;
5+
import com.laytonsmith.PureUtilities.Common.ReflectionUtils.ReflectionException;
56
import com.laytonsmith.abstraction.enums.EnumConvertor;
7+
import com.laytonsmith.abstraction.enums.MCVersion;
68
import com.laytonsmith.annotations.abstractionenum;
7-
import com.laytonsmith.core.Prefs;
9+
import com.laytonsmith.core.LogLevel;
10+
import com.laytonsmith.core.MSLog;
11+
import com.laytonsmith.core.MSLog.Tags;
12+
import com.laytonsmith.core.constructs.Target;
813

9-
import java.lang.reflect.InvocationTargetException;
1014
import java.lang.reflect.Method;
11-
import java.util.Set;
1215

1316
/**
1417
* This class dynamically detects the server version being run, using various checks as needed.
@@ -67,89 +70,110 @@ public static void setServerType(Implementation.Type type) {
6770
}
6871
}
6972

73+
if(type == Type.TEST || type == Type.SHELL || !useAbstractEnumThread) {
74+
return;
75+
}
7076
//Fire off our abstractionenum checks in a new Thread
71-
if(type != Type.TEST && type != Type.SHELL && useAbstractEnumThread) {
72-
Thread abstractionenumsThread;
73-
abstractionenumsThread = new Thread(() -> {
77+
Thread abstractionenumsThread = new Thread(() -> {
78+
try {
7479
try {
75-
try {
76-
//Let the server startup data blindness go by first, so we display any error messages prominently,
77-
//since an Error is a case of very bad code that shouldn't have been released to begin with.
78-
Thread.sleep(15000);
79-
} catch (InterruptedException ex) {
80-
//
80+
//Let the server startup data blindness go by first, so we display any error messages prominently,
81+
//since an Error is a case of very bad code that shouldn't have been released to begin with.
82+
Thread.sleep(15000);
83+
} catch (InterruptedException ex) {
84+
//
85+
}
86+
for(Class c : ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(abstractionenum.class)) {
87+
abstractionenum annotation = (abstractionenum) c.getAnnotation(abstractionenum.class);
88+
if(!EnumConvertor.class.isAssignableFrom(c)) {
89+
throw new Error("Only classes that extend EnumConvertor may use @abstractionenum. "
90+
+ c.getName() + " does not, yet it uses the annotation.");
8191
}
82-
Set<Class<?>> abstractionenums = ClassDiscovery.getDefaultInstance().loadClassesWithAnnotation(abstractionenum.class);
83-
for(Class c : abstractionenums) {
84-
abstractionenum annotation = (abstractionenum) c.getAnnotation(abstractionenum.class);
85-
if(EnumConvertor.class.isAssignableFrom(c)) {
86-
EnumConvertor<Enum, Enum> convertor;
87-
try {
88-
//Now, if this is not the current server type, skip it
89-
if(annotation.implementation() != serverType) {
90-
continue;
91-
}
92-
//Next, verify usage of the annotation (it is an error if not used properly)
93-
//All EnumConvertor subclasses should have public static getConvertor methods, let's grab it now
94-
Method m = c.getDeclaredMethod("getConvertor");
95-
convertor = (EnumConvertor<Enum, Enum>) m.invoke(null);
96-
//Go through and check for a proper mapping both ways, from concrete to abstract, and vice versa.
97-
//At this point, if there is an error, it is only a warning, NOT an error.
98-
Class abstractEnum = annotation.forAbstractEnum();
99-
Class concreteEnum = annotation.forConcreteEnum();
100-
checkEnumConvertors(convertor, abstractEnum, concreteEnum, false);
101-
checkEnumConvertors(convertor, concreteEnum, abstractEnum, true);
102-
103-
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
104-
throw new Error(ex);
105-
} catch (NoSuchMethodException ex) {
106-
throw new Error(serverType.getBranding() + ": The method with signature public static " + c.getName() + " getConvertor() was not found in " + c.getName()
107-
+ " Please add the following code: \n"
108-
+ "private static " + c.getName() + " instance;\n"
109-
+ "public static " + c.getName() + " getConvertor(){\n"
110-
+ "\tif(instance == null){\n"
111-
+ "\t\tinstance = new " + c.getName() + "();\n"
112-
+ "\t}\n"
113-
+ "\treturn instance;\n"
114-
+ "}\n"
115-
+ "If you do not know what error is, please report this to the developers.");
116-
}
117-
} else {
118-
throw new Error("Only classes that extend EnumConvertor may use @abstractionenum. " + c.getName() + " does not, yet it uses the annotation.");
119-
}
120-
92+
//Now, if this is not the current server type, skip it
93+
if(annotation.implementation() != serverType) {
94+
continue;
12195
}
122-
} catch (Exception e) {
123-
boolean debugMode;
96+
EnumConvertor<Enum, Enum> convertor;
12497
try {
125-
debugMode = Prefs.DebugMode();
126-
} catch (RuntimeException ex) {
127-
//Set it to true if we fail to load prefs, which can happen
128-
//with a buggy front end.
129-
debugMode = true;
98+
//Next, verify usage of the annotation (it is an error if not used properly)
99+
//All EnumConvertor subclasses should have public static getConvertor methods, let's grab it now
100+
Method m = c.getDeclaredMethod("getConvertor");
101+
convertor = (EnumConvertor<Enum, Enum>) m.invoke(null);
102+
} catch (NoSuchMethodException ex) {
103+
throw new Error("The method with signature public static " + c.getName()
104+
+ " getConvertor() was not found in " + c.getName() + "."
105+
+ " Please add the following code: \n"
106+
+ "private static " + c.getName() + " instance;\n"
107+
+ "public static " + c.getName() + " getConvertor(){\n"
108+
+ "\tif(instance == null){\n"
109+
+ "\t\tinstance = new " + c.getName() + "();\n"
110+
+ "\t}\n"
111+
+ "\treturn instance;\n"
112+
+ "}\n"
113+
+ "If you do not know what error is, please report this to the developers.");
130114
}
131-
if(debugMode) {
132-
//If we're in debug mode, sure, go ahead and print the stack trace,
133-
//but otherwise we don't want to bother the user.
134-
e.printStackTrace();
115+
//Go through and check for a proper mapping both ways, from concrete to abstract, and vice versa.
116+
//At this point, if there is an error, it is only a warning, NOT an error.
117+
if(MSLog.GetLogger().WillLog(Tags.GENERAL, LogLevel.WARNING)) {
118+
Class abstractEnum = annotation.forAbstractEnum();
119+
Class concreteEnum = annotation.forConcreteEnum();
120+
checkAbstractEnumConversion(convertor, abstractEnum, concreteEnum);
121+
checkConcreteEnumConversion(convertor, concreteEnum, abstractEnum);
135122
}
136123
}
137-
}, "Abstraction Enum Verification Thread");
138-
abstractionenumsThread.setPriority(Thread.MIN_PRIORITY);
139-
abstractionenumsThread.setDaemon(true);
140-
abstractionenumsThread.start();
141-
}
124+
} catch (Exception e) {
125+
MSLog.GetLogger().e(Tags.GENERAL, e, Target.UNKNOWN);
126+
}
127+
}, "Abstraction Enum Verification Thread");
128+
abstractionenumsThread.setPriority(Thread.MIN_PRIORITY);
129+
abstractionenumsThread.setDaemon(true);
130+
abstractionenumsThread.start();
142131
}
143132

144-
private static void checkEnumConvertors(EnumConvertor convertor, Class to, Class from, boolean isToConcrete) {
145-
for(Object enumConst : from.getEnumConstants()) {
146-
ReflectionUtils.set(EnumConvertor.class, convertor, "useError", false);
147-
if(isToConcrete) {
148-
convertor.getConcreteEnum((Enum) enumConst);
133+
private static void checkAbstractEnumConversion(EnumConvertor convertor, Class<? extends Enum> abstracted, Class<? extends Enum> concrete) {
134+
for(Enum abstractValue : abstracted.getEnumConstants()) {
135+
Enum enumConcrete;
136+
Deprecated deprecated;
137+
try {
138+
enumConcrete = ReflectionUtils.invokeMethod(convertor, "getConcreteEnumCustom", abstractValue);
139+
deprecated = concrete.getField(enumConcrete.name()).getAnnotation(Deprecated.class);
140+
} catch (NoSuchFieldException | ReflectionException ex) {
141+
// Log missing concrete values for existing abstract values.
142+
// These can mean implementation differences, removed values, or we're running an older MC version.
143+
MSLog.GetLogger().w(Tags.GENERAL, abstracted.getSimpleName() + "." + abstractValue.name()
144+
+ " cannot be converted to " + concrete.getSimpleName(), Target.UNKNOWN);
145+
continue;
146+
}
147+
// Log deprecations of concrete values.
148+
if(deprecated == null) {
149+
continue;
150+
}
151+
if(deprecated.since().isEmpty()) {
152+
MSLog.GetLogger().i(Tags.GENERAL, concrete.getSimpleName() + "." + enumConcrete.name()
153+
+ " is deprecated", Target.UNKNOWN);
154+
} else if(MCVersion.match(deprecated.since().split("\\.")).lte(MCVersion.EARLIEST_SUPPORTED)) {
155+
MSLog.GetLogger().w(Tags.GENERAL, concrete.getSimpleName() + "." + enumConcrete.name()
156+
+ " is deprecated since " + deprecated.since(), Target.UNKNOWN);
149157
} else {
150-
convertor.getAbstractedEnum((Enum) enumConst);
158+
MSLog.GetLogger().i(Tags.GENERAL, concrete.getSimpleName() + "." + enumConcrete.name()
159+
+ " is deprecated since " + deprecated.since(), Target.UNKNOWN);
160+
}
161+
}
162+
}
163+
164+
private static void checkConcreteEnumConversion(EnumConvertor convertor, Class<? extends Enum> concrete, Class<? extends Enum> abstracted) {
165+
for(Enum concreteValue : concrete.getEnumConstants()) {
166+
try {
167+
ReflectionUtils.invokeMethod(convertor, "getAbstractedEnumCustom", concreteValue);
168+
} catch (ReflectionException ex) {
169+
try {
170+
// Log missing abstract values for concrete values that are not deprecated.
171+
if(concrete.getField(concreteValue.name()).getAnnotation(Deprecated.class) == null) {
172+
MSLog.GetLogger().w(Tags.GENERAL, concrete.getSimpleName() + "." + concreteValue.name()
173+
+ " cannot be converted to " + abstracted.getSimpleName(), Target.UNKNOWN);
174+
}
175+
} catch (NoSuchFieldException ignore) {}
151176
}
152-
ReflectionUtils.set(EnumConvertor.class, convertor, "useError", true);
153177
}
154178
}
155179

src/main/java/com/laytonsmith/abstraction/enums/EnumConvertor.java

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.laytonsmith.annotations.abstractionenum;
44
import com.laytonsmith.core.MSLog;
5-
import com.laytonsmith.core.LogLevel;
65
import com.laytonsmith.core.constructs.Target;
76

87
/**
@@ -20,11 +19,6 @@ public abstract class EnumConvertor<Abstracted extends Enum, Concrete extends En
2019
private Class<? extends Abstracted> abstractedClass;
2120
private Class<? extends Concrete> concreteClass;
2221

23-
/**
24-
* This is changed reflectively by the startup mechanism. Please do not change the name of this variable.
25-
*/
26-
private boolean useError = true;
27-
2822
protected EnumConvertor() {
2923
abstractionenum annotation = this.getClass().getAnnotation(abstractionenum.class);
3024
if(annotation == null) {
@@ -102,11 +96,7 @@ protected Concrete getConcreteEnumCustom(Abstracted abstracted) throws IllegalAr
10296
}
10397

10498
private void doLog(Class from, Class to, Enum value) {
105-
String message = from.getSimpleName() + "." + value.name() + " missing a match in " + to.getName();
106-
LogLevel level = LogLevel.WARNING;
107-
if(useError) {
108-
level = LogLevel.ERROR;
109-
}
110-
MSLog.GetLogger().Log(MSLog.Tags.RUNTIME, level, message, Target.UNKNOWN);
99+
MSLog.GetLogger().e(MSLog.Tags.RUNTIME, from.getSimpleName() + "." + value.name() + " missing a match in "
100+
+ to.getSimpleName(), Target.UNKNOWN);
111101
}
112102
}

src/main/java/com/laytonsmith/abstraction/enums/MCEntityEffect.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ public enum MCEntityEffect {
3838
HURT_EXPLOSION,
3939
DOLPHIN_FED,
4040
RAVAGER_STUNNED,
41-
CAT_TAME_FAIL,
42-
CAT_TAME_SUCCESS,
4341
VILLAGER_SPLASH,
4442
PLAYER_BAD_OMEN_RAID,
4543
HURT_BERRY_BUSH,

src/main/java/com/laytonsmith/abstraction/enums/MCVersion.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ public enum MCVersion implements Version {
5353
MC1_15_X,
5454
MC1_16,
5555
MC1_16_1,
56+
MC1_16_2,
57+
MC1_16_3,
58+
MC1_16_4,
5659
MC1_16_X,
5760
MC1_17,
5861
MC1_17_X,
@@ -80,6 +83,7 @@ public enum MCVersion implements Version {
8083
MC1_21_7,
8184
MC1_21_8,
8285
MC1_21_9,
86+
MC1_21_10,
8387
MC1_21_X,
8488
MC1_X,
8589
MC2_X,
@@ -88,6 +92,9 @@ public enum MCVersion implements Version {
8892
FUTURE,
8993
NEVER;
9094

95+
public static final MCVersion EARLIEST_SUPPORTED = MC1_16_X;
96+
public static final MCVersion LATEST_SUPPORTED = MC1_21_10;
97+
9198
public static MCVersion match(String[] source) {
9299
String[] parts = new String[Math.min(3, source.length)];
93100
for(int i = 0; i < parts.length; i++) {
@@ -136,23 +143,17 @@ public int getMajor() {
136143

137144
@Override
138145
public int getMinor() {
139-
String form = name().split("_")[1];
140-
if("X".equals(form)) {
146+
String[] parts = name().split("_");
147+
if(parts.length < 2 || "X".equals(parts[1])) {
141148
return -1;
142149
}
143-
return Integer.parseInt(form);
150+
return Integer.parseInt(parts[1]);
144151
}
145152

146153
@Override
147154
public int getSupplemental() {
148155
String[] parts = name().split("_");
149-
if(parts.length > 2) {
150-
if("X".equals(parts[2])) {
151-
return -1;
152-
}
153-
return Integer.parseInt(parts[2]);
154-
}
155-
if(getMinor() == -1) {
156+
if(parts.length < 3 || "X".equals(parts[2])) {
156157
return -1;
157158
}
158159
return Integer.parseInt(parts[2]);

src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCParticle.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static void build() {
5454
try {
5555
type = Particle.valueOf(v.name());
5656
} catch (IllegalArgumentException | NoSuchFieldError ex) {
57-
MSLog.GetLogger().w(MSLog.Tags.RUNTIME, "Could not find a Bukkit Particle for " + v.name(), Target.UNKNOWN);
57+
MSLog.GetLogger().w(Tags.GENERAL, "Could not find a Bukkit Particle for " + v.name(), Target.UNKNOWN);
5858
continue;
5959
}
6060
BukkitMCParticle wrapper = new BukkitMCParticle(v, type);

src/main/java/com/laytonsmith/abstraction/enums/bukkit/BukkitMCSound.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.laytonsmith.abstraction.enums.MCSound;
44
import com.laytonsmith.core.MSLog;
5+
import com.laytonsmith.core.MSLog.Tags;
56
import com.laytonsmith.core.Static;
67
import com.laytonsmith.core.constructs.Target;
78
import org.bukkit.Sound;
@@ -24,7 +25,7 @@ public static void build() {
2425
try {
2526
sound = (Sound) Sound.class.getDeclaredField(v.name()).get(null);
2627
} catch (IllegalAccessException | NoSuchFieldException e) {
27-
MSLog.GetLogger().w(MSLog.Tags.RUNTIME, "Could not find a Bukkit Sound for " + v.name(), Target.UNKNOWN);
28+
MSLog.GetLogger().w(Tags.GENERAL, "Could not find a Bukkit Sound for " + v.name(), Target.UNKNOWN);
2829
continue;
2930
}
3031
BukkitMCSound wrapper = new BukkitMCSound(v, sound);

src/main/java/com/laytonsmith/commandhelper/CommandHelperPlugin.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,16 @@ public void run() {
290290

291291
MSLog.initialize(CommandHelperFileLocations.getDefault().getConfigDirectory());
292292

293+
if(myServer.getMinecraftVersion().lt(MCVersion.EARLIEST_SUPPORTED)) {
294+
MSLog.GetLogger().w(MSLog.Tags.DEPRECATION, "Server is running an older unsupported Minecraft version."
295+
+ " Many features may still function, but it is recommended to use an earlier version of"
296+
+ " CommandHelper.", Target.UNKNOWN);
297+
} else if(myServer.getMinecraftVersion().gt(MCVersion.LATEST_SUPPORTED)) {
298+
MSLog.GetLogger().w(MSLog.Tags.DEPRECATION, "Server is running a newer unsupported Minecraft version."
299+
+ " Most features should continue to function, but it is recommended to update CommandHelper to a"
300+
+ " later build if one is available.", Target.UNKNOWN);
301+
}
302+
293303
Telemetry.GetDefault().initializeTelemetry();
294304
Telemetry.GetDefault().doNag();
295305
Telemetry.GetDefault().log(DefaultTelemetry.StartupModeMetric.class,

src/main/java/com/laytonsmith/core/functions/EntityManagement.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,7 @@ public MSVersion since() {
11421142
}
11431143

11441144
@api(environments = {CommandHelperEnvironment.class})
1145-
public static class play_entity_effect extends EntitySetterFunction {
1145+
public static class play_entity_effect extends EntitySetterFunction implements Optimizable {
11461146

11471147
@Override
11481148
public Mixed exec(Target t, Environment environment, Mixed... args) throws ConfigRuntimeException {
@@ -1178,6 +1178,32 @@ public MSVersion since() {
11781178
return MSVersion.V3_3_1;
11791179
}
11801180

1181+
@Override
1182+
public ParseTree optimizeDynamic(Target t, com.laytonsmith.core.environments.Environment env,
1183+
Set<Class<? extends com.laytonsmith.core.environments.Environment.EnvironmentImpl>> envs,
1184+
List<ParseTree> children, FileOptions fileOptions)
1185+
throws ConfigCompileException, ConfigRuntimeException {
1186+
1187+
if(children.size() < 2) {
1188+
return null;
1189+
}
1190+
Mixed c = children.get(1).getData();
1191+
if(c.isInstanceOf(CString.TYPE)) {
1192+
try {
1193+
MCEntityEffect.valueOf(c.val().toUpperCase());
1194+
} catch(IllegalArgumentException ex) {
1195+
env.getEnv(CompilerEnvironment.class).addCompilerWarning(fileOptions, new CompilerWarning(
1196+
c.val() + " is not a valid enum in com.commandhelper.EntityEffect",
1197+
c.getTarget(), null));
1198+
}
1199+
}
1200+
return null;
1201+
}
1202+
1203+
@Override
1204+
public Set<OptimizationOption> optimizationOptions() {
1205+
return EnumSet.of(OptimizationOption.OPTIMIZE_DYNAMIC);
1206+
}
11811207
}
11821208

11831209
@api(environments = {CommandHelperEnvironment.class})

0 commit comments

Comments
 (0)