aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/CfgMigrator.java126
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/CfgName.java19
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/JsonMigrator.java152
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/JsonName.java19
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/Migrator.java16
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceMigrator.java25
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceName.java6
7 files changed, 353 insertions, 10 deletions
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/CfgMigrator.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/CfgMigrator.java
new file mode 100644
index 0000000..d558649
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/CfgMigrator.java
@@ -0,0 +1,126 @@
+package cc.polyfrost.oneconfig.config.migration;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CfgMigrator implements Migrator {
+ final String filePath;
+ final boolean fileExists;
+ HashMap<String, HashMap<String, Object>> values;
+ final Pattern stringOrFloatPattern = Pattern.compile("S:(?<name>\\S+)=(?<value>\\S+)");
+ final Pattern intPattern = Pattern.compile("I:(?<name>\\S+)=(?<value>\\S+)");
+ final Pattern doublePattern = Pattern.compile("D:(?<name>\\S+)=(?<value>\\S+)");
+ final Pattern booleanPattern = Pattern.compile("B:(?<name>\\S+)=(?<value>\\S+)");
+ final Pattern listPattern = Pattern.compile("S:(?<name>\\S+)\\s<");
+ final Pattern categoryPattern = Pattern.compile("(?<name>\\S+)\\s\\Q{");
+
+ public CfgMigrator(String filePath) {
+ this.filePath = filePath;
+ this.fileExists = new File(filePath).exists();
+ }
+
+
+ /**
+ * Get the value from its name, category, and subcategory. The target field is also supplied, which can be used to check for {@link VigilanceName}.
+ * The returned Object is intended to be a "Duck" object, and should be cast to the correct type. The Migrator should never return ClassCastExceptions.
+ * <br><b>NOTE: .cfg files DO NOT support subcategories! The implementation of this is:</b> <br>
+ * <pre>{@code // if a category and a subcategory is supplied, only the category is used. else:
+ * if(category == null && subcategory != null) category = subcategory;}</pre>
+ *
+ * @param subcategory The subcategory of the option (not supported!)
+ */
+ @Nullable
+ @Override
+ public Object getValue(Field field, @NotNull String name, @Nullable String category, @Nullable String subcategory) {
+ if (!fileExists) return null;
+ if (values == null) generateValues();
+ if (field.isAnnotationPresent(CfgName.class)) {
+ CfgName annotation = field.getAnnotation(CfgName.class);
+ name = annotation.name();
+ category = annotation.category();
+ }
+ name = parse(name);
+ if (category == null) {
+ if (subcategory != null) {
+ category = parse(subcategory);
+ }
+ } else category = parse(category);
+ return values.getOrDefault(category, new HashMap<>()).getOrDefault(name, null);
+ }
+
+ protected void generateValues() {
+ if (values == null) values = new HashMap<>();
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String currentCategory = null;
+ String line;
+ while ((line = reader.readLine()) != null) {
+ Matcher categoryMatcher = categoryPattern.matcher(line);
+ if (categoryMatcher.find()) {
+ currentCategory = categoryMatcher.group("name");
+ if (!values.containsKey(currentCategory)) values.put(currentCategory, new HashMap<>());
+ }
+ if (currentCategory == null) continue;
+ if (line.contains("\"")) {
+ line = line.replaceAll("\"", "").replaceAll(" ", "");
+ }
+ Matcher booleanMatcher = booleanPattern.matcher(line);
+ if (booleanMatcher.find()) {
+ values.get(currentCategory).put(booleanMatcher.group("name"), Boolean.parseBoolean(booleanMatcher.group("value")));
+ continue;
+ }
+ Matcher intMatcher = intPattern.matcher(line);
+ if (intMatcher.find()) {
+ values.get(currentCategory).put(intMatcher.group("name"), Integer.parseInt(intMatcher.group("value")));
+ continue;
+ }
+ Matcher doubleMatcher = doublePattern.matcher(line);
+ if (doubleMatcher.find()) {
+ values.get(currentCategory).put(doubleMatcher.group("name"), Double.parseDouble(doubleMatcher.group("value")));
+ continue;
+ }
+ Matcher stringOrFloatMatcher = stringOrFloatPattern.matcher(line.trim());
+ if (stringOrFloatMatcher.matches()) {
+ if (line.contains(".")) {
+ try {
+ values.get(currentCategory).put(stringOrFloatMatcher.group("name"), Float.parseFloat(stringOrFloatMatcher.group("value")));
+ } catch (Exception ignored) {
+ values.get(currentCategory).put(stringOrFloatMatcher.group("name"), stringOrFloatMatcher.group("value"));
+ }
+ }
+ values.get(currentCategory).put(stringOrFloatMatcher.group("name"), stringOrFloatMatcher.group("value"));
+ continue;
+ }
+ Matcher listMatcher = listPattern.matcher(line.trim());
+ if (listMatcher.matches()) {
+ String name = listMatcher.group("name");
+ ArrayList<String> list = new ArrayList<>();
+ while ((line = reader.readLine()) != null && !line.contains(">")) {
+ list.add(line.trim());
+ }
+ String[] array = new String[list.size()];
+ // type casting doesn't work, so you have to do this
+ list.toArray(array);
+ values.get(currentCategory).put(name, array);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @NotNull
+ protected String parse(@NotNull String value) {
+ if (value.contains("\"")) {
+ return value.replaceAll("\"", "").replaceAll(" ", "");
+ } else return value;
+ }
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/CfgName.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/CfgName.java
new file mode 100644
index 0000000..560c374
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/CfgName.java
@@ -0,0 +1,19 @@
+package cc.polyfrost.oneconfig.config.migration;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <pre style="font-size: 12px">{@code public @interface CfgName}</pre>
+ * This interface is used to specify a previous name for an element.<br>
+ * For example, if you changed the name of a variable when you migrated/updated your mod to use OneConfig,
+ * you can use this annotation to specify the previous name so that the Migrator can grab it.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface CfgName {
+ String name();
+ String category();
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/JsonMigrator.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/JsonMigrator.java
new file mode 100644
index 0000000..292d583
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/JsonMigrator.java
@@ -0,0 +1,152 @@
+package cc.polyfrost.oneconfig.config.migration;
+
+import com.google.gson.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.FileReader;
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * <pre>{@code public class JsonMigrator implements Migrator}</pre>
+ * <h2>JsonMigrator is a class that is used to migrate old configs using ANY .json system to the new OneConfig one.</h2>
+ * It works using the {@link JsonName} annotation to specify a full, case sensitive, dot (or slash) path to the element; <br>or if the annotation isn't present, it will assume the target is in the 'master' object, and will use field.getName(). <br><br>
+ * An easy way to get this path is to right-click on any json key in IntelliJ, and select "Copy JSON Pointer" or Copy Special > Copy Reference (Ctrl+Alt+Shift+C) <br><br>
+ * <h2>Examples</h2>
+ * Here is our example .json:
+ * <pre>{@code
+ * {
+ * "heartbeatTimeFactor": 100.321,
+ * "heartbeatVolume": 1.0,
+ * "hearts": {
+ * "disabled": false,
+ * "opacity": 1.0,
+ * "scale": 1.0,
+ * "animationSpeed": 0,
+ * "blur": {
+ * "disabled": false,
+ * "opacity": 0.97
+ * }
+ * }
+ * }
+ * }</pre>
+ * And here is some examples of how to fetch values, in a config that uses a JsonMigrator:
+ * <pre>{@code
+ * @JsonName("hearts.blur.disabled") // retrieve the value from the old JSON file
+ * @Switch(name = "Disable Blur", category = "Hearts") // initialize a field like a normal config
+ * public static boolean isHeartsBlurDisabled = false;
+ *
+ * @JsonName("/hearts/blur/opacity") // retrieve the value from the old JSON file using slashes instead (either work!)
+ * @Slider(name = "Hearts Opacity", category = "Hearts", min = 0f, max = 1f) // initialize a field like a normal config
+ * public static float isHeartsBlurDisabled = 1f;
+ *
+ * //@JsonName("heartbeatVolume") // This one does not need to have the annotation, as it is in the master object, and the variable name is the same.
+ * @Slider(name = "Heartbeat Volume", category = "Hearts", min = 0f, max = 1f)
+ * public static float heartbeatVolume = 0.5f; // It's good practice to have it though.
+ * }</pre>
+ */
+public class JsonMigrator implements Migrator {
+
+ protected JsonObject object;
+ protected HashMap<String, Object> values = null;
+
+
+ /**
+ * <h2>{@link JsonMigrator Click ME} for the full javadoc on how to use JsonMigrator!</h2>
+ * Construct a new JsonMigrator for this config file.
+ *
+ * @param filePath the full path to where the previous config file should be located.
+ */
+ public JsonMigrator(String filePath) {
+ File file = new File(filePath);
+ try {
+ object = new JsonParser().parse(new FileReader(file)).getAsJsonObject();
+ } catch (Exception e) {
+ e.printStackTrace();
+ object = null;
+ }
+ }
+
+ /**
+ * @param field The target field of the option
+ * @param name <b>IGNORED!</b> Uses field.getName() if the annotation is not present.
+ * @param category <b>IGNORED!</b>
+ * @param subcategory <b>IGNORED!</b>
+ */
+ @Override
+ public Object getValue(Field field, @Nullable String name, @Nullable String category, @Nullable String subcategory) {
+ if (object == null) return null;
+ if (values == null) generateValues();
+ String key;
+ if (field.isAnnotationPresent(JsonName.class)) {
+ JsonName annotation = field.getAnnotation(JsonName.class);
+ key = annotation.value();
+ } else key = field.getName();
+ return values.get(parse(key));
+ }
+
+ protected String parse(@NotNull String value) {
+ if (value.startsWith("/") || value.startsWith(".")) value = value.substring(1);
+ return value.replaceAll("/", ".");
+ }
+
+ protected void generateValues() {
+ if (object == null) return;
+ values = new HashMap<>();
+ for (Map.Entry<String, JsonElement> master : object.entrySet()) {
+ loopThroughChildren(master.getKey(), master.getValue().getAsJsonObject());
+ }
+ }
+
+ protected void loopThroughChildren(String path, JsonObject in) {
+ for (Map.Entry<String, JsonElement> element : in.entrySet()) {
+ String thisPath = path + "." + element.getKey();
+ if (element.getValue().isJsonObject()) {
+ loopThroughChildren(thisPath, element.getValue().getAsJsonObject());
+ } else put(thisPath, element.getValue());
+ }
+ }
+
+ /**
+ * Take the JsonElement and add it as the correct type to the hashmap.
+ *
+ * @param key "." delimited key
+ * @param val value to be parsed
+ */
+ protected void put(String key, JsonElement val) {
+ if (val.isJsonNull()) values.put(key, null);
+ else if (val.isJsonPrimitive()) values.put(key, cast(val.getAsJsonPrimitive()));
+ else if (val.isJsonArray()) {
+ JsonArray array = val.getAsJsonArray();
+ Iterator<JsonElement> iterator = array.iterator();
+ Object[] objects = new Object[array.size()];
+ int i = 0;
+ while (iterator.hasNext()) {
+ objects[i] = cast(iterator.next().getAsJsonPrimitive());
+ }
+ values.put(key, objects);
+ } else values.put(key, val);
+ }
+
+ /**
+ * Cast the given JsonPrimitive to an appropriate number, boolean, or String.
+ *
+ * @param primitive the json primitive
+ * @return the value in the correct type.
+ */
+ private Object cast(JsonPrimitive primitive) {
+ if (primitive.isJsonNull()) return null;
+ else if (primitive.isBoolean()) return primitive.getAsBoolean();
+ else if (primitive.isNumber()) {
+ Number number = primitive.getAsNumber();
+ if (number.floatValue() % 1f != 0) {
+ return number.floatValue();
+ } else return number.intValue();
+ } else
+ return primitive.getAsString(); // if is not boolean, null or number return as String
+ }
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/JsonName.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/JsonName.java
new file mode 100644
index 0000000..98ed2c9
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/JsonName.java
@@ -0,0 +1,19 @@
+package cc.polyfrost.oneconfig.config.migration;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <pre style="font-size: 12px">{@code public @interface JsonName}</pre>
+ * This interface is used to specify a previous name for an element.<br>
+ * For example, if you changed the name of a variable when you migrated/updated your mod to use OneConfig,
+ * you can use this annotation to specify the previous name so that the Migrator can grab it. <br>
+ * <b>This annotation uses dot notation to specify the full, case sensitive path to the old config field. See {@link JsonMigrator} for more information.</b>
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface JsonName {
+ String value();
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/Migrator.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/Migrator.java
index abfb2a0..e04becc 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/migration/Migrator.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/Migrator.java
@@ -1,14 +1,24 @@
package cc.polyfrost.oneconfig.config.migration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
import java.lang.reflect.Field;
public interface Migrator {
/**
- * @param field The field of the option
- * @param name The name of the option
+ * Get the value from its name, category, and subcategory. The target field is also supplied, which can be used to check for migration names.
+ * The returned Object is intended to be a "Duck" object, and should be cast to the correct type. The Migrator should never return ClassCastExceptions.
+ *
+ * @param field The target field of the option
+ * @param name The name of the option (has to be present)
* @param category The category of the option
* @param subcategory The subcategory of the option
* @return Value of the option, null if not found
+ * @apiNote <b>The nullability of the subcategory or category depends on the implementation. Please check for @NotNull or @Nullable in your implementation.</b>
*/
- Object getValue(Field field, String name, String category, String subcategory);
+ @Nullable
+ Object getValue(Field field, @NotNull String name, String category, String subcategory);
+
+
}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceMigrator.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceMigrator.java
index 1397ef9..8b03252 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceMigrator.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceMigrator.java
@@ -1,5 +1,8 @@
package cc.polyfrost.oneconfig.config.migration;
+import cc.polyfrost.oneconfig.config.core.OneColor;
+import org.jetbrains.annotations.NotNull;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
@@ -13,6 +16,7 @@ public class VigilanceMigrator implements Migrator {
private static final Pattern booleanPattern = Pattern.compile("\"?(?<name>[^\\s\"]+)\"? = (?<value>true|false)");
private static final Pattern numberPattern = Pattern.compile("\"?(?<name>[^\\s\"]+)\"? = (?<value>[\\d.]+)");
private static final Pattern stringPattern = Pattern.compile("\"?(?<name>[^\\s\"]+)\"? = \"(?<value>.+)\"");
+ private static final Pattern colorPattern = Pattern.compile("\"?(?<name>[^\\s\"]+)\"? = \"(?<value>(\\d{1,3},){3}\\d{1,3})\"");
protected final String filePath;
protected HashMap<String, HashMap<String, HashMap<String, Object>>> values = null;
protected final boolean fileExists;
@@ -23,9 +27,9 @@ public class VigilanceMigrator implements Migrator {
}
@Override
- public Object getValue(Field field, String name, String category, String subcategory) {
+ public Object getValue(Field field, @NotNull String name, @NotNull String category, @NotNull String subcategory) {
if (!fileExists) return null;
- if (values == null) getOptions();
+ if (values == null) generateValues();
if (field.isAnnotationPresent(VigilanceName.class)) {
VigilanceName annotation = field.getAnnotation(VigilanceName.class);
name = annotation.name();
@@ -35,16 +39,14 @@ public class VigilanceMigrator implements Migrator {
name = parse(name);
category = parse(category);
subcategory = parse(subcategory);
- if (values.containsKey(category) && values.get(category).containsKey(subcategory) && values.get(category).get(subcategory).containsKey(name))
- return values.get(category).get(subcategory).get(name);
- return null;
+ return values.getOrDefault(category, new HashMap<>()).getOrDefault(subcategory, new HashMap<>()).getOrDefault(name, null);
}
- protected String parse(String value) {
+ protected @NotNull String parse(@NotNull String value) {
return value.toLowerCase().replace(" ", "_");
}
- protected void getOptions() {
+ protected void generateValues() {
if (values == null) values = new HashMap<>();
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String currentCategory = null;
@@ -74,6 +76,15 @@ public class VigilanceMigrator implements Migrator {
else options.put(numberMatcher.group("name"), Integer.parseInt(value));
continue;
}
+ Matcher colorMatcher = colorPattern.matcher(line);
+ if (colorMatcher.find()) {
+ String[] strings = colorMatcher.group("value").split(",");
+ int[] values = new int[4];
+ for (int i = 0; i < 4; i++) {
+ values[i] = Integer.parseInt(strings[i]);
+ }
+ options.put(colorMatcher.group("name"), new OneColor(values[0], values[1], values[2], values[3]));
+ }
Matcher stringMatcher = stringPattern.matcher(line);
if (stringMatcher.find()) {
options.put(stringMatcher.group("name"), stringMatcher.group("value"));
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceName.java b/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceName.java
index d9db17e..5e14f9c 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceName.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/migration/VigilanceName.java
@@ -5,6 +5,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+/**
+ * <pre style="font-size: 12px">{@code public @interface VigilanceName}</pre>
+ * This interface is used to specify a previous name for an element.<br>
+ * For example, if you changed the name of a variable when you migrated/updated your mod to use OneConfig,
+ * you can use this annotation to specify the previous name so that the Migrator can grab it.
+ */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface VigilanceName {