1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
package dev.isxander.yacl3.config;
import com.google.gson.*;
import dev.isxander.yacl3.impl.utils.YACLConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import java.awt.*;
import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.function.UnaryOperator;
/**
* Uses GSON to serialize and deserialize config data from JSON to a file.
*
* Only fields annotated with {@link ConfigEntry} are included in the JSON.
* {@link Component}, {@link Style} and {@link Color} have default type adapters, so there is no need to provide them in your GSON instance.
* GSON is automatically configured to format fields as {@code lower_camel_case}.
*
* @param <T> config data type
*/
public class GsonConfigInstance<T> extends ConfigInstance<T> {
private final Gson gson;
private final Path path;
@Deprecated
public GsonConfigInstance(Class<T> configClass, Path path) {
this(configClass, path, new GsonBuilder());
}
@Deprecated
public GsonConfigInstance(Class<T> configClass, Path path, Gson gson) {
this(configClass, path, gson.newBuilder());
}
@Deprecated
public GsonConfigInstance(Class<T> configClass, Path path, UnaryOperator<GsonBuilder> builder) {
this(configClass, path, builder.apply(new GsonBuilder()));
}
@Deprecated
public GsonConfigInstance(Class<T> configClass, Path path, GsonBuilder builder) {
super(configClass);
this.path = path;
this.gson = builder
.setExclusionStrategies(new ConfigExclusionStrategy())
.registerTypeHierarchyAdapter(Component.class, new Component.Serializer())
.registerTypeHierarchyAdapter(Style.class, new Style.Serializer())
.registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter())
.serializeNulls()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
}
private GsonConfigInstance(Class<T> configClass, Path path, Gson gson, boolean fromBuilder) {
super(configClass);
this.path = path;
this.gson = gson;
}
@Override
public void save() {
try {
YACLConstants.LOGGER.info("Saving {}...", getConfigClass().getSimpleName());
Files.writeString(path, gson.toJson(getConfig()), StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void load() {
try {
if (Files.notExists(path)) {
save();
return;
}
YACLConstants.LOGGER.info("Loading {}...", getConfigClass().getSimpleName());
setConfig(gson.fromJson(Files.readString(path), getConfigClass()));
} catch (IOException e) {
e.printStackTrace();
}
}
public Path getPath() {
return this.path;
}
private static class ConfigExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(ConfigEntry.class) == null;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
public static class ColorTypeAdapter implements JsonSerializer<Color>, JsonDeserializer<Color> {
@Override
public Color deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return new Color(jsonElement.getAsInt(), true);
}
@Override
public JsonElement serialize(Color color, Type type, JsonSerializationContext jsonSerializationContext) {
return new JsonPrimitive(color.getRGB());
}
}
/**
* Creates a builder for a GSON config instance.
* @param configClass the config class
* @return a new builder
* @param <T> the config type
*/
public static <T> Builder<T> createBuilder(Class<T> configClass) {
return new Builder<>(configClass);
}
public static class Builder<T> {
private final Class<T> configClass;
private Path path;
private UnaryOperator<GsonBuilder> gsonBuilder = builder -> builder
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.serializeNulls()
.registerTypeHierarchyAdapter(Component.class, new Component.Serializer())
.registerTypeHierarchyAdapter(Style.class, new Style.Serializer())
.registerTypeHierarchyAdapter(Color.class, new ColorTypeAdapter());
private Builder(Class<T> configClass) {
this.configClass = configClass;
}
/**
* Sets the file path to save and load the config from.
*/
public Builder<T> setPath(Path path) {
this.path = path;
return this;
}
/**
* Sets the GSON instance to use. Overrides all YACL defaults such as:
* <ul>
* <li>lower_camel_case field naming policy</li>
* <li>null serialization</li>
* <li>{@link Component}, {@link Style} and {@link Color} type adapters</li>
* </ul>
* Still respects the exclusion strategy to only serialize {@link ConfigEntry}
* but these can be added to with setExclusionStrategies.
*
* @param gsonBuilder gson builder to use
*/
public Builder<T> overrideGsonBuilder(GsonBuilder gsonBuilder) {
this.gsonBuilder = builder -> gsonBuilder;
return this;
}
/**
* Sets the GSON instance to use. Overrides all YACL defaults such as:
* <ul>
* <li>lower_camel_case field naming policy</li>
* <li>null serialization</li>
* <li>{@link Component}, {@link Style} and {@link Color} type adapters</li>
* </ul>
* Still respects the exclusion strategy to only serialize {@link ConfigEntry}
* but these can be added to with setExclusionStrategies.
*
* @param gson gson instance to be converted to a builder
*/
public Builder<T> overrideGsonBuilder(Gson gson) {
return this.overrideGsonBuilder(gson.newBuilder());
}
/**
* Appends extra configuration to a GSON builder.
* This is the intended way to add functionality to the GSON instance.
* <p>
* By default, YACL sets the GSON with the following options:
* <ul>
* <li>lower_camel_case field naming policy</li>
* <li>null serialization</li>
* <li>{@link Component}, {@link Style} and {@link Color} type adapters</li>
* </ul>
*
* @param gsonBuilder the function to apply to the builder
*/
public Builder<T> appendGsonBuilder(UnaryOperator<GsonBuilder> gsonBuilder) {
UnaryOperator<GsonBuilder> prev = this.gsonBuilder;
this.gsonBuilder = builder -> gsonBuilder.apply(prev.apply(builder));
return this;
}
/**
* Builds the config instance.
* @return the built config instance
*/
public GsonConfigInstance<T> build() {
UnaryOperator<GsonBuilder> gsonBuilder = builder -> this.gsonBuilder.apply(builder)
.addSerializationExclusionStrategy(new ConfigExclusionStrategy())
.addDeserializationExclusionStrategy(new ConfigExclusionStrategy());
return new GsonConfigInstance<>(configClass, path, gsonBuilder.apply(new GsonBuilder()).create(), true);
}
}
}
|