JsonAdapters.java 6.99 KB
Newer Older
AnimeGitB's avatar
AnimeGitB committed
1
2
3
4
5
6
7
8
9
10
11
12
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;
13
import com.google.gson.stream.JsonToken;
AnimeGitB's avatar
AnimeGitB committed
14
15
16
import com.google.gson.stream.JsonWriter;

import emu.grasscutter.data.common.DynamicFloat;
17
import it.unimi.dsi.fastutil.floats.FloatArrayList;
AnimeGitB's avatar
AnimeGitB committed
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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<DynamicFloat> {
        @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<DynamicFloat.StackOp>();
                    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<IntList> {
        @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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
        public void write(JsonWriter writer, IntList l) throws IOException {
            writer.beginArray();
            for (val i : l)  // .forEach() doesn't appreciate exceptions
                writer.value(i);
            writer.endArray();
        };
    }

    static class PositionAdapter extends TypeAdapter<Position> {
        @Override
        public Position read(JsonReader reader) throws IOException {
            switch (reader.peek()) {
                case BEGIN_ARRAY:  // "pos": [x,y,z]
                    reader.beginArray();
                    val array = new FloatArrayList(3);
                    while (reader.hasNext())
                        array.add(reader.nextInt());
                    reader.endArray();
                    return new Position(array);
                case BEGIN_OBJECT:  // "pos": {"x": x, "y": y, "z": z}
                    float x = 0f;
                    float y = 0f;
                    float z = 0f;
                    reader.beginObject();
                    for (var next = reader.peek(); next != JsonToken.END_OBJECT; next = reader.peek()) {
                        val name = reader.nextName();
                        switch (name) {
                            case "x", "X", "_x" -> x = (float) reader.nextDouble();
                            case "y", "Y", "_y" -> y = (float) reader.nextDouble();
                            case "z", "Z", "_z" -> z = (float) reader.nextDouble();
                            default -> throw new IOException("Invalid field in Position definition - " + name);
                        }
                    }
                    reader.endObject();
                    return new Position(x, y, z);
                default:
                    throw new IOException("Invalid Position definition - " + reader.peek().name());
            }
        }

        @Override
        public void write(JsonWriter writer, Position i) throws IOException {
            writer.beginArray();
            writer.value(i.getX());
            writer.value(i.getY());
            writer.value(i.getZ());
            writer.endArray();
        };
AnimeGitB's avatar
AnimeGitB committed
118
119
120
121
122
    }

    static class EnumTypeAdapterFactory implements TypeAdapterFactory {
        @SuppressWarnings("unchecked")
        public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
123
124
            Class<T> enumClass = (Class<T>) type.getRawType();
            if (!enumClass.isEnum()) return null;
github-actions's avatar
github-actions committed
125

126
127
128
129
130
            // Make mappings of (string) names to enum constants
            val map = new HashMap<String, T>();
            val enumConstants = enumClass.getEnumConstants();
            for (val constant : enumConstants)
                map.put(constant.toString(), constant);
AnimeGitB's avatar
AnimeGitB committed
131

132
            // If the enum also has a numeric value, map those to the constants too
AnimeGitB's avatar
AnimeGitB committed
133
            // System.out.println("Looking for enum value field");
134
135
136
137
138
139
140
141
142
143
144
145
146
            for (Field f : enumClass.getDeclaredFields()) {
                if (switch (f.getName()) {case "value", "id" -> true; default -> false;}) {
                    // System.out.println("Enum value field found - " + f.getName());
                    boolean acc = f.isAccessible();
                    f.setAccessible(true);
                    try {
                        for (val constant : enumConstants)
                            map.put(String.valueOf(f.getInt(constant)), constant);
                    } catch (IllegalAccessException e) {
                        // System.out.println("Failed to access enum id field.");
                    }
                    f.setAccessible(acc);
                    break;
AnimeGitB's avatar
AnimeGitB committed
147
148
149
150
151
152
153
154
155
156
157
158
159
160
                }
            }

            return new TypeAdapter<T>() {
                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());
                    }
                }
161
162
163
                public void write(JsonWriter writer, T value) throws IOException {
                    writer.value(value.toString());
                }
AnimeGitB's avatar
AnimeGitB committed
164
165
166
167
            };
        }
    }
}