Skip to content

Commit

Permalink
specs generics
Browse files Browse the repository at this point in the history
  • Loading branch information
KrLite committed May 3, 2024
1 parent af482e5 commit 49608a8
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 100 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,5 +13,5 @@
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface SpecInList {
Class<? extends ValuesInList<?>> definition();
Class<? extends InListProvider<?>> definition();
}
Original file line number Diff line number Diff line change
@@ -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<? extends InRangeProvider<?>> definition();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,14 +24,14 @@ public enum ExampleEnum {
THIRD
}

public static class ExampleStringValuesInList implements ValuesInList<String> {
public static class ExampleStringInListProvider implements InListProvider<String> {
@Override
public Collection<String> acceptableValues() {
return List.of("case 1", "case 2", "case 3");
}
}

public static class ExampleEnumValuesInList implements ValuesInList<ExampleEnum> {
public static class ExampleEnumInListProvider implements InListProvider<ExampleEnum> {
@Override
public Collection<ExampleEnum> acceptableValues() {
return List.of(ExampleEnum.FIRST, ExampleEnum.SECOND);
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

import java.util.Collection;

public interface ValuesInList<T> {
public interface InListProvider<T> {
Collection<T> acceptableValues();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package band.kessokuteatime.nightautoconfig.spec;

import org.jetbrains.annotations.NotNull;

public interface InRangeProvider<T extends Comparable<T>> {
@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;
}
}
212 changes: 121 additions & 91 deletions src/main/java/band/kessokuteatime/nightautoconfig/spec/Specs.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ private void appendNestedSpecs(ConfigSpec spec) {
});
}

private <E extends Enum<E>> void appendBasicSpecs(ConfigSpec spec) {
private void appendBasicSpecs(ConfigSpec spec) {
Arrays.stream(nonNestedFields())
.filter(field -> !field.getType().isEnum()) // Enums are handled separately
.forEach(field -> {
Expand Down Expand Up @@ -263,82 +263,107 @@ private <V extends Comparable<? super V>> void appendInRangeSpec(
spec.defineInRange(getPath(field), value, min, max);
}

private <V extends Comparable<? super V>> 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) {
throw new RuntimeException(e);
}
});

// 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) {
throw new RuntimeException(e);
}
});

// 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);
}
Expand All @@ -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) {
Expand All @@ -364,76 +390,80 @@ private void appendInListSpecs(ConfigSpec spec) {
});
}

private <E> 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<? super E>) 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 <E> 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<? super E>) 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 <E extends Enum<E>> 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<E> acceptableEnumValues = (Collection<E>) 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<E>) 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 <E extends Enum<E>> 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<E> acceptableEnumValues = (Collection<E>) 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<E>) 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));
}
}

0 comments on commit 49608a8

Please sign in to comment.