diff --git a/common/src/main/java/dev/xpple/betterconfig/api/Config.java b/common/src/main/java/dev/xpple/betterconfig/api/Config.java index ffdd5eb..58ad985 100644 --- a/common/src/main/java/dev/xpple/betterconfig/api/Config.java +++ b/common/src/main/java/dev/xpple/betterconfig/api/Config.java @@ -9,128 +9,299 @@ /** *

- * An annotation that specifies that this field should be treated as a configuration. - * The class in which this field is defined must be defined as {@code public}. You can - * add a comment about the config by setting the {@link Config#comment()} attribute. + * An annotation that specifies that this field should be treated as a configuration. The class in which this field is defined + * must be declared as {@code public}. *

* *

- * The name of the field will be used as the name of the config as well. The initial - * value given to the field will be used as default (fallback) value. Note that the - * field should not be final! Immutable in-code behaviour can be achieved by making the - * field private and adding a getter method. + * The name of the field will be used as the name of the config as well. The initial value given to the field will be used as + * default (fallback) value. This value will be used in the {@code reset} subcommand of the config command. Note that the + * field should not be {@code final}! Immutable in-code behaviour can be achieved by making the field {@code private} and + * adding a getter method. In the case of {@link java.util.Collection}s or {@link java.util.Map}s, one can return an immutable + * view. To make a config immutable in-game, see {@link Config#readOnly()}. *

* *

- * For each of the update attributes of this annotation, its value represents the name - * of a method along with optionally type parameters. See below for an example. - *

- *     {@code
- *     @Config(setter = @Config.Setter("exampleSetter"))
- *     public static String exampleString = "defaultString";
- *     public static void exampleSetter(String string) {
- *         exampleString = string.toLowerCase(Locale.ROOT);
- *     }
- *     }
- *     
- * This method can be private and is allowed to throw a {@link com.mojang.brigadier.exceptions.CommandSyntaxException}, - * which will be caught and dealt with as usual.
- * If an attribute equals the empty string (the default value), the Java built-in - * method will be used. For instance, the method {@link java.util.Collection#add} for - * adders. When the attribute is set to {@code "none"}, the availability of this default - * method is removed. + * In general, all elements of the config class should be {@code static}. That means both fields and methods should be {@code static}. *

* *

- * The {@link Config#setter()} attribute is used for objects and primitives. For these - * types the other attributes don't make sense, and will therefore be ignored. The - * {@link Config#adder()} is used for {@link java.util.Collection}s. You can also - * define an adder for a {@link java.util.Map}, in which you can create a key-value - * pair based on the single parameter. The {@link Config#putter()} attribute is solely - * used for {@code Map}s. Lastly, the {@link Config#remover()} is used for both - * {@code Collection}s and {@code Map}s. In the case of a {@code Map}, by default an - * entry will be removed based on its key. + * BetterConfig distinguishes three different types of configs: *

* - *

- * To track changes to the configs, you can use the {@link Config#onChange()} attribute. - * Its value represents the name of a method where the changes can be tracked. The method - * should have two parameters; one for the old value and one for the new value. For - * example: - *

- *     {@code
- *     @Config(onChange = "exampleOnChange")
- *     public static String exampleString = "defaultString";
- *     public static void exampleOnChange(String oldValue, String newValue) {
- *         LOGGER.info("exampleOnChange was updated | old: {}, new: {}", oldValue, newValue);
- *     }
- *     }
- *     
- * Both values are deep copies of the config that was changed, so they can be modified - * freely without care for the original object. - *

+ *
    + *
  1. {@link java.util.Collection}s
  2. + *
  3. {@link java.util.Map}s
  4. + *
  5. All other types
  6. + *
* *

- * To make a configuration unmodifiable by commands, mark it with {@code readOnly = true}. - * Enabling this will ignore all update annotations. To make a configuration temporary, - * that is, to disable loading and saving from a config file, set {@code temporary} to - * {@code true}. + * The type of the config is determined in the above order using the {@link Class#isAssignableFrom(Class)} method. The + * different types of configs can be customised differently. Below is a table showing which properties are applicable to which + * config types. *

* + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
PropertyType 1Type 2Type 3
{@link Setter}
{@link Adder}
{@link Putter}
{@link Remover}
+ * *

- * To make a configuration's visibility conditional on the {@code CommandSource}, use {@link Config#condition()}. - * The {@code CommandSource} parameter is optional. - *

- *     {@code
- *     @Config(condition = "myCondition")
- *     public static String myConfig = "";
- *     public static boolean myCondition(CommandSource source) {
- *         return Boolean.getBoolean("enableMyConfig");
- *     }
- *     }
- *     
+ * Attempting to use a property on an unsupported type will have no effect. *

*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Config { + /** + * An explanatory comment about the config. This value will be used in the {@code comment} subcommand of the config command. + * @return the comment + */ String comment() default ""; + /** + * A method name that will be used to represent the config value in the chat. The method should have no parameters and return + * a chat {@code Component}, which is {@link net.minecraft.network.chat.Component} on Fabric and {@link net.kyori.adventure.text.Component} + * on Paper. Below is an example for Fabric: + *
+     * {@code
+     * @Config(chatRepresentation = "chatRepresentation")
+     * public static ChatFormatting formatting = ChatFormatting.YELLOW;
+     * public static Component chatRepresentation() {
+     *     return Component.literal(formatting.getName()).withStyle(formatting);
+     * }
+     * }
+     * 
+ * @return the method name + */ String chatRepresentation() default ""; + /** + * A {@link Setter} that customises the {@code set} subcommand of the config command. + * @return the setter + */ Setter setter() default @Setter; + /** + * An {@link Adder} that customises the {@code add} subcommand of the config command. + * @return the setter + */ Adder adder() default @Adder; + /** + * A {@link Putter} that customises the {@code put} subcommand of the config command. + * @return the setter + */ Putter putter() default @Putter; + /** + * A {@link Remover} that customises the {@code remove} subcommand of the config command. + * @return the setter + */ Remover remover() default @Remover; + /** + * The name of a method that will be called whenever the config value is altered. The method should have two parameters; one + * for the old value and one for the new value. For example: + *
+     * {@code
+     * @Config(onChange = "exampleOnChange")
+     * public static String exampleString = "defaultString";
+     * public static void exampleOnChange(String oldValue, String newValue) {
+     *     LOGGER.info("exampleOnChange was updated | old: {}, new: {}", oldValue, newValue);
+     * }
+     * }
+     * 
+ * Both values are deep copies of the config that was changed, so they can be modified freely without care for the original + * object. + * @return the method name + */ String onChange() default ""; + /** + * Whether the config value is read-only. If set to true, only the {@code get} subcommand of the config command will be + * available. + * @return {@code true} if the config is read-only, {@code false} otherwise + */ boolean readOnly() default false; + /** + * Whether the config value is temporary. If set to true, the config will not be saved to the config file and will not persist + * between sessions. + * @return {@code true} if the config is temporary, {@code false} otherwise + */ boolean temporary() default false; + /** + * Specify a condition for the visibility of the config in the config command. The value should be a method name of a method + * that returns a boolean and optionally has the {@code CommandSource} as parameter. On Fabric, this is {@link net.minecraft.commands.SharedSuggestionProvider} + * and on Paper this is {@link io.papermc.paper.command.brigadier.CommandSourceStack}. For example: + *
+     * {@code
+     * @Config(condition = "myCondition")
+     * public static String myConfig = "";
+     * public static boolean myCondition(CommandSource source) {
+     *     return Boolean.getBoolean("enableMyConfig");
+     * }
+     * }
+     * 
+ * @return the method name + */ String condition() default ""; + /** + * The setter customises the {@code set} subcommand of the config command. This annotation can be used to transform or verify + * the user's input to the subcommand. + */ @Target({}) @interface Setter { + /** + * The method name of the setter. The method should not return anything and have a single parameter whose type matches the + * type of the config itself, unless the {@link Setter#type()} is specified. The method can be {@code private}. + * Optionally, the method may throw a {@link com.mojang.brigadier.exceptions.CommandSyntaxException} to indicate failure. + * Below is an example of a config along with a custom setter. + *
+         * {@code
+         * @Config(setter = @Config.Setter("exampleSetter"))
+         * public static String exampleString = "defaultString";
+         * public static void exampleSetter(String string) {
+         *     exampleString = string.toLowerCase(Locale.ROOT);
+         * }
+         * }
+         * 
+ * If this value is set to the empty string (the default), the field will be set using reflection. Additionally if the + * value is set to {@code none}, the {@code set} subcommand will not be available. + * @return the method name + */ String value() default ""; + /** + * An optional property that allows for an alternate type of the method parameter. + * @return the alternate type + */ Class type() default EMPTY.class; } + /** + * The adder customises the {@code add} subcommand of the config command. This annotation can be used to transform or verify + * the user's input to the subcommand. + */ @Target({}) @interface Adder { + /** + * The method name of the adder. The method should not return anything and have a single parameter whose type matches the + * type of the config itself, unless the {@link Adder#type()} is specified. The method can be {@code private}. + * Optionally, the method may throw a {@link com.mojang.brigadier.exceptions.CommandSyntaxException} to indicate failure. + * Below is an example of a config along with a custom adder. + *
+         * {@code
+         * @Config(adder = @Config.Adder("exampleAdder"))
+         * public static List exampleList = new ArrayList<>();
+         * public static void exampleAdder(String string) {
+         *     exampleList.add(string.toLowerCase(Locale.ROOT));
+         * }
+         * }
+         * 
+ * If this value is set to the empty string (the default), the {@link java.util.Collection#add(Object)} method will be + * used. Additionally if the value is set to {@code none}, the {@code add} subcommand will not be available. + * @return the method name + */ String value() default ""; + /** + * An optional property that allows for an alternate type of the method parameter. + * @return the alternate type + */ Class type() default EMPTY.class; } + /** + * The putter customises the {@code put} subcommand of the config command. This annotation can be used to transform or verify + * the user's input to the subcommand. + */ @Target({}) @interface Putter { + /** + * The method name of the putter. The method should not return anything and have two parameter whose types match the types + * of the key and value types of the map, unless {@link Putter#keyType()} or {@link Putter#valueType()} is specified. The + * method can be {@code private}. Optionally, the method may throw a {@link com.mojang.brigadier.exceptions.CommandSyntaxException} + * to indicate failure. Below is an example of a config along with a custom adder. + *
+         * {@code
+         * @Config(putter = @Config.Putter("examplePutter"))
+         * public static Map exampleMap = new HashMap<>();
+         * public static void examplePutter(String string) {
+         *     exampleMap.put(string.toLowerCase(Locale.ROOT), string.toUpperCase(Locale.ROOT));
+         * }
+         * }
+         * 
+ * If this value is set to the empty string (the default), the {@link java.util.Map#put(Object, Object)} method will be + * used. Additionally if the value is set to {@code none}, the {@code put} subcommand will not be available. + * @return the method name + */ String value() default ""; + /** + * An optional property that allows for an alternate type of the method parameter for the key. + * @return the alternate type + */ Class keyType() default EMPTY.class; + /** + * An optional property that allows for an alternate type of the method parameter for the value. + * @return the alternate type + */ Class valueType() default EMPTY.class; } + /** + * The remover customises the {@code remove} subcommand of the config command. This annotation can be used to transform or + * verify the user's input to the subcommand. + */ @Target({}) @interface Remover { + /** + * The method name of the remover. The method should not return anything and have a single parameter whose type matches + * the type of the config itself, unless the {@link Remover#type()} is specified. The method can be {@code private}. + * Optionally, the method may throw a {@link com.mojang.brigadier.exceptions.CommandSyntaxException} to indicate failure. + * Below is an example of a config along with a custom adder. + *
+         * {@code
+         * @Config(remover = @Config.Remover("exampleRemover"))
+         * public static List exampleList = new ArrayList<>();
+         * public static void exampleRemover(String string) {
+         *     exampleList.remove(string.toLowerCase(Locale.ROOT));
+         * }
+         * }
+         * 
+ * If this value is set to the empty string (the default), the {@link java.util.Collection#remove(Object)} method will be + * used. Additionally if the value is set to {@code none}, the {@code remove} subcommand will not be available. + * @return the method name + */ String value() default ""; + /** + * An optional property that allows for an alternate type of the method parameter. + * @return the alternate type + */ Class type() default EMPTY.class; }