Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add onChange attribute #10

Merged
merged 4 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions common/src/main/java/dev/xpple/betterconfig/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,24 @@
* </p>
*
* <p>
* To track changes to the configs, you can use the {@link Config#onChange()} attribute.
* Its value represents the name of a method where the changes can be tracked. The method
* should have two parameters; one for the old value and one for the new value. For
* example:
* <pre>
* {@code
* @Config(onChange = "exampleOnChange")
* public static String exampleString = "defaultString";
* public static void exampleOnChange(String oldValue, String newValue) {
* System.out.println("Old: " + oldValue + ", new: " + newValue);
* }
* }
* </pre>
* Both values are deep copies of the config that was changed, so they can be modified
* freely without care for the original object.
* </p>
*
* <p>
* To make a configuration unmodifiable by commands, mark it with {@code readOnly = true}.
* Enabling this will ignore all update annotations. To make a configuration temporary,
* that is, to disable loading and saving from a config file, set {@code temporary} to
Expand Down Expand Up @@ -83,6 +101,8 @@
Putter putter() default @Putter;
Remover remover() default @Remover;

String onChange() default "";

boolean readOnly() default false;
boolean temporary() default false;
String condition() default "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import dev.xpple.betterconfig.BetterConfigCommon;
import dev.xpple.betterconfig.api.Config;
import dev.xpple.betterconfig.util.CheckedRunnable;

import java.io.BufferedReader;
import java.io.BufferedWriter;
Expand All @@ -19,6 +20,7 @@
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;

public class BetterConfigInternals {

Expand Down Expand Up @@ -47,12 +49,12 @@ public static void init(ModConfigImpl<?, ?> modConfig) {

String fieldName = field.getName();
modConfig.getConfigs().put(fieldName, field);
modConfig.getAnnotations().put(fieldName, annotation);
try {
modConfig.getDefaults().put(fieldName, modConfig.getGson().fromJson(modConfig.getGson().toJsonTree(field.get(null)), field.getGenericType()));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
modConfig.getAnnotations().put(fieldName, annotation);

if (!annotation.comment().isEmpty()) {
modConfig.getComments().put(fieldName, annotation.comment());
Expand All @@ -74,61 +76,21 @@ public static void init(ModConfigImpl<?, ?> modConfig) {
}
}

if (annotation.condition().isEmpty()) {
modConfig.getConditions().put(fieldName, source -> true);
} else {
Method predicateMethod;
boolean hasParameter = false;
try {
predicateMethod = modConfig.getConfigsClass().getDeclaredMethod(annotation.condition());
} catch (ReflectiveOperationException e) {
hasParameter = true;
try {
Class<?> commandSourceClass = Platform.current.getCommandSourceClass();
predicateMethod = modConfig.getConfigsClass().getDeclaredMethod(annotation.condition(), commandSourceClass);
} catch (ReflectiveOperationException e1) {
throw new AssertionError(e1);
}
}
if (predicateMethod.getReturnType() != boolean.class) {
throw new AssertionError("Condition method '" + annotation.condition() + "' does not return boolean");
}
if (!Modifier.isStatic(predicateMethod.getModifiers())) {
throw new AssertionError("Condition method '" + annotation.condition() + "' is not static");
}
predicateMethod.setAccessible(true);

Method predicateMethod_f = predicateMethod;

if (hasParameter) {
modConfig.getConditions().put(fieldName, source -> {
try {
return (Boolean) predicateMethod_f.invoke(null, source);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
});
} else {
modConfig.getConditions().put(fieldName, source -> {
try {
return (Boolean) predicateMethod_f.invoke(null);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
});
}
}
initCondition(modConfig, annotation.condition(), fieldName);

if (annotation.readOnly()) {
continue;
}

BiConsumer<Object, Object> onChange = initOnChange(modConfig, field, annotation.onChange());

Class<?> type = field.getType();
if (Collection.class.isAssignableFrom(type)) {
initCollection(modConfig, field, annotation);
initCollection(modConfig, field, annotation, onChange);
} else if (Map.class.isAssignableFrom(type)) {
initMap(modConfig, field, annotation);
initMap(modConfig, field, annotation, onChange);
} else {
initObject(modConfig, field, annotation);
initObject(modConfig, field, annotation, onChange);
}
}

Expand All @@ -141,7 +103,77 @@ public static void init(ModConfigImpl<?, ?> modConfig) {
}
}

private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, Config annotation) {
private static void initCondition(ModConfigImpl<?, ?> modConfig, String condition, String fieldName) {
if (condition.isEmpty()) {
modConfig.getConditions().put(fieldName, source -> true);
return;
}
Method predicateMethod;
boolean hasParameter = false;
try {
predicateMethod = modConfig.getConfigsClass().getDeclaredMethod(condition);
} catch (ReflectiveOperationException e) {
hasParameter = true;
try {
Class<?> commandSourceClass = Platform.current.getCommandSourceClass();
predicateMethod = modConfig.getConfigsClass().getDeclaredMethod(condition, commandSourceClass);
} catch (ReflectiveOperationException e1) {
throw new AssertionError(e1);
}
}
if (predicateMethod.getReturnType() != boolean.class) {
throw new AssertionError("Condition method '" + condition + "' does not return boolean");
}
if (!Modifier.isStatic(predicateMethod.getModifiers())) {
throw new AssertionError("Condition method '" + condition + "' is not static");
}
predicateMethod.setAccessible(true);

Method predicateMethod_f = predicateMethod;

if (hasParameter) {
modConfig.getConditions().put(fieldName, source -> {
try {
return (Boolean) predicateMethod_f.invoke(null, source);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
});
} else {
modConfig.getConditions().put(fieldName, source -> {
try {
return (Boolean) predicateMethod_f.invoke(null);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
});
}
}

private static BiConsumer<Object, Object> initOnChange(ModConfigImpl<?, ?> modConfig, Field field, String onChangeMethodName) {
if (onChangeMethodName.isEmpty()) {
return (oldValue, newValue) -> {};
}
Method onChangeMethod;
try {
onChangeMethod = modConfig.getConfigsClass().getDeclaredMethod(onChangeMethodName, field.getType(), field.getType());
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
onChangeMethod.setAccessible(true);
BiConsumer<Object, Object> onChange = (oldValue, newValue) -> {
try {
onChangeMethod.invoke(null, oldValue, newValue);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
};

modConfig.getOnChangeCallbacks().put(field.getName(), onChange);
return onChange;
}

private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
String fieldName = field.getName();
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
Config.Adder adder = annotation.adder();
Expand All @@ -157,7 +189,7 @@ private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, C
}
modConfig.getAdders().put(fieldName, value -> {
try {
add.invoke(field.get(null), value);
onChange(modConfig, field, () -> add.invoke(field.get(null), value), onChange);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
Expand All @@ -173,7 +205,7 @@ private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, C
adderMethod.setAccessible(true);
modConfig.getAdders().put(fieldName, value -> {
try {
adderMethod.invoke(null, value);
onChange(modConfig, field, () -> adderMethod.invoke(null, value), onChange);
} catch (ReflectiveOperationException e) {
if (e.getCause() instanceof CommandSyntaxException commandSyntaxException) {
throw commandSyntaxException;
Expand All @@ -195,7 +227,7 @@ private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, C
}
modConfig.getRemovers().put(fieldName, value -> {
try {
remove.invoke(field.get(null), value);
onChange(modConfig, field, () -> remove.invoke(field.get(null), value), onChange);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
Expand All @@ -211,7 +243,7 @@ private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, C
removerMethod.setAccessible(true);
modConfig.getRemovers().put(fieldName, value -> {
try {
removerMethod.invoke(null, value);
onChange(modConfig, field, () -> removerMethod.invoke(null, value), onChange);
} catch (ReflectiveOperationException e) {
if (e.getCause() instanceof CommandSyntaxException commandSyntaxException) {
throw commandSyntaxException;
Expand All @@ -222,7 +254,7 @@ private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, C
}
}

private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config annotation) {
private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
String fieldName = field.getName();
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
Config.Adder adder = annotation.adder();
Expand All @@ -240,7 +272,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
adderMethod.setAccessible(true);
modConfig.getAdders().put(fieldName, key -> {
try {
adderMethod.invoke(null, key);
onChange(modConfig, field, () -> adderMethod.invoke(null, key), onChange);
} catch (ReflectiveOperationException e) {
if (e.getCause() instanceof CommandSyntaxException commandSyntaxException) {
throw commandSyntaxException;
Expand All @@ -262,7 +294,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
}
modConfig.getPutters().put(fieldName, (key, value) -> {
try {
put.invoke(field.get(null), key, value);
onChange(modConfig, field, () -> put.invoke(field.get(null), key, value), onChange);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
Expand All @@ -279,7 +311,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
putterMethod.setAccessible(true);
modConfig.getPutters().put(fieldName, (key, value) -> {
try {
putterMethod.invoke(null, key, value);
onChange(modConfig, field, () -> putterMethod.invoke(null, key, value), onChange);
} catch (ReflectiveOperationException e) {
if (e.getCause() instanceof CommandSyntaxException commandSyntaxException) {
throw commandSyntaxException;
Expand All @@ -301,7 +333,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
}
modConfig.getRemovers().put(fieldName, key -> {
try {
remove.invoke(field.get(null), key);
onChange(modConfig, field, () -> remove.invoke(field.get(null), key), onChange);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
Expand All @@ -317,7 +349,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
removerMethod.setAccessible(true);
modConfig.getRemovers().put(fieldName, key -> {
try {
removerMethod.invoke(null, key);
onChange(modConfig, field, () -> removerMethod.invoke(null, key), onChange);
} catch (ReflectiveOperationException e) {
if (e.getCause() instanceof CommandSyntaxException commandSyntaxException) {
throw commandSyntaxException;
Expand All @@ -328,7 +360,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
}
}

private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Config annotation) {
private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
String fieldName = field.getName();
Config.Setter setter = annotation.setter();
String setterMethodName = setter.value();
Expand All @@ -337,7 +369,7 @@ private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Confi
} else if (setterMethodName.isEmpty()) {
modConfig.getSetters().put(fieldName, value -> {
try {
field.set(null, value);
onChange(modConfig, field, () -> field.set(null, value), onChange);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
Expand All @@ -353,7 +385,7 @@ private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Confi
setterMethod.setAccessible(true);
modConfig.getSetters().put(fieldName, value -> {
try {
setterMethod.invoke(null, value);
onChange(modConfig, field, () -> setterMethod.invoke(null, value), onChange);
} catch (ReflectiveOperationException e) {
if (e.getCause() instanceof CommandSyntaxException commandSyntaxException) {
throw commandSyntaxException;
Expand All @@ -363,4 +395,11 @@ private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Confi
});
}
}

static void onChange(ModConfigImpl<?, ?> modConfig, Field field, CheckedRunnable<ReflectiveOperationException> updater, BiConsumer<Object, Object> onChange) throws ReflectiveOperationException {
Object oldValue = modConfig.deepCopy(field.get(null), field.getGenericType());
updater.run();
Object newValue = modConfig.deepCopy(field.get(null), field.getGenericType());
onChange.accept(oldValue, newValue);
}
}
Loading
Loading