6
6
import com .sk89q .worldedit .internal .util .LogManagerCompat ;
7
7
import org .apache .logging .log4j .Logger ;
8
8
9
+ import javax .annotation .Nullable ;
9
10
import java .io .ByteArrayOutputStream ;
10
11
import java .io .File ;
11
12
import java .io .PrintWriter ;
18
19
import java .lang .reflect .InvocationTargetException ;
19
20
import java .lang .reflect .Modifier ;
20
21
import java .lang .reflect .ParameterizedType ;
22
+ import java .util .AbstractMap ;
21
23
import java .util .ArrayList ;
22
24
import java .util .Arrays ;
23
25
import java .util .Collection ;
@@ -31,10 +33,15 @@ public class Config {
31
33
private static final Logger LOGGER = LogManagerCompat .getLogger ();
32
34
33
35
private final Map <String , Object > removedKeyVals = new HashMap <>();
36
+ @ Nullable
37
+ private Map <String , Map .Entry <String , Object >> copyTo = new HashMap <>();
38
+ private boolean performCopyTo = false ;
34
39
private List <String > existingMigrateNodes = null ;
35
40
36
41
public Config () {
37
- save (new PrintWriter (new ByteArrayOutputStream (0 )), getClass (), this , 0 );
42
+ // This is now definitely required as the save -> load -> save order means the @CopiedFrom annotated fields work
43
+ save (new PrintWriter (new ByteArrayOutputStream (0 )), getClass (), this , 0 , null );
44
+ performCopyTo = true ;
38
45
}
39
46
40
47
/**
@@ -60,11 +67,12 @@ private <T> T get(String key, Class<?> root) {
60
67
61
68
/**
62
69
* Set the value of a specific node. Probably throws some error if you supply non existing keys or invalid values.
70
+ * This should only be called during loading of a config file
63
71
*
64
72
* @param key config node
65
73
* @param value value
66
74
*/
67
- private void set (String key , Object value , Class <?> root ) {
75
+ private void setLoadedNode (String key , Object value , Class <?> root ) {
68
76
String [] split = key .split ("\\ ." );
69
77
Object instance = getInstance (split , root );
70
78
if (instance != null ) {
@@ -74,8 +82,18 @@ private void set(String key, Object value, Class<?> root) {
74
82
if (field .getAnnotation (Final .class ) != null ) {
75
83
return ;
76
84
}
85
+ if (copyTo != null ) {
86
+ copyTo .remove (key ); // Remove if the config field is already written
87
+ final Object finalValue = value ;
88
+ copyTo .replaceAll ((copyToNode , entry ) -> {
89
+ if (!key .equals (entry .getKey ())) {
90
+ return entry ;
91
+ }
92
+ return new AbstractMap .SimpleEntry <>(key , finalValue );
93
+ });
94
+ }
77
95
Migrate migrate = field .getAnnotation (Migrate .class );
78
- if (existingMigrateNodes != null && migrate != null ) {
96
+ if (migrate != null ) {
79
97
existingMigrateNodes .add (migrate .value ());
80
98
}
81
99
if (field .getType () == String .class && !(value instanceof String )) {
@@ -90,8 +108,9 @@ private void set(String key, Object value, Class<?> root) {
90
108
}
91
109
}
92
110
removedKeyVals .put (key , value );
93
- LOGGER .error (
94
- "Failed to set config option: {}: {} | {} | {}.yml. This is likely because it was removed." ,
111
+ LOGGER .warn (
112
+ "Failed to set config option: {}: {} | {} | {}.yml. This is likely because it was removed or was set with an " +
113
+ "invalid value." ,
95
114
key ,
96
115
value ,
97
116
instance ,
@@ -110,7 +129,7 @@ public boolean load(File file) {
110
129
if (value instanceof MemorySection ) {
111
130
continue ;
112
131
}
113
- set (key , value , getClass ());
132
+ setLoadedNode (key , value , getClass ());
114
133
}
115
134
for (String node : existingMigrateNodes ) {
116
135
removedKeyVals .remove (node );
@@ -133,7 +152,7 @@ public void save(File file) {
133
152
}
134
153
PrintWriter writer = new PrintWriter (file );
135
154
Object instance = this ;
136
- save (writer , getClass (), instance , 0 );
155
+ save (writer , getClass (), instance , 0 , null );
137
156
writer .close ();
138
157
} catch (Throwable e ) {
139
158
LOGGER .error ("Failed to save config file: {}" , file , e );
@@ -190,7 +209,7 @@ public void save(File file) {
190
209
}
191
210
192
211
/**
193
- * Indicates that a field should be instantiated / created.
212
+ * Indicates that a field should be migrated from a node that is deleted
194
213
*
195
214
* @since 2.10.0
196
215
*/
@@ -202,6 +221,19 @@ public void save(File file) {
202
221
203
222
}
204
223
224
+ /**
225
+ * Indicates that a field's default value should match another input if the config is otherwise already generated
226
+ *
227
+ * @since TODO
228
+ */
229
+ @ Retention (RetentionPolicy .RUNTIME )
230
+ @ Target ({ElementType .FIELD })
231
+ public @interface CopiedFrom {
232
+
233
+ String value ();
234
+
235
+ }
236
+
205
237
@ Ignore // This is not part of the config
206
238
public static class ConfigBlock <T > {
207
239
@@ -254,7 +286,7 @@ private String toYamlString(Object value, String spacing) {
254
286
return value != null ? value .toString () : "null" ;
255
287
}
256
288
257
- private void save (PrintWriter writer , Class <?> clazz , final Object instance , int indent ) {
289
+ private void save (PrintWriter writer , Class <?> clazz , final Object instance , int indent , String parentNode ) {
258
290
try {
259
291
String CTRF = System .lineSeparator ();
260
292
String spacing = StringMan .repeat (" " , indent );
@@ -274,7 +306,7 @@ private void save(PrintWriter writer, Class<?> clazz, final Object instance, int
274
306
}
275
307
if (current == ConfigBlock .class ) {
276
308
current = (Class <?>) ((ParameterizedType ) (field .getGenericType ())).getActualTypeArguments ()[0 ];
277
- handleConfigBlockSave (writer , instance , indent , field , spacing , CTRF , current );
309
+ handleConfigBlockSave (writer , instance , indent , field , spacing , CTRF , current , parentNode );
278
310
continue ;
279
311
} else if (!removedKeyVals .isEmpty ()) {
280
312
Migrate migrate = field .getAnnotation (Migrate .class );
@@ -283,6 +315,17 @@ private void save(PrintWriter writer, Class<?> clazz, final Object instance, int
283
315
field .set (instance , value );
284
316
}
285
317
}
318
+ CopiedFrom copiedFrom ;
319
+ if (copyTo != null && (copiedFrom = field .getAnnotation (CopiedFrom .class )) != null ) {
320
+ String node = toNodeName (field .getName ());
321
+ node = parentNode == null ? node : parentNode + "." + node ;
322
+ Map .Entry <String , Object > entry = copyTo .remove (node );
323
+ if (entry == null ) {
324
+ copyTo .put (node ,new AbstractMap .SimpleEntry <>(copiedFrom .value (), null ));
325
+ } else {
326
+ field .set (instance , entry .getValue ());
327
+ }
328
+ }
286
329
Create create = field .getAnnotation (Create .class );
287
330
if (create != null ) {
288
331
Object value = field .get (instance );
@@ -296,11 +339,12 @@ private void save(PrintWriter writer, Class<?> clazz, final Object instance, int
296
339
writer .write (spacing + "# " + commentLine + CTRF );
297
340
}
298
341
}
299
- writer .write (spacing + toNodeName (current .getSimpleName ()) + ":" + CTRF );
342
+ String node = toNodeName (current .getSimpleName ());
343
+ writer .write (spacing + node + ":" + CTRF );
300
344
if (value == null ) {
301
345
field .set (instance , value = current .getDeclaredConstructor ().newInstance ());
302
346
}
303
- save (writer , current , value , indent + 2 );
347
+ save (writer , current , value , indent + 2 , parentNode == null ? node : parentNode + "." + node );
304
348
} else {
305
349
writer .write (spacing + toNodeName (field .getName () + ": " ) + toYamlString (
306
350
field .get (instance ),
@@ -311,6 +355,10 @@ private void save(PrintWriter writer, Class<?> clazz, final Object instance, int
311
355
} catch (Throwable e ) {
312
356
LOGGER .error ("Failed to save config file" , e );
313
357
}
358
+ if (parentNode == null && performCopyTo ) {
359
+ performCopyTo = false ;
360
+ copyTo = null ;
361
+ }
314
362
}
315
363
316
364
private <T > void handleConfigBlockSave (
@@ -320,7 +368,8 @@ private <T> void handleConfigBlockSave(
320
368
Field field ,
321
369
String spacing ,
322
370
String CTRF ,
323
- Class <T > current
371
+ Class <T > current ,
372
+ String parentNode
324
373
) throws IllegalAccessException , InstantiationException , InvocationTargetException , NoSuchMethodException {
325
374
Comment comment = current .getAnnotation (Comment .class );
326
375
if (comment != null ) {
@@ -330,6 +379,7 @@ private <T> void handleConfigBlockSave(
330
379
}
331
380
BlockName blockNames = current .getAnnotation (BlockName .class );
332
381
if (blockNames != null ) {
382
+ String node = toNodeName (current .getSimpleName ());
333
383
writer .write (spacing + toNodeName (current .getSimpleName ()) + ":" + CTRF );
334
384
ConfigBlock <T > configBlock = (ConfigBlock <T >) field .get (instance );
335
385
if (configBlock == null || configBlock .getInstances ().isEmpty ()) {
@@ -343,7 +393,7 @@ private <T> void handleConfigBlockSave(
343
393
for (Map .Entry <String , T > entry : configBlock .getRaw ().entrySet ()) {
344
394
String key = entry .getKey ();
345
395
writer .write (spacing + " " + toNodeName (key ) + ":" + CTRF );
346
- save (writer , current , entry .getValue (), indent + 4 );
396
+ save (writer , current , entry .getValue (), indent + 4 , parentNode == null ? node : parentNode + "." + node );
347
397
}
348
398
}
349
399
}
0 commit comments