From 49608a80fe50f28a7255ea54b9422f6790ba9e5e Mon Sep 17 00:00:00 2001 From: KrLite <68179735+KrLite@users.noreply.github.com> Date: Fri, 3 May 2024 09:43:59 +0800 Subject: [PATCH] specs generics --- .../annotation/SpecInList.java | 4 +- .../annotation/SpecInRange.java | 14 ++ .../example/config/ExampleConfig.java | 11 +- ...{ValuesInList.java => InListProvider.java} | 2 +- .../nightautoconfig/spec/InRangeProvider.java | 16 ++ .../nightautoconfig/spec/Specs.java | 212 ++++++++++-------- 6 files changed, 159 insertions(+), 100 deletions(-) create mode 100644 src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInRange.java rename src/main/java/band/kessokuteatime/nightautoconfig/spec/{ValuesInList.java => InListProvider.java} (76%) create mode 100644 src/main/java/band/kessokuteatime/nightautoconfig/spec/InRangeProvider.java diff --git a/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInList.java b/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInList.java index 7a7e5e1..e68dcbd 100644 --- a/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInList.java +++ b/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInList.java @@ -1,6 +1,6 @@ package band.kessokuteatime.nightautoconfig.annotation; -import band.kessokuteatime.nightautoconfig.spec.ValuesInList; +import band.kessokuteatime.nightautoconfig.spec.InListProvider; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; @@ -13,5 +13,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface SpecInList { - Class> definition(); + Class> definition(); } diff --git a/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInRange.java b/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInRange.java new file mode 100644 index 0000000..f493b49 --- /dev/null +++ b/src/main/java/band/kessokuteatime/nightautoconfig/annotation/SpecInRange.java @@ -0,0 +1,14 @@ +package band.kessokuteatime.nightautoconfig.annotation; + +import band.kessokuteatime.nightautoconfig.spec.InRangeProvider; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface SpecInRange { + Class> definition(); +} diff --git a/src/main/java/band/kessokuteatime/nightautoconfig/example/config/ExampleConfig.java b/src/main/java/band/kessokuteatime/nightautoconfig/example/config/ExampleConfig.java index 48a13b3..2abac65 100644 --- a/src/main/java/band/kessokuteatime/nightautoconfig/example/config/ExampleConfig.java +++ b/src/main/java/band/kessokuteatime/nightautoconfig/example/config/ExampleConfig.java @@ -5,8 +5,7 @@ import band.kessokuteatime.nightautoconfig.annotation.SpecInRangeDouble; import band.kessokuteatime.nightautoconfig.annotation.SpecOfClass; import band.kessokuteatime.nightautoconfig.converter.FloatToDoubleConverter; -import band.kessokuteatime.nightautoconfig.converter.MapToConfigConverter; -import band.kessokuteatime.nightautoconfig.spec.ValuesInList; +import band.kessokuteatime.nightautoconfig.spec.InListProvider; import com.electronwill.nightconfig.core.EnumGetMethod; import com.electronwill.nightconfig.core.conversion.Conversion; import com.electronwill.nightconfig.core.conversion.Path; @@ -25,14 +24,14 @@ public enum ExampleEnum { THIRD } - public static class ExampleStringValuesInList implements ValuesInList { + public static class ExampleStringInListProvider implements InListProvider { @Override public Collection acceptableValues() { return List.of("case 1", "case 2", "case 3"); } } - public static class ExampleEnumValuesInList implements ValuesInList { + public static class ExampleEnumInListProvider implements InListProvider { @Override public Collection acceptableValues() { return List.of(ExampleEnum.FIRST, ExampleEnum.SECOND); @@ -80,14 +79,14 @@ public static class InnerConfig { public String innerString = "S.T.A.Y."; - @SpecInList(definition = ExampleStringValuesInList.class) + @SpecInList(definition = ExampleStringInListProvider.class) public String restrictedString = "case 1"; @SpecOfClass(ExampleEnum.class) public ExampleEnum innerEnum = ExampleEnum.SECOND; @SpecEnum(method = EnumGetMethod.ORDINAL_OR_NAME) - @SpecInList(definition = ExampleEnumValuesInList.class) + @SpecInList(definition = ExampleEnumInListProvider.class) public ExampleEnum restrictedEnum = ExampleEnum.SECOND; } } diff --git a/src/main/java/band/kessokuteatime/nightautoconfig/spec/ValuesInList.java b/src/main/java/band/kessokuteatime/nightautoconfig/spec/InListProvider.java similarity index 76% rename from src/main/java/band/kessokuteatime/nightautoconfig/spec/ValuesInList.java rename to src/main/java/band/kessokuteatime/nightautoconfig/spec/InListProvider.java index f5d85f8..5907226 100644 --- a/src/main/java/band/kessokuteatime/nightautoconfig/spec/ValuesInList.java +++ b/src/main/java/band/kessokuteatime/nightautoconfig/spec/InListProvider.java @@ -2,6 +2,6 @@ import java.util.Collection; -public interface ValuesInList { +public interface InListProvider { Collection acceptableValues(); } diff --git a/src/main/java/band/kessokuteatime/nightautoconfig/spec/InRangeProvider.java b/src/main/java/band/kessokuteatime/nightautoconfig/spec/InRangeProvider.java new file mode 100644 index 0000000..2a86116 --- /dev/null +++ b/src/main/java/band/kessokuteatime/nightautoconfig/spec/InRangeProvider.java @@ -0,0 +1,16 @@ +package band.kessokuteatime.nightautoconfig.spec; + +import org.jetbrains.annotations.NotNull; + +public interface InRangeProvider> { + @NotNull T min(); + @NotNull T max(); + + default boolean inRangeInclusive(T value) { + return value.compareTo(min()) >= 0 && value.compareTo(max()) <= 0; + } + + default boolean inRangeExclusive(T value) { + return value.compareTo(min()) > 0 && value.compareTo(max()) < 0; + } +} diff --git a/src/main/java/band/kessokuteatime/nightautoconfig/spec/Specs.java b/src/main/java/band/kessokuteatime/nightautoconfig/spec/Specs.java index f3858a4..b514918 100644 --- a/src/main/java/band/kessokuteatime/nightautoconfig/spec/Specs.java +++ b/src/main/java/band/kessokuteatime/nightautoconfig/spec/Specs.java @@ -210,7 +210,7 @@ private void appendNestedSpecs(ConfigSpec spec) { }); } - private > void appendBasicSpecs(ConfigSpec spec) { + private void appendBasicSpecs(ConfigSpec spec) { Arrays.stream(nonNestedFields()) .filter(field -> !field.getType().isEnum()) // Enums are handled separately .forEach(field -> { @@ -263,41 +263,66 @@ private > void appendInRangeSpec( spec.defineInRange(getPath(field), value, min, max); } + private > void appendInRangeSpec(ConfigSpec spec, Field field) { + SpecInRange inRangeAnnotation = field.getAnnotation(SpecInRange.class); + try { + field.setAccessible(true); + Object value = field.get(t); + + InRangeProvider inRangeProvider = inRangeAnnotation.definition().getDeclaredConstructor().newInstance(); + if (field.getType() == inRangeProvider.min().getClass()) { + appendInRangeSpec(spec, field, (V) value, (V) inRangeProvider.min(), (V) inRangeProvider.max()); + } else { + LOGGER.error( + "Invalid @{} annotation for {}: range values must be of the same type as the field. Ignoring!", + SpecInRange.class.getSimpleName(), getPath(field) + ); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + private void appendInRangeSpecs(ConfigSpec spec) { Field[] fields = nonNestedFields(); - // Byte + // General Arrays.stream(fields) - .filter(typeChecker(SpecInRangeByte.associatedTypes)) - .filter(field -> field.isAnnotationPresent(SpecInRangeByte.class)) + .filter(field -> field.isAnnotationPresent(SpecInRange.class)) + .forEach(field -> appendInRangeSpec(spec, field)); + + // Double + Arrays.stream(fields) + .filter(typeChecker(List.of(Double.class, double.class))) + .filter(field -> field.isAnnotationPresent(SpecInRangeDouble.class)) .forEach(field -> { - SpecInRangeByte inRangeAnnotation = field.getAnnotation(SpecInRangeByte.class); + SpecInRangeDouble inRangeAnnotation = field.getAnnotation(SpecInRangeDouble.class); try { - appendInRangeSpec(spec, field, inRangeAnnotation.min(), inRangeAnnotation.max()); + appendInRangeSpec(spec, field, (double) field.get(t), inRangeAnnotation.min(), inRangeAnnotation.max()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }); - // Short + // Float Arrays.stream(fields) - .filter(typeChecker(SpecInRangeShort.associatedTypes)) - .filter(field -> field.isAnnotationPresent(SpecInRangeShort.class)) + .filter(typeChecker(List.of(Float.class, float.class))) + .filter(field -> field.isAnnotationPresent(SpecInRangeDouble.class)) .forEach(field -> { - SpecInRangeShort inRangeAnnotation = field.getAnnotation(SpecInRangeShort.class); + SpecInRangeDouble inRangeAnnotation = field.getAnnotation(SpecInRangeDouble.class); try { - appendInRangeSpec(spec, field, inRangeAnnotation.min(), inRangeAnnotation.max()); + appendInRangeSpec(spec, field, safeDouble(field.get(t)), inRangeAnnotation.min(), inRangeAnnotation.max()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }); - // Int + // Long Arrays.stream(fields) - .filter(typeChecker(SpecInRangeInt.associatedTypes)) - .filter(field -> field.isAnnotationPresent(SpecInRangeInt.class)) + .filter(typeChecker(SpecInRangeLong.associatedTypes)) + .filter(field -> field.isAnnotationPresent(SpecInRangeLong.class)) .forEach(field -> { - SpecInRangeInt inRangeAnnotation = field.getAnnotation(SpecInRangeInt.class); + SpecInRangeLong inRangeAnnotation = field.getAnnotation(SpecInRangeLong.class); try { appendInRangeSpec(spec, field, inRangeAnnotation.min(), inRangeAnnotation.max()); } catch (IllegalAccessException e) { @@ -305,12 +330,12 @@ private void appendInRangeSpecs(ConfigSpec spec) { } }); - // Long + // Int Arrays.stream(fields) - .filter(typeChecker(SpecInRangeLong.associatedTypes)) - .filter(field -> field.isAnnotationPresent(SpecInRangeLong.class)) + .filter(typeChecker(SpecInRangeInt.associatedTypes)) + .filter(field -> field.isAnnotationPresent(SpecInRangeInt.class)) .forEach(field -> { - SpecInRangeLong inRangeAnnotation = field.getAnnotation(SpecInRangeLong.class); + SpecInRangeInt inRangeAnnotation = field.getAnnotation(SpecInRangeInt.class); try { appendInRangeSpec(spec, field, inRangeAnnotation.min(), inRangeAnnotation.max()); } catch (IllegalAccessException e) { @@ -318,27 +343,27 @@ private void appendInRangeSpecs(ConfigSpec spec) { } }); - // Float + // Short Arrays.stream(fields) - .filter(typeChecker(List.of(Float.class, float.class))) - .filter(field -> field.isAnnotationPresent(SpecInRangeDouble.class)) + .filter(typeChecker(SpecInRangeShort.associatedTypes)) + .filter(field -> field.isAnnotationPresent(SpecInRangeShort.class)) .forEach(field -> { - SpecInRangeDouble inRangeAnnotation = field.getAnnotation(SpecInRangeDouble.class); + SpecInRangeShort inRangeAnnotation = field.getAnnotation(SpecInRangeShort.class); try { - appendInRangeSpec(spec, field, safeDouble(field.get(t)), inRangeAnnotation.min(), inRangeAnnotation.max()); + appendInRangeSpec(spec, field, inRangeAnnotation.min(), inRangeAnnotation.max()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }); - // Double + // Byte Arrays.stream(fields) - .filter(typeChecker(List.of(Double.class, double.class))) - .filter(field -> field.isAnnotationPresent(SpecInRangeDouble.class)) + .filter(typeChecker(SpecInRangeByte.associatedTypes)) + .filter(field -> field.isAnnotationPresent(SpecInRangeByte.class)) .forEach(field -> { - SpecInRangeDouble inRangeAnnotation = field.getAnnotation(SpecInRangeDouble.class); + SpecInRangeByte inRangeAnnotation = field.getAnnotation(SpecInRangeByte.class); try { - appendInRangeSpec(spec, field, (double) field.get(t), inRangeAnnotation.min(), inRangeAnnotation.max()); + appendInRangeSpec(spec, field, inRangeAnnotation.min(), inRangeAnnotation.max()); } catch (IllegalAccessException e) { throw new RuntimeException(e); } @@ -355,7 +380,8 @@ private void appendInListSpecs(ConfigSpec spec) { field.setAccessible(true); Object value = field.get(t); - Collection acceptableValues = inListAnnotation.definition().getDeclaredConstructor().newInstance().acceptableValues(); + InListProvider inListProvider = inListAnnotation.definition().getDeclaredConstructor().newInstance(); + Collection acceptableValues = inListProvider.acceptableValues(); spec.defineInList(getPath(field), value, acceptableValues); } catch (Exception e) { @@ -364,76 +390,80 @@ private void appendInListSpecs(ConfigSpec spec) { }); } + private void appendOfClassSpec(ConfigSpec spec, Field field) { + SpecOfClass ofClassAnnotation = field.getAnnotation(SpecOfClass.class); + try { + field.setAccessible(true); + Object value = field.get(t); + + Class ofClass = ofClassAnnotation.value(); + + if (ofClass.isAssignableFrom(field.getType())) { + spec.defineOfClass(getPath(field), (E) value, (Class) ofClass); + } else { + LOGGER.error( + "Invalid @{} annotation for {}: the field type is not a subclass of the specified class (the specified class is not assignable from the field type). Ignoring!", + SpecOfClass.class.getSimpleName(), getPath(field) + ); + } + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + private void appendOfClassSpecs(ConfigSpec spec) { Arrays.stream(nonNestedFields()) .filter(field -> !field.getType().isEnum()) // Enums are handled separately .filter(field -> field.isAnnotationPresent(SpecOfClass.class)) - .forEach(field -> { - SpecOfClass ofClassAnnotation = field.getAnnotation(SpecOfClass.class); - try { - field.setAccessible(true); - Object value = field.get(t); - - Class ofClass = ofClassAnnotation.value(); + .forEach(field -> appendOfClassSpec(spec, field)); + } - if (ofClass.isAssignableFrom(field.getType())) { - spec.defineOfClass(getPath(field), (E) value, (Class) ofClass); - } else { - LOGGER.error( - "Invalid @{} annotation for {}: the field type is not a subclass of the specified class. Ignoring!", - SpecOfClass.class.getSimpleName(), getPath(field) - ); - } - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - }); + private > void appendEnumSpec(ConfigSpec spec, Field field) { + SpecEnum specEnum = field.getAnnotation(SpecEnum.class); + EnumGetMethod enumGetMethod = (specEnum != null) ? specEnum.method() : EnumGetMethod.NAME_IGNORECASE; + try { + field.setAccessible(true); + Object value = field.get(t); + + if (field.isAnnotationPresent(SpecInList.class)) { + // Restricted + SpecInList inListAnnotation = field.getAnnotation(SpecInList.class); + Collection acceptableValues = inListAnnotation.definition().getDeclaredConstructor().newInstance().acceptableValues(); + + if (acceptableValues.stream().allMatch(v -> v.getClass().isEnum() && v.getClass() == field.getType())) { + Collection acceptableEnumValues = (Collection) acceptableValues; + spec.defineRestrictedEnum(getPath(field), (E) value, acceptableEnumValues, enumGetMethod); + } else { + LOGGER.error( + "Invalid @{} annotation for {}: acceptable values must be enums of the same type as the field. Ignoring!", + SpecInList.class.getSimpleName(), getPath(field) + ); + } + } else if (field.isAnnotationPresent(SpecOfClass.class)) { + // Restricted by class + // Currently cannot be handled by `defineOfClass` due to unknown issues + SpecOfClass ofClassAnnotation = field.getAnnotation(SpecOfClass.class); + Class ofClass = ofClassAnnotation.value(); + if (ofClass.isAssignableFrom(field.getType())) { + spec.defineRestrictedEnum(getPath(field), (E) value, (List) List.of(ofClass.getEnumConstants()), enumGetMethod); + } else { + LOGGER.error( + "Invalid @{} annotation for {}: the field type is not a subclass of the specified class (the specified class is not assignable from the field type). Ignoring!", + SpecOfClass.class.getSimpleName(), getPath(field) + ); + } + }else { + // Unrestricted + spec.defineEnum(getPath(field), (E) value, enumGetMethod); + } + } catch (Exception e) { + throw new RuntimeException(e); + } } private > void appendEnumSpecs(ConfigSpec spec) { Arrays.stream(nonNestedFields()) .filter(field -> field.getType().isEnum()) - .forEach(field -> { - SpecEnum specEnum = field.getAnnotation(SpecEnum.class); - EnumGetMethod enumGetMethod = (specEnum != null) ? specEnum.method() : EnumGetMethod.NAME_IGNORECASE; - try { - field.setAccessible(true); - Object value = field.get(t); - - if (field.isAnnotationPresent(SpecInList.class)) { - // Restricted - SpecInList inListAnnotation = field.getAnnotation(SpecInList.class); - Collection acceptableValues = inListAnnotation.definition().getDeclaredConstructor().newInstance().acceptableValues(); - - if (acceptableValues.stream().allMatch(v -> v.getClass().isEnum() && v.getClass() == field.getType())) { - Collection acceptableEnumValues = (Collection) acceptableValues; - spec.defineRestrictedEnum(getPath(field), (E) value, acceptableEnumValues, enumGetMethod); - } else { - LOGGER.error( - "Invalid @{} annotation for {}: acceptable values must be enums of the same type as the field. Ignoring!", - SpecInList.class.getSimpleName(), getPath(field) - ); - } - } else if (field.isAnnotationPresent(SpecOfClass.class)) { - // Restricted by class - // Currently cannot be handled by `defineOfClass` due to unknown issues - SpecOfClass ofClassAnnotation = field.getAnnotation(SpecOfClass.class); - Class ofClass = ofClassAnnotation.value(); - if (ofClass.isAssignableFrom(field.getType())) { - spec.defineRestrictedEnum(getPath(field), (E) value, (List) List.of(ofClass.getEnumConstants()), enumGetMethod); - } else { - LOGGER.error( - "Invalid @{} annotation for {}: the field type is not a subclass of the specified class. Ignoring!", - SpecOfClass.class.getSimpleName(), getPath(field) - ); - } - }else { - // Unrestricted - spec.defineEnum(getPath(field), (E) value, enumGetMethod); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + .forEach(field -> appendEnumSpec(spec, field)); } }