@@ -49,16 +49,16 @@ public abstract class StaticConfig {
4949 private final ConfigDocument document ;
5050
5151 /**
52- * Maps all config routes to their associated fields.
52+ * Maps all config field routes to their associated fields.
5353 */
5454 @ Getter (AccessLevel .NONE )
5555 private final Map <String , Field > fields = new HashMap <>();
5656
5757 /**
58- * A list of all routes mapped to if they can't be removed .
58+ * Maps all config class routes to a set of their members .
5959 */
6060 @ Getter (AccessLevel .NONE )
61- private final Map <String , Boolean > routes = new HashMap <>();
61+ private final Map <String , Set < AnnotatedElement >> sections = new HashMap <>();
6262
6363 /**
6464 * A map of relocation replacements to their target paths.
@@ -102,6 +102,7 @@ public StaticConfig(File file, InputStream defaults, ConfigHandler handler) {
102102 * @param file The file to be used for the configuration document.
103103 * @param handler The config handler to use for config settings.
104104 */
105+ @ SuppressWarnings ("unused" )
105106 public StaticConfig (File file , ConfigHandler handler ) {
106107 this (file , null , handler );
107108 }
@@ -139,7 +140,7 @@ public void load() {
139140
140141 // If fields are empty, perform the initialization.
141142 if (fields .isEmpty ()) {
142- step (getClass (), "" , true );
143+ initialize (getClass (), "" );
143144 }
144145
145146 // Load and set each field value from the document.
@@ -207,7 +208,7 @@ public void save() {
207208 document .wipeComments (document );
208209
209210 // Recurse through the static fields, setting the values in the document.
210- step (getClass (), "" , false );
211+ step ("" );
211212
212213 // Set the additional custom comments specified by the user.
213214 setComments ();
@@ -219,15 +220,8 @@ public void save() {
219220 }
220221 }
221222
222- /**
223- * Recursively steps through each configuration section.
224- *
225- * @param parent The parent class of the section.
226- * @param path The current path.
227- * @param initialize If this is the initialization phase.
228- */
229- private void step (Class <?> parent , String path , boolean initialize ) throws ReflectiveOperationException {
230- Map <Integer , List <Pair <String , AnnotatedElement >>> members = new TreeMap <>();
223+ private void initialize (Class <?> parent , String path ) {
224+ Map <Integer , Set <AnnotatedElement >> priorities = new TreeMap <>();
231225
232226 for (Field field : parent .getDeclaredFields ()) {
233227 // Skip field if its modifiers are invalid, or it is marked @Ignore.
@@ -242,17 +236,12 @@ private void step(Class<?> parent, String path, boolean initialize) throws Refle
242236 field .setAccessible (true );
243237
244238 // Get the route by combining the current path and the formatted key.
245- String route = path + getRoute (field . getAnnotation ( Key . class ), field . getName () );
239+ String route = getRoute (field , path );
246240
247- // Add the fields and routes if we are in the initialization phase.
248- if (initialize ) {
249- routes .put (route , true );
250- fields .put (route , field );
251- continue ;
252- }
241+ // Add the field to the field map.
242+ fields .put (route , field );
253243
254- // Add the field to the member priority map.
255- members .computeIfAbsent (getPriority (field ), key -> new ArrayList <>()).add (new Pair <>(route , field ));
244+ priorities .computeIfAbsent (getPriority (field ), key -> new LinkedHashSet <>()).add (field );
256245 }
257246
258247 Class <?>[] classes = parent .getDeclaredClasses ();
@@ -273,64 +262,65 @@ private void step(Class<?> parent, String path, boolean initialize) throws Refle
273262 }
274263
275264 // Get the route by combining the current path and the formatted key.
276- String route = path + getRoute (clazz . getAnnotation ( Key . class ), clazz . getSimpleName () );
265+ String route = getRoute (clazz , path );
277266
278- // Add the routes and recurse if we are in the initialization phase.
279- if (initialize ) {
280- routes .put (route , false );
281- step (clazz , route + "." , true );
282- continue ;
283- }
267+ // Initialize the child class.
268+ initialize (clazz , route );
284269
285- // Add the class to the member priority map.
286- members .computeIfAbsent (getPriority (clazz ), key -> new ArrayList <>()).add (new Pair <>(route , clazz ));
270+ priorities .computeIfAbsent (getPriority (clazz ), key -> new LinkedHashSet <>()).add (clazz );
287271 }
288272
289- for (List <Pair <String , AnnotatedElement >> priority : members .values ()) {
290- for (Pair <String , AnnotatedElement > member : priority ) {
291- String route = member .getLeft ();
292-
293- if (member .getRight () instanceof Field ) {
294- Field field = (Field ) member .getRight ();
273+ for (Set <AnnotatedElement > members : priorities .values ()) {
274+ this .sections .computeIfAbsent (path , key -> new LinkedHashSet <>()).addAll (members );
275+ }
276+ }
295277
296- boolean hidden = field .isAnnotationPresent (Hidden .class );
297- boolean present = document .contains (route );
278+ /**
279+ * Recursively steps through each configuration section.
280+ *
281+ * @param path The current path.
282+ */
283+ private void step (String path ) throws ReflectiveOperationException {
284+ for (AnnotatedElement member : sections .get (path )) {
285+ String route = getRoute (member , path );
298286
299- // If this is the initial saving of the value in the document,
300- // or the field is final, so we have to override the value anyway.
301- boolean initial = (!present || Modifier .isFinal (field .getModifiers ())) && !hidden ;
287+ if (member instanceof Field ) {
288+ Field field = (Field ) member ;
302289
303- // Set the field value in the configuration document.
304- if (initial || (!hidden || present )) {
305- set (route , field , field .get (null ));
306- }
290+ boolean hidden = field .isAnnotationPresent (Hidden .class );
291+ boolean present = document .contains (route );
307292
308- // Set the associated comment for the route's block if present.
309- document .setComment (document .getBlock (route ), field .getAnnotation (Comment .class ));
293+ // If this is the initial saving of the value in the document,
294+ // or the field is final, so we have to override the value anyway.
295+ boolean initial = (!present || Modifier .isFinal (field .getModifiers ())) && !hidden ;
310296
311- continue ;
297+ // Set the field value in the configuration document.
298+ if (initial || (!hidden || present )) {
299+ set (route , field , field .get (null ));
312300 }
313301
314- if (member .getRight () instanceof Class ) {
315- Class <?> clazz = (Class <?>) member .getRight ();
302+ // Set the associated comment for the route's block if present.
303+ document .setComment (document .getBlock (route ), field .getAnnotation (Comment .class ));
304+ continue ;
305+ }
316306
317- // If the class is final, remove it so it is regenerated with default values.
318- if (Modifier .isFinal (clazz .getModifiers ())) {
319- document .remove (route );
320- }
307+ if (member instanceof Class ) {
308+ // If the class is final, remove it so it is regenerated with default values.
309+ if (Modifier .isFinal (((Class <?>) member ).getModifiers ())) {
310+ document .remove (route );
311+ }
321312
322- Section section = document .getSection (route );
313+ Section section = document .getSection (route );
323314
324- // If the section doesn't exist and is not hidden, create it.
325- if (section == null && !clazz .isAnnotationPresent (Hidden .class )) {
326- section = document .createSection (route );
327- }
315+ // If the section doesn't exist and is not hidden, create it.
316+ if (section == null && !member .isAnnotationPresent (Hidden .class )) {
317+ section = document .createSection (route );
318+ }
328319
329- // If the section exists, and we shouldn't skip, set the comment and recurse.
330- if (section != null ) {
331- document .setComment (section , clazz .getAnnotation (Comment .class ));
332- step (clazz , route + "." , false );
333- }
320+ // If the section exists, and we shouldn't skip, set the comment and recurse.
321+ if (section != null ) {
322+ document .setComment (section , member .getAnnotation (Comment .class ));
323+ step (route );
334324 }
335325 }
336326 }
@@ -347,11 +337,11 @@ public void format() throws IOException {
347337
348338 // If there is no field for the route,
349339 // but the route is present in the file.
350- if (!fields .containsKey (route ) && !routes .containsKey (route )) {
340+ if (!fields .containsKey (route ) && !sections .containsKey (route )) {
351341
352342 // If the route starts with any manual paths,
353343 // prevent it from being flagged for removal.
354- if (routes . entrySet ().stream ().anyMatch (entry -> entry . getValue () && route . startsWith ( entry . getKey ()) )) {
344+ if (fields . keySet ().stream ().anyMatch (route :: startsWith )) {
355345 continue ;
356346 }
357347
@@ -402,6 +392,7 @@ public void format() throws IOException {
402392 * @param target The old location.
403393 * @param replacement The new location.
404394 */
395+ @ SuppressWarnings ("unused" )
405396 protected void relocate (String target , String replacement ) {
406397 relocations .computeIfAbsent (replacement , key -> new ArrayList <>()).add (target );
407398 }
@@ -437,6 +428,7 @@ private void relocate() {
437428 * @param side If the comment is on the side.
438429 * @param comment The comment.
439430 */
431+ @ SuppressWarnings ("SameParameterValue" )
440432 protected void setComment (String route , boolean side , String ...comment ) {
441433 comments .put (route , new Pair <>(Arrays .asList (comment ), side ));
442434 }
@@ -447,6 +439,7 @@ protected void setComment(String route, boolean side, String ...comment) {
447439 * @param route The route.
448440 * @param comment The comment.
449441 */
442+ @ SuppressWarnings ("unused" )
450443 protected void setComment (String route , String ...comment ) {
451444 setComment (route , false , comment );
452445 }
@@ -481,16 +474,20 @@ private <T> void set(String key, Field field, T value) {
481474 }
482475
483476 /**
484- * If the key annotation is present, uses that value.
485- * Otherwise, uses the member name with the key formatter.
477+ * Gets the route of a field/class and appends it to the path.
486478 *
487- * @param key The key annotation.
488- * @param name The name of the member .
479+ * @param member The member key annotation.
480+ * @param path The current path .
489481 * @return The formatted key.
490482 */
491- private String getRoute (Key key , String name ) {
483+ private String getRoute (AnnotatedElement member , String path ) {
484+ Key key = member .getAnnotation (Key .class );
485+ String name = ClassUtils .getName (member );
486+
492487 boolean valid = key != null && !key .value ().trim ().isEmpty ();
493- return valid ? key .value () : handler .getKeyFormatter ().apply (name );
488+ String route = valid ? key .value () : handler .getKeyFormatter ().apply (name );
489+
490+ return path + (path .isEmpty () ? "" : "." ) + route ;
494491 }
495492
496493 /**
@@ -539,11 +536,20 @@ private int getPriority(AnnotatedElement member) {
539536 protected @interface Hidden {
540537 }
541538
539+ /**
540+ * Sets the priority (order) of the entry in the document.
541+ */
542+ @ Retention (RetentionPolicy .RUNTIME )
543+ @ Target ({ElementType .TYPE , ElementType .FIELD })
544+ public @interface Priority {
545+ int value ();
546+ }
547+
542548 /**
543549 * Adds a header comment to the top of the config document.
544550 */
545551 @ Retention (RetentionPolicy .RUNTIME )
546- @ Target ({ ElementType .TYPE } )
552+ @ Target (ElementType .TYPE )
547553 public @interface Header {
548554 String [] value ();
549555 }
@@ -552,14 +558,8 @@ private int getPriority(AnnotatedElement member) {
552558 * Adds a footer comment to the bottom of the config document.
553559 */
554560 @ Retention (RetentionPolicy .RUNTIME )
555- @ Target ({ ElementType .TYPE } )
561+ @ Target (ElementType .TYPE )
556562 public @interface Footer {
557563 String [] value ();
558564 }
559-
560- @ Retention (RetentionPolicy .RUNTIME )
561- @ Target ({ElementType .TYPE , ElementType .FIELD })
562- public @interface Priority {
563- int value ();
564- }
565565}
0 commit comments