diff --git a/src/main/java/emu/grasscutter/utils/JsonAdapters.java b/src/main/java/emu/grasscutter/utils/JsonAdapters.java new file mode 100644 index 000000000..c9c1a330a --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/JsonAdapters.java @@ -0,0 +1,121 @@ +package emu.grasscutter.utils; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import emu.grasscutter.data.common.DynamicFloat; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import lombok.val; + +public class JsonAdapters { + static class DynamicFloatAdapter extends TypeAdapter { + @Override + public DynamicFloat read(JsonReader reader) throws IOException { + switch (reader.peek()) { + case STRING: + return new DynamicFloat(reader.nextString()); + case NUMBER: + return new DynamicFloat((float) reader.nextDouble()); + case BEGIN_ARRAY: + reader.beginArray(); + val opStack = new ArrayList(); + while (reader.hasNext()) { + opStack.add(switch (reader.peek()) { + case STRING -> new DynamicFloat.StackOp(reader.nextString()); + case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble()); + default -> throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name()); + }); + } + reader.endArray(); + return new DynamicFloat(opStack); + default: + throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name()); + } + } + + @Override + public void write(JsonWriter writer, DynamicFloat f) {}; + } + + static class IntListAdapter extends TypeAdapter { + @Override + public IntList read(JsonReader reader) throws IOException { + switch (reader.peek()) { + case BEGIN_ARRAY: + reader.beginArray(); + val i = new IntArrayList(); + while (reader.hasNext()) + i.add(reader.nextInt()); + reader.endArray(); + i.trim(); // We might have a ton of these from resources and almost all of them immutable, don't overprovision! + return i; + default: + throw new IOException("Invalid IntList definition - " + reader.peek().name()); + } + } + + @Override + public void write(JsonWriter writer, IntList i) {}; + } + + static class EnumTypeAdapterFactory implements TypeAdapterFactory { + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken type) { + Class rawType = (Class) type.getRawType(); + if (!rawType.isEnum()) return null; + + Field id = null; + // System.out.println("Looking for enum value field"); + for (Field f : rawType.getDeclaredFields()) { + id = switch (f.getName()) { + case "value", "id" -> f; + default -> null; + }; + if (id != null) break; + } + if (id == null) { + // System.out.println("Not found"); + return null; + } + // System.out.println("Enum value field found - " + id.getName()); + + val map = new HashMap(); + boolean acc = id.isAccessible(); + id.setAccessible(true); + try { + for (T constant : rawType.getEnumConstants()) { + map.put(constant.toString(), constant); + map.put(String.valueOf(id.getInt(constant)), constant); + } + } catch (IllegalAccessException e) { + // System.out.println("Failed to access enum id field."); + return null; + } + id.setAccessible(acc); + + return new TypeAdapter() { + public T read(JsonReader reader) throws IOException { + switch (reader.peek()) { + case STRING: + return map.get(reader.nextString()); + case NUMBER: + return map.get(String.valueOf(reader.nextInt())); + default: + throw new IOException("Invalid Enum definition - " + reader.peek().name()); + } + } + public void write(JsonWriter writer, T value) {} + }; + } + } +} diff --git a/src/main/java/emu/grasscutter/utils/JsonUtils.java b/src/main/java/emu/grasscutter/utils/JsonUtils.java index fec756e11..c8f95fded 100644 --- a/src/main/java/emu/grasscutter/utils/JsonUtils.java +++ b/src/main/java/emu/grasscutter/utils/JsonUtils.java @@ -7,7 +7,6 @@ import java.io.Reader; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -15,48 +14,18 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonSyntaxException; -import com.google.gson.TypeAdapter; import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import emu.grasscutter.data.common.DynamicFloat; -import lombok.val; +import emu.grasscutter.utils.JsonAdapters.*; +import it.unimi.dsi.fastutil.ints.IntList; public final class JsonUtils { - // Adapters - static class DynamicFloatAdapter extends TypeAdapter { - @Override - public DynamicFloat read(JsonReader reader) throws IOException { - switch (reader.peek()) { - case STRING: - return new DynamicFloat(reader.nextString()); - case NUMBER: - return new DynamicFloat((float) reader.nextDouble()); - case BEGIN_ARRAY: - reader.beginArray(); - val opStack = new ArrayList(); - while (reader.hasNext()) { - opStack.add(switch (reader.peek()) { - case STRING -> new DynamicFloat.StackOp(reader.nextString()); - case NUMBER -> new DynamicFloat.StackOp((float) reader.nextDouble()); - default -> throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name()); - }); - } - reader.endArray(); - return new DynamicFloat(opStack); - default: - throw new IOException("Invalid DynamicFloat definition - " + reader.peek().name()); - } - } - - @Override - public void write(JsonWriter writer, DynamicFloat f) {}; - } - static final Gson gson = new GsonBuilder() .setPrettyPrinting() .registerTypeAdapter(DynamicFloat.class, new DynamicFloatAdapter()) + .registerTypeAdapter(IntList.class, new IntListAdapter()) + .registerTypeAdapterFactory(new EnumTypeAdapterFactory()) .create(); /*