downgrade java version to java 8 (velocity still use java 11)

master
InkerBot 3 months ago
parent 093ded264d
commit f2b7c98d36

@ -1,4 +1,4 @@
allprojects {
group = "de.exlll"
version = "4.5.0"
group = "org.inksnow.husk"
version = "4.5.1"
}

@ -6,8 +6,8 @@ plugins {
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
withJavadocJar()
withSourcesJar()
@ -37,11 +37,19 @@ dependencies {
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/Exlll/ConfigLib")
name = "husk-release"
url = findProperty("repository.huskrelease.url")
?.let(::uri)
?: throw GradleException("repository.huskrelease.url is not set")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
username = findProperty("repository.huskrelease.username")
?.toString()
?: throw GradleException("repository.huskrelease.username is not set")
password = findProperty("repository.huskrelease.password")
?.toString()
?: throw GradleException("repository.huskrelease.password is not set")
}
}
}

@ -24,9 +24,22 @@ import java.util.List;
* int fn2;
* }
* </pre>
*
* @param comments
* @param elementNames
*/
record CommentNode(List<String> comments, List<String> elementNames) {}
public class CommentNode {
private final List<String> comments;
private final List<String> elementNames;
public CommentNode(List<String> comments, List<String> elementNames) {
this.comments = comments;
this.elementNames = elementNames;
}
public List<String> elementNames() {
return elementNames;
}
public List<String> comments() {
return comments;
}
}

@ -2,9 +2,11 @@ package de.exlll.configlib;
import de.exlll.configlib.ConfigurationElements.FieldElement;
import de.exlll.configlib.ConfigurationElements.RecordComponentElement;
import de.exlll.configlib.util.ref.R$Class;
import java.lang.reflect.AnnotatedElement;
import java.util.*;
import java.util.stream.Collectors;
import static de.exlll.configlib.Validator.requireConfigurationType;
import static de.exlll.configlib.Validator.requireNonNull;
@ -20,10 +22,23 @@ final class CommentNodeExtractor {
this.outputNull = properties.outputNulls();
}
private record State(
Iterator<? extends ConfigurationElement<?>> iterator,
Object elementHolder
) {}
private class State {
private final Iterator<? extends ConfigurationElement<?>> iterator;
private final Object elementHolder;
private State(Iterator<? extends ConfigurationElement<?>> iterator, Object elementHolder) {
this.iterator = iterator;
this.elementHolder = elementHolder;
}
public Iterator<? extends ConfigurationElement<?>> iterator() {
return iterator;
}
public Object elementHolder() {
return elementHolder;
}
}
/**
* Extracts {@code CommentNode}s of the given configuration type in a DFS manner.
@ -37,8 +52,8 @@ final class CommentNodeExtractor {
public Queue<CommentNode> extractCommentNodes(final Object elementHolder) {
requireConfigurationType(elementHolder.getClass());
final Queue<CommentNode> result = new ArrayDeque<>();
final var elementNameStack = new ArrayDeque<>(List.of(""));
final var stateStack = new ArrayDeque<>(List.of(stateFromObject(elementHolder)));
final ArrayDeque<String> elementNameStack = new ArrayDeque<>(Collections.singletonList(""));
final ArrayDeque<State> stateStack = new ArrayDeque<>(Collections.singletonList(stateFromObject(elementHolder)));
State state;
while (!stateStack.isEmpty()) {
@ -46,21 +61,21 @@ final class CommentNodeExtractor {
elementNameStack.removeLast();
while (state.iterator.hasNext()) {
final var element = state.iterator.next();
final var elementValue = element.value(state.elementHolder);
final ConfigurationElement<?> element = state.iterator.next();
final Object elementValue = element.value(state.elementHolder);
if ((elementValue == null) && !outputNull)
continue;
final var elementName = element.name();
final var commentNode = createNodeIfCommentPresent(
final String elementName = element.name();
final Optional<CommentNode> commentNode = createNodeIfCommentPresent(
element.element(),
elementName,
elementNameStack
);
commentNode.ifPresent(result::add);
final var elementType = element.type();
final Class<?> elementType = element.type();
if ((elementValue != null) && Reflect.isConfigurationType(elementType)) {
stateStack.addLast(state);
elementNameStack.addLast(nameFormatter.format(elementName));
@ -73,8 +88,8 @@ final class CommentNodeExtractor {
}
private State stateFromObject(final Object elementHolder) {
final var type = elementHolder.getClass();
final var iter = type.isRecord()
final Class<?> type = elementHolder.getClass();
final Iterator<? extends ConfigurationElement<? extends AnnotatedElement>> iter = R$Class.of(type).isRecord()
? recordComponentElements(elementHolder)
: fieldElements(elementHolder);
return new State(iter, elementHolder);
@ -86,13 +101,13 @@ final class CommentNodeExtractor {
final Deque<String> elementNameStack
) {
if (element.isAnnotationPresent(Comment.class)) {
final var comments = Arrays.stream(element.getAnnotation(Comment.class).value())
final List<String> comments = Arrays.stream(element.getAnnotation(Comment.class).value())
.flatMap(s -> Arrays.stream(s.split("\n", -1)))
.toList();
final var formattedName = nameFormatter.format(elementName);
final var elementNames = new ArrayList<>(elementNameStack);
.collect(Collectors.toList());
final String formattedName = nameFormatter.format(elementName);
final ArrayList<String> elementNames = new ArrayList<>(elementNameStack);
elementNames.add(formattedName);
final var result = new CommentNode(comments, elementNames);
final CommentNode result = new CommentNode(comments, elementNames);
return Optional.of(result);
}
return Optional.empty();
@ -106,7 +121,7 @@ final class CommentNodeExtractor {
}
private Iterator<RecordComponentElement> recordComponentElements(Object record) {
return Arrays.stream(record.getClass().getRecordComponents())
return Arrays.stream(R$Class.of(record.getClass()).getRecordComponents())
.map(RecordComponentElement::new)
.iterator();
}

@ -4,18 +4,14 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.RecordComponent;
/**
* Represents an element of a serializable configuration type. The element can either be a
* {@link Field} for configuration classes or a {@link RecordComponent} for records.
* {@link Field} for configuration classes or a {@link java.lang.reflect.RecordComponent} for records.
*
* @param <T> the type of the element
*/
public sealed interface ConfigurationElement<T extends AnnotatedElement>
permits
ConfigurationElements.FieldElement,
ConfigurationElements.RecordComponentElement {
public interface ConfigurationElement<T extends AnnotatedElement> {
/**
* Returns the element itself.
*

@ -1,13 +1,25 @@
package de.exlll.configlib;
import de.exlll.configlib.util.ref.R$RecordComponent;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field;
import java.lang.reflect.RecordComponent;
final class ConfigurationElements {
private ConfigurationElements() {}
record FieldElement(Field element) implements ConfigurationElement<Field> {
static class FieldElement implements ConfigurationElement<Field> {
private final Field element;
public FieldElement(Field element) {
this.element = element;
}
@Override
public Field element() {
return element;
}
@Override
public String name() {
return element.getName();
@ -34,8 +46,18 @@ final class ConfigurationElements {
}
}
record RecordComponentElement(RecordComponent element)
implements ConfigurationElement<RecordComponent> {
static class RecordComponentElement implements ConfigurationElement<R$RecordComponent> {
private final R$RecordComponent element;
RecordComponentElement(R$RecordComponent element) {
this.element = element;
}
@Override
public R$RecordComponent element() {
return element;
}
@Override
public String name() {
return element.getName();

@ -4,6 +4,7 @@ import de.exlll.configlib.ConfigurationElements.FieldElement;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
final class ConfigurationSerializer<T> extends TypeSerializer<T, FieldElement> {
ConfigurationSerializer(Class<T> configurationType, ConfigurationProperties properties) {
@ -12,8 +13,8 @@ final class ConfigurationSerializer<T> extends TypeSerializer<T, FieldElement> {
@Override
public T deserialize(Map<?, ?> serializedConfiguration) {
final var deserializedElements = deserializeConfigurationElements(serializedConfiguration);
final var elements = elements();
final Object[] deserializedElements = deserializeConfigurationElements(serializedConfiguration);
final List<FieldElement> elements = elements();
final T result = newDefaultInstance();
for (int i = 0; i < deserializedElements.length; i++) {
final FieldElement fieldElement = elements.get(i);
@ -33,8 +34,7 @@ final class ConfigurationSerializer<T> extends TypeSerializer<T, FieldElement> {
@Override
protected String baseDeserializeExceptionMessage(FieldElement element, Object value) {
return "Deserialization of value '%s' with type '%s' for field '%s' failed."
.formatted(value, value.getClass(), element.element());
return "Deserialization of value '" + value + "' with type '" + value.getClass() + "' for field '" + element.element() + "' failed.";
}
@Override
@ -42,7 +42,7 @@ final class ConfigurationSerializer<T> extends TypeSerializer<T, FieldElement> {
return FieldExtractors.CONFIGURATION.extract(type)
.filter(properties.getFieldFilter())
.map(FieldElement::new)
.toList();
.collect(Collectors.toList());
}
@Override

@ -3,6 +3,7 @@ package de.exlll.configlib;
import java.lang.reflect.Field;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
enum FieldExtractors implements FieldExtractor {
@ -22,7 +23,7 @@ enum FieldExtractors implements FieldExtractor {
List<Field> fields = classes.stream()
.flatMap(c -> Arrays.stream(c.getDeclaredFields()))
.filter(FieldFilters.DEFAULT)
.toList();
.collect(Collectors.toList());
requireNoShadowing(fields);
return fields.stream();
}
@ -32,8 +33,8 @@ enum FieldExtractors implements FieldExtractor {
Map<String, Class<?>> map = new LinkedHashMap<>();
for (Field field : fields) {
var fieldName = field.getName();
var fieldClass = field.getDeclaringClass();
String fieldName = field.getName();
Class<?> fieldClass = field.getDeclaringClass();
if (map.containsKey(fieldName)) {
Class<?> superClass = map.get(fieldName);

@ -1,5 +1,7 @@
package de.exlll.configlib;
import de.exlll.configlib.util.StringUtil;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@ -21,22 +23,20 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
}
private void requireNonBlankProperty() {
if (polymorphic.property().isBlank()) {
String msg = "The @Polymorphic annotation does not allow a blank property name but " +
"type '%s' uses one.".formatted(polymorphicType.getName());
throw new ConfigurationException(msg);
if (StringUtil.isBlank(polymorphic.property())) {
throw new ConfigurationException("The @Polymorphic annotation does not allow a blank property name but type '" + polymorphicType.getName() + "' uses one.");
}
}
private void initAliases() {
final var polymorphicTypes = polymorphicType.getAnnotation(PolymorphicTypes.class);
final PolymorphicTypes polymorphicTypes = polymorphicType.getAnnotation(PolymorphicTypes.class);
if (polymorphicTypes == null)
return;
for (PolymorphicTypes.Type pType : polymorphicTypes.value()) {
final var type = pType.type();
final var alias = pType.alias().isBlank() ? type.getName() : pType.alias();
final Class<?> type = pType.type();
final String alias = StringUtil.isBlank(pType.alias()) ? type.getName() : pType.alias();
requireDistinctAliases(alias);
requireDistinctTypes(type);
@ -48,37 +48,32 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
private void requireDistinctAliases(String alias) {
if (typeByAlias.containsKey(alias)) {
String msg = "The @PolymorphicTypes annotation must not use the same alias for " +
"multiple types. Alias '%s' appears more than once."
.formatted(alias);
throw new ConfigurationException(msg);
throw new ConfigurationException("The @PolymorphicTypes annotation must not use the same alias for multiple types. Alias '" + alias + "' appears more than once.");
}
}
private void requireDistinctTypes(Class<?> type) {
if (aliasByType.containsKey(type)) {
String msg = "The @PolymorphicTypes annotation must not contain multiple " +
"definitions for the same subtype. Type '%s' appears more than once."
.formatted(type.getName());
throw new ConfigurationException(msg);
throw new ConfigurationException("The @PolymorphicTypes annotation must not contain multiple definitions for the same subtype. Type '" + type.getName() + "' appears more than once.");
}
}
@Override
@SuppressWarnings("rawtypes")
public Map<?, ?> serialize(Object element) {
final Class<?> elementType = element.getClass();
// this cast won't cause any exceptions as we only pass objects of types the
// serializer expects
@SuppressWarnings("unchecked")
final var serializer = (TypeSerializer<Object, ?>) TypeSerializer.newSerializerFor(
final TypeSerializer<Object, ?> serializer = (TypeSerializer<Object, ?>) TypeSerializer.newSerializerFor(
elementType,
context.properties()
);
final var serialization = serializer.serialize(element);
final Map serialization = serializer.serialize(element);
requireSerializationNotContainsProperty(serialization);
final var result = new LinkedHashMap<>();
final LinkedHashMap result = new LinkedHashMap<>();
result.put(polymorphic.property(), getTypeIdentifierByType(elementType));
result.putAll(serialization);
return result;
@ -91,11 +86,9 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
private void requireSerializationNotContainsProperty(Map<?, ?> serialization) {
if (serialization.containsKey(polymorphic.property())) {
String msg = ("Polymorphic serialization for type '%s' failed. The type contains a " +
"configuration element with name '%s' but that name is " +
"used by the @Polymorphic property.")
.formatted(polymorphicType.getName(), polymorphic.property());
throw new ConfigurationException(msg);
throw new ConfigurationException("Polymorphic serialization for type '" + polymorphicType.getName() +
"' failed. The type contains a configuration element with name '" + polymorphic.property() +
"' but that name is used by the @Polymorphic property.");
}
}
@ -103,11 +96,11 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
public Object deserialize(Map<?, ?> element) {
requirePropertyPresent(element);
final var typeIdentifier = element.get(polymorphic.property());
final Object typeIdentifier = element.get(polymorphic.property());
requireTypeIdentifierString(typeIdentifier);
final var type = getTypeByTypeIdentifier((String) typeIdentifier);
final var serializer = TypeSerializer.newSerializerFor(type, context.properties());
final Class<?> type = getTypeByTypeIdentifier((String) typeIdentifier);
final TypeSerializer<?, ?> serializer = TypeSerializer.newSerializerFor(type, context.properties());
return serializer.deserialize(element);
}
@ -120,41 +113,23 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
try {
return Reflect.getClassByName(className);
} catch (RuntimeException e) {
String msg = ("Polymorphic deserialization for type '%s' failed. " +
"The class '%s' does not exist.")
.formatted(polymorphicType.getName(), className);
throw new ConfigurationException(msg, e);
throw new ConfigurationException("Polymorphic deserialization for type '" + polymorphicType.getName() + "' failed. The class '" + className + "' does not exist.", e);
}
}
private void requirePropertyPresent(Map<?, ?> element) {
if (element.get(polymorphic.property()) != null)
return;
String msg = """
Polymorphic deserialization for type '%s' failed. \
The property '%s' which holds the type is missing. \
Value to be deserialized:
%s\
"""
.formatted(
polymorphicType.getName(),
polymorphic.property(),
element
);
throw new ConfigurationException(msg);
throw new ConfigurationException("Polymorphic deserialization for type '" + polymorphicType.getName() +
"' failed. The property '" + polymorphic.property() + "' which holds the type is missing. " +
"Value to be deserialized:\n" + element);
}
private void requireTypeIdentifierString(Object typeIdentifier) {
if (typeIdentifier instanceof String)
return;
String msg = ("Polymorphic deserialization for type '%s' failed. The type identifier '%s' " +
"which should hold the type is not a string but of type '%s'.")
.formatted(
polymorphicType.getName(),
typeIdentifier,
typeIdentifier.getClass().getName()
);
throw new ConfigurationException(msg);
throw new ConfigurationException("Polymorphic deserialization for type '" + polymorphicType.getName() + "' failed. The type identifier '" + typeIdentifier + "' " +
"which should hold the type is not a string but of type '" + typeIdentifier.getClass().getName() + "'.");
}
Class<?> getPolymorphicType() {

@ -1,10 +1,12 @@
package de.exlll.configlib;
import de.exlll.configlib.ConfigurationElements.RecordComponentElement;
import de.exlll.configlib.util.ref.R$Class;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
final class RecordSerializer<R> extends TypeSerializer<R, RecordComponentElement> {
RecordSerializer(Class<R> recordType, ConfigurationProperties properties) {
@ -13,31 +15,28 @@ final class RecordSerializer<R> extends TypeSerializer<R, RecordComponentElement
@Override
public R deserialize(Map<?, ?> serializedConfiguration) {
final var ctorArgs = deserializeConfigurationElements(serializedConfiguration);
final var result = Reflect.callCanonicalConstructor(type, ctorArgs);
final Object[] ctorArgs = deserializeConfigurationElements(serializedConfiguration);
final R result = Reflect.callCanonicalConstructor(type, ctorArgs);
return postProcessor.apply(result);
}
@Override
protected void requireSerializableElements() {
if (serializers.isEmpty()) {
String msg = "Record type '%s' does not define any components."
.formatted(type.getSimpleName());
throw new ConfigurationException(msg);
throw new ConfigurationException("Record type '" + type.getSimpleName() + "' does not define any components.");
}
}
@Override
protected String baseDeserializeExceptionMessage(RecordComponentElement element, Object value) {
return "Deserialization of value '%s' with type '%s' for component '%s' of record '%s' failed."
.formatted(value, value.getClass(), element.element(), element.declaringType());
return "Deserialization of value '" + value + "' with type '" + value.getClass() + "' for component '" + element.element() + "' of record '" + element.declaringType() + "' failed.";
}
@Override
protected List<RecordComponentElement> elements() {
return Arrays.stream(type.getRecordComponents())
return Arrays.stream(R$Class.of(type).getRecordComponents())
.map(RecordComponentElement::new)
.toList();
.collect(Collectors.toList());
}
@Override

@ -1,5 +1,8 @@
package de.exlll.configlib;
import de.exlll.configlib.util.ref.R$Class;
import de.exlll.configlib.util.ref.R$RecordComponent;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
@ -45,19 +48,14 @@ final class Reflect {
constructor.setAccessible(true);
return constructor.newInstance(arguments);
} catch (NoSuchMethodException e) {
String msg = "Type '%s' doesn't have a constructor with parameters: %s."
.formatted(cls.getSimpleName(), argumentTypeNamesJoined(argumentTypes));
throw new RuntimeException(msg, e);
throw new RuntimeException("Type '" + cls.getSimpleName() + "' doesn't have a constructor with parameters: " + argumentTypeNamesJoined(argumentTypes) + ".", e);
} catch (IllegalAccessException e) {
// cannot happen because we set the constructor to be accessible.
throw new RuntimeException(e);
} catch (InstantiationException e) {
String msg = "Type '%s' is not instantiable.".formatted(cls.getSimpleName());
throw new RuntimeException(msg, e);
throw new RuntimeException("Type '" + cls.getSimpleName() + "' is not instantiable.", e);
} catch (InvocationTargetException e) {
String msg = "Constructor of type '%s' with parameters '%s' threw an exception."
.formatted(cls.getSimpleName(), argumentTypeNamesJoined(argumentTypes));
throw new RuntimeException(msg, e);
throw new RuntimeException("Constructor of type '" + cls.getSimpleName() + "' with parameters '" + argumentTypeNamesJoined(argumentTypes) + "' threw an exception.", e);
}
}
@ -79,22 +77,15 @@ final class Reflect {
constructor.setAccessible(true);
return constructor.newInstance();
} catch (NoSuchMethodException e) {
String msg = "Type '%s' doesn't have a no-args constructor."
.formatted(cls.getSimpleName());
throw new RuntimeException(msg, e);
throw new RuntimeException("Type '" + cls.getSimpleName() + "' doesn't have a no-args constructor.", e);
} catch (IllegalAccessException e) {
/* This exception should not be thrown because
* we set the constructor to be accessible. */
String msg = "No-args constructor of type '%s' not accessible."
.formatted(cls.getSimpleName());
throw new RuntimeException(msg, e);
throw new RuntimeException("No-args constructor of type '" + cls.getSimpleName() + "' not accessible.", e);
} catch (InstantiationException e) {
String msg = "Type '%s' is not instantiable.".formatted(cls.getSimpleName());
throw new RuntimeException(msg, e);
throw new RuntimeException("Type '" + cls.getSimpleName() + "' is not instantiable.", e);
} catch (InvocationTargetException e) {
String msg = "No-args constructor of type '%s' threw an exception."
.formatted(cls.getSimpleName());
throw new RuntimeException(msg, e);
throw new RuntimeException("No-args constructor of type '" + cls.getSimpleName() + "' threw an exception.", e);
}
}
@ -115,9 +106,7 @@ final class Reflect {
// cannot happen because records are instantiable
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
String msg = "The canonical constructor of record type '%s' threw an exception."
.formatted(recordType.getSimpleName());
throw new RuntimeException(msg, e);
throw new RuntimeException("The canonical constructor of record type '" + recordType.getSimpleName() + "' threw an exception.", e);
}
}
@ -135,8 +124,8 @@ final class Reflect {
}
private static <R> Class<?>[] recordParameterTypes(Class<R> recordType) {
return Arrays.stream(recordType.getRecordComponents())
.map(RecordComponent::getType)
return Arrays.stream(R$Class.of(recordType).getRecordComponents())
.map(R$RecordComponent::getType)
.toArray(Class<?>[]::new);
}
@ -158,25 +147,20 @@ final class Reflect {
return field.get(instance);
} catch (IllegalAccessException e) {
/* This exception should not be thrown because we set the field to be accessible. */
String msg = "Illegal access of field '%s' on object %s.".formatted(field, instance);
throw new RuntimeException(msg, e);
throw new RuntimeException("Illegal access of field '" + field + "' on object " + instance + ".", e);
}
}
static Object getValue(RecordComponent component, Object recordInstance) {
static Object getValue(R$RecordComponent component, Object recordInstance) {
final Method accessor = component.getAccessor();
try {
accessor.setAccessible(true);
return accessor.invoke(recordInstance);
} catch (IllegalAccessException e) {
/* Should not be thrown because we set the method to be accessible. */
String msg = "Illegal access of method '%s' on record '%s'."
.formatted(accessor, recordInstance);
throw new RuntimeException(msg, e);
throw new RuntimeException("Illegal access of method '" + accessor + "' on record '" + recordInstance + "'.", e);
} catch (InvocationTargetException e) {
String msg = "Invocation of method '%s' on record '%s' failed."
.formatted(accessor, recordInstance);
throw new RuntimeException(msg, e);
throw new RuntimeException("Invocation of method '" + accessor + "' on record '" + recordInstance + "' failed.", e);
}
}
@ -186,8 +170,7 @@ final class Reflect {
field.set(instance, value);
} catch (IllegalAccessException e) {
/* This exception should not be thrown because we set the field to be accessible. */
String msg = "Illegal access of field '%s' on object %s.".formatted(field, instance);
throw new RuntimeException(msg, e);
throw new RuntimeException("Illegal access of field '" + field + "' on object " + instance + ".", e);
}
}
@ -228,7 +211,7 @@ final class Reflect {
}
static boolean isConfigurationType(Class<?> type) {
return type.isRecord() || (type.getAnnotation(Configuration.class) != null);
return R$Class.of(type).isRecord() || (type.getAnnotation(Configuration.class) != null);
}
static boolean isIgnored(Field field) {

@ -4,15 +4,30 @@ import java.lang.reflect.AnnotatedType;
import static de.exlll.configlib.Validator.requireNonNull;
record SerializerContextImpl(
ConfigurationProperties properties,
ConfigurationElement<?> element,
AnnotatedType annotatedType
) implements SerializerContext {
SerializerContextImpl {
properties = requireNonNull(properties, "configuration properties");
element = requireNonNull(element, "configuration element");
annotatedType = requireNonNull(annotatedType, "annotated type");
class SerializerContextImpl implements SerializerContext {
private final ConfigurationProperties properties;
private final ConfigurationElement<?> element;
private final AnnotatedType annotatedType;
SerializerContextImpl(ConfigurationProperties properties, ConfigurationElement<?> element, AnnotatedType annotatedType) {
this.properties = requireNonNull(properties, "configuration properties");
this.element = requireNonNull(element, "configuration element");
this.annotatedType = requireNonNull(annotatedType, "annotated type");
}
@Override
public ConfigurationProperties properties() {
return properties;
}
@Override
public ConfigurationElement<?> element() {
return element;
}
@Override
public AnnotatedType annotatedType() {
return annotatedType;
}
}

@ -1,8 +1,10 @@
package de.exlll.configlib;
import de.exlll.configlib.Serializers.*;
import de.exlll.configlib.util.MapUtil;
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
@ -16,39 +18,41 @@ import java.time.LocalTime;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import static de.exlll.configlib.Validator.requireNonNull;
final class SerializerSelector {
private static final Map<Class<?>, Serializer<?, ?>> DEFAULT_SERIALIZERS = Map.ofEntries(
Map.entry(boolean.class, new BooleanSerializer()),
Map.entry(Boolean.class, new BooleanSerializer()),
Map.entry(byte.class, new NumberSerializer(byte.class)),
Map.entry(Byte.class, new NumberSerializer(Byte.class)),
Map.entry(short.class, new NumberSerializer(short.class)),
Map.entry(Short.class, new NumberSerializer(Short.class)),
Map.entry(int.class, new NumberSerializer(int.class)),
Map.entry(Integer.class, new NumberSerializer(Integer.class)),
Map.entry(long.class, new NumberSerializer(long.class)),
Map.entry(Long.class, new NumberSerializer(Long.class)),
Map.entry(float.class, new NumberSerializer(float.class)),
Map.entry(Float.class, new NumberSerializer(Float.class)),
Map.entry(double.class, new NumberSerializer(double.class)),
Map.entry(Double.class, new NumberSerializer(Double.class)),
Map.entry(char.class, new CharacterSerializer()),
Map.entry(Character.class, new CharacterSerializer()),
Map.entry(String.class, new StringSerializer()),
Map.entry(BigInteger.class, new BigIntegerSerializer()),
Map.entry(BigDecimal.class, new BigDecimalSerializer()),
Map.entry(LocalDate.class, new LocalDateSerializer()),
Map.entry(LocalTime.class, new LocalTimeSerializer()),
Map.entry(LocalDateTime.class, new LocalDateTimeSerializer()),
Map.entry(Instant.class, new InstantSerializer()),
Map.entry(UUID.class, new UuidSerializer()),
Map.entry(File.class, new FileSerializer()),
Map.entry(Path.class, new PathSerializer()),
Map.entry(URL.class, new UrlSerializer()),
Map.entry(URI.class, new UriSerializer())
MapUtil.entry(boolean.class, new BooleanSerializer()),
MapUtil.entry(Boolean.class, new BooleanSerializer()),
MapUtil.entry(byte.class, new NumberSerializer(byte.class)),
MapUtil.entry(Byte.class, new NumberSerializer(Byte.class)),
MapUtil.entry(short.class, new NumberSerializer(short.class)),
MapUtil.entry(Short.class, new NumberSerializer(Short.class)),
MapUtil.entry(int.class, new NumberSerializer(int.class)),
MapUtil.entry(Integer.class, new NumberSerializer(Integer.class)),
MapUtil.entry(long.class, new NumberSerializer(long.class)),
MapUtil.entry(Long.class, new NumberSerializer(Long.class)),
MapUtil.entry(float.class, new NumberSerializer(float.class)),
MapUtil.entry(Float.class, new NumberSerializer(Float.class)),
MapUtil.entry(double.class, new NumberSerializer(double.class)),
MapUtil.entry(Double.class, new NumberSerializer(Double.class)),
MapUtil.entry(char.class, new CharacterSerializer()),
MapUtil.entry(Character.class, new CharacterSerializer()),
MapUtil.entry(String.class, new StringSerializer()),
MapUtil.entry(BigInteger.class, new BigIntegerSerializer()),
MapUtil.entry(BigDecimal.class, new BigDecimalSerializer()),
MapUtil.entry(LocalDate.class, new LocalDateSerializer()),
MapUtil.entry(LocalTime.class, new LocalTimeSerializer()),
MapUtil.entry(LocalDateTime.class, new LocalDateTimeSerializer()),
MapUtil.entry(Instant.class, new InstantSerializer()),
MapUtil.entry(UUID.class, new UuidSerializer()),
MapUtil.entry(File.class, new FileSerializer()),
MapUtil.entry(Path.class, new PathSerializer()),
MapUtil.entry(URL.class, new UrlSerializer()),
MapUtil.entry(URI.class, new UriSerializer())
);
private final ConfigurationProperties properties;
/**
@ -101,18 +105,28 @@ final class SerializerSelector {
}
private Serializer<?, ?> selectCustomSerializer(AnnotatedType annotatedType) {
return findConfigurationElementSerializer(annotatedType)
.or(() -> findSerializerFactoryForType(annotatedType))
.or(() -> findSerializerForType(annotatedType))
.or(() -> findSerializerOnType(annotatedType))
.or(() -> findMetaSerializerOnType(annotatedType))
.or(() -> findSerializerByCondition(annotatedType))
.orElse(null);
Optional<Serializer<?, ?>> o = findConfigurationElementSerializer(annotatedType);
if (o.isPresent()) return o.get();
o = findSerializerFactoryForType(annotatedType);
if (o.isPresent()) return o.get();
o = findSerializerForType(annotatedType);
if (o.isPresent()) return o.get();
o = findSerializerOnType(annotatedType);
if (o.isPresent()) return o.get();
o = findMetaSerializerOnType(annotatedType);
if (o.isPresent()) return o.get();
o = findSerializerByCondition(annotatedType);
return o.orElse(null);
}
private Optional<Serializer<?, ?>> findConfigurationElementSerializer(AnnotatedType annotatedType) {
// SerializeWith annotation on configuration elements
final var annotation = element.annotation(SerializeWith.class);
final SerializeWith annotation = element.annotation(SerializeWith.class);
if ((annotation != null) && (currentNesting == annotation.nesting())) {
return Optional.of(newSerializerFromAnnotation(annotatedType, annotation));
}
@ -121,11 +135,12 @@ final class SerializerSelector {
private Optional<Serializer<?, ?>> findSerializerFactoryForType(AnnotatedType annotatedType) {
// Serializer factory registered for Type via configurations properties
if ((annotatedType.getType() instanceof Class<?> cls) &&
properties.getSerializerFactories().containsKey(cls)) {
final var context = new SerializerContextImpl(properties, element, annotatedType);
final var factory = properties.getSerializerFactories().get(cls);
final var serializer = factory.apply(context);
if ((annotatedType.getType() instanceof Class<?>) &&
properties.getSerializerFactories().containsKey((Class<?>) annotatedType.getType())) {
Class<?> cls = (Class<?>) annotatedType.getType();
final SerializerContextImpl context = new SerializerContextImpl(properties, element, annotatedType);
final Function<? super SerializerContext, ? extends Serializer<?, ?>> factory = properties.getSerializerFactories().get(cls);
final Serializer<?, ?> serializer = factory.apply(context);
if (serializer == null) {
String msg = "Serializer factories must not return null.";
throw new ConfigurationException(msg);
@ -137,8 +152,9 @@ final class SerializerSelector {
private Optional<Serializer<?, ?>> findSerializerForType(AnnotatedType annotatedType) {
// Serializer registered for Type via configurations properties
if ((annotatedType.getType() instanceof Class<?> cls) &&
properties.getSerializers().containsKey(cls)) {
if ((annotatedType.getType() instanceof Class<?>) &&
properties.getSerializers().containsKey((Class<?>) annotatedType.getType())) {
Class<?> cls = (Class<?>) annotatedType.getType();
return Optional.of(properties.getSerializers().get(cls));
}
return Optional.empty();
@ -146,9 +162,10 @@ final class SerializerSelector {
private Optional<Serializer<?, ?>> findSerializerOnType(AnnotatedType annotatedType) {
// SerializeWith annotation on type
if ((annotatedType.getType() instanceof Class<?> cls) &&
(cls.getDeclaredAnnotation(SerializeWith.class) != null)) {
final var annotation = cls.getDeclaredAnnotation(SerializeWith.class);
if ((annotatedType.getType() instanceof Class<?>) &&
(((Class<?>) annotatedType.getType()).getDeclaredAnnotation(SerializeWith.class) != null)) {
final Class<?> cls = (Class<?>) annotatedType.getType();
final SerializeWith annotation = cls.getDeclaredAnnotation(SerializeWith.class);
return Optional.of(newSerializerFromAnnotation(annotatedType, annotation));
}
return Optional.empty();
@ -156,10 +173,11 @@ final class SerializerSelector {
private Optional<Serializer<?, ?>> findMetaSerializerOnType(AnnotatedType annotatedType) {
// SerializeWith meta annotation on type
if ((annotatedType.getType() instanceof Class<?> cls)) {
for (final var meta : cls.getDeclaredAnnotations()) {
final var metaType = meta.annotationType();
final var annotation = metaType.getDeclaredAnnotation(SerializeWith.class);
if (annotatedType.getType() instanceof Class<?>) {
final Class<?> cls = (Class<?>) annotatedType.getType();
for (final Annotation meta : cls.getDeclaredAnnotations()) {
final Class<? extends Annotation> metaType = meta.annotationType();
final SerializeWith annotation = metaType.getDeclaredAnnotation(SerializeWith.class);
if (annotation != null)
return Optional.of(newSerializerFromAnnotation(annotatedType, annotation));
}
@ -169,7 +187,7 @@ final class SerializerSelector {
private Optional<Serializer<?, ?>> findSerializerByCondition(AnnotatedType annotatedType) {
// Serializer registered for condition via configurations properties
for (var entry : properties.getSerializersByCondition().entrySet()) {
for (Map.Entry<Predicate<? super Type>, Serializer<?, ?>> entry : properties.getSerializersByCondition().entrySet()) {
if (entry.getKey().test(annotatedType.getType()))
return Optional.of(entry.getValue());
}
@ -180,7 +198,7 @@ final class SerializerSelector {
AnnotatedType annotatedType,
SerializeWith annotation
) {
final var context = new SerializerContextImpl(properties, element, annotatedType);
final SerializerContextImpl context = new SerializerContextImpl(properties, element, annotatedType);
return Serializers.newCustomSerializer(annotation.serializer(), context);
}
@ -191,7 +209,7 @@ final class SerializerSelector {
if (Reflect.isEnumType(cls)) {
// The following cast won't fail because we just checked that it's an enum.
@SuppressWarnings("unchecked")
final var enumType = (Class<? extends Enum<?>>) cls;
final Class<? extends Enum<?>> enumType = (Class<? extends Enum<?>>) cls;
return new Serializers.EnumSerializer(enumType);
}
if (Reflect.isArrayType(cls))
@ -225,34 +243,34 @@ final class SerializerSelector {
} else if (elementType == double.class) {
return new PrimitiveDoubleArraySerializer();
}
var elementSerializer = selectForType(annotatedElementType);
var inputNulls = properties.inputNulls();
var outputNulls = properties.outputNulls();
Serializer<?, ?> elementSerializer = selectForType(annotatedElementType);
boolean inputNulls = properties.inputNulls();
boolean outputNulls = properties.outputNulls();
return new ArraySerializer<>(elementType, elementSerializer, outputNulls, inputNulls);
}
private Serializer<?, ?> selectForParameterizedType(AnnotatedParameterizedType annotatedType) {
// the raw type returned by Java is always a class
final var type = (ParameterizedType) annotatedType.getType();
final var rawType = (Class<?>) type.getRawType();
final var typeArgs = annotatedType.getAnnotatedActualTypeArguments();
final var inputNulls = properties.inputNulls();
final var outputNulls = properties.outputNulls();
final ParameterizedType type = (ParameterizedType) annotatedType.getType();
final Class<?> rawType = (Class<?>) type.getRawType();
final AnnotatedType[] typeArgs = annotatedType.getAnnotatedActualTypeArguments();
final boolean inputNulls = properties.inputNulls();
final boolean outputNulls = properties.outputNulls();
if (Reflect.isListType(rawType)) {
var elementSerializer = selectForType(typeArgs[0]);
Serializer<?, ?> elementSerializer = selectForType(typeArgs[0]);
return new ListSerializer<>(elementSerializer, outputNulls, inputNulls);
} else if (Reflect.isSetType(rawType)) {
var elementSerializer = selectForType(typeArgs[0]);
Serializer<?, ?> elementSerializer = selectForType(typeArgs[0]);
return properties.serializeSetsAsLists()
? new SetAsListSerializer<>(elementSerializer, outputNulls, inputNulls)
: new SetSerializer<>(elementSerializer, outputNulls, inputNulls);
} else if (Reflect.isMapType(rawType)) {
if ((typeArgs[0].getType() instanceof Class<?> cls) &&
(DEFAULT_SERIALIZERS.containsKey(cls) ||
Reflect.isEnumType(cls))) {
var keySerializer = selectForClass(typeArgs[0]);
var valSerializer = selectForType(typeArgs[1]);
if ((typeArgs[0].getType() instanceof Class<?>) &&
(DEFAULT_SERIALIZERS.containsKey((Class<?>) typeArgs[0].getType()) ||
Reflect.isEnumType((Class<?>) typeArgs[0].getType()))) {
Serializer<?, ?> keySerializer = selectForClass(typeArgs[0]);
Serializer<?, ?> valSerializer = selectForType(typeArgs[1]);
return new MapSerializer<>(keySerializer, valSerializer, outputNulls, inputNulls);
}
String msg = baseExceptionMessage(type) +
@ -266,6 +284,6 @@ final class SerializerSelector {
}
private String baseExceptionMessage(Type type) {
return "Cannot select serializer for type '%s'.\n".formatted(type);
return "Cannot select serializer for type '" + type + "'.\n";
}
}

@ -7,6 +7,7 @@ import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
@ -300,7 +301,7 @@ final class Serializers {
@Override
public Path deserialize(String element) {
return Path.of(element);
return Paths.get(element);
}
}
@ -454,10 +455,10 @@ final class Serializers {
for (final Map.Entry<S1, S2> entry : element.entrySet()) {
if (!outputNulls && isEntryNull(entry))
continue;
var s1key = entry.getKey();
var s2val = entry.getValue();
var t1key = (s1key == null) ? null : keySerializer.serialize(s1key);
var t2val = (s2val == null) ? null : valSerializer.serialize(s2val);
S1 s1key = entry.getKey();
S2 s2val = entry.getValue();
T1 t1key = (s1key == null) ? null : keySerializer.serialize(s1key);
T2 t2val = (s2val == null) ? null : valSerializer.serialize(s2val);
result.put(t1key, t2val);
}
return result;
@ -470,10 +471,10 @@ final class Serializers {
for (final Map.Entry<T1, T2> entry : element.entrySet()) {
if (!inputNulls && isEntryNull(entry))
continue;
var t1key = entry.getKey();
var t2val = entry.getValue();
var s1key = (t1key == null) ? null : keySerializer.deserialize(t1key);
var s2val = (t2val == null) ? null : valSerializer.deserialize(t2val);
T1 t1key = entry.getKey();
T2 t2val = entry.getValue();
S1 s1key = (t1key == null) ? null : keySerializer.deserialize(t1key);
S2 s2val = (t2val == null) ? null : valSerializer.deserialize(t2val);
result.put(s1key, s2val);
}
return result;
@ -515,7 +516,7 @@ final class Serializers {
final Stream<T2> stream = outputNulls
? Arrays.stream(element).map(s -> s == null ? null : serializer.serialize(s))
: Arrays.stream(element).filter(Objects::nonNull).map(serializer::serialize);
return stream.toList();
return stream.collect(Collectors.toList());
}
@Override
@ -543,7 +544,8 @@ final class Serializers {
@Override
public List<Boolean> serialize(Object element) {
final boolean[] array = (boolean[]) element;
return IntStream.range(0, array.length).mapToObj(i -> array[i]).toList();
return IntStream.range(0, array.length).mapToObj(i -> array[i])
.collect(Collectors.toList());
}
@Override
@ -564,7 +566,7 @@ final class Serializers {
final char[] array = (char[]) element;
return IntStream.range(0, array.length)
.mapToObj(i -> serializer.serialize(array[i]))
.toList();
.collect(Collectors.toList());
}
@Override
@ -587,7 +589,7 @@ final class Serializers {
final byte[] array = (byte[]) element;
return IntStream.range(0, array.length).
mapToObj(i -> serializer.serialize(array[i]))
.toList();
.collect(Collectors.toList());
}
@Override
@ -610,7 +612,7 @@ final class Serializers {
final short[] array = (short[]) element;
return IntStream.range(0, array.length)
.mapToObj(i -> serializer.serialize(array[i]))
.toList();
.collect(Collectors.toList());
}
@Override
@ -631,7 +633,7 @@ final class Serializers {
@Override
public List<Number> serialize(Object element) {
final int[] array = (int[]) element;
return Arrays.stream(array).mapToObj(serializer::serialize).toList();
return Arrays.stream(array).mapToObj(serializer::serialize).collect(Collectors.toList());
}
@Override
@ -652,7 +654,7 @@ final class Serializers {
@Override
public List<Number> serialize(Object element) {
final long[] array = (long[]) element;
return Arrays.stream(array).mapToObj(serializer::serialize).toList();
return Arrays.stream(array).mapToObj(serializer::serialize).collect(Collectors.toList());
}
@Override
@ -675,7 +677,7 @@ final class Serializers {
final float[] array = (float[]) element;
return IntStream.range(0, array.length)
.mapToObj(i -> serializer.serialize(array[i]))
.toList();
.collect(Collectors.toList());
}
@Override
@ -696,7 +698,7 @@ final class Serializers {
@Override
public List<Number> serialize(Object element) {
final double[] array = (double[]) element;
return Arrays.stream(array).mapToObj(serializer::serialize).toList();
return Arrays.stream(array).mapToObj(serializer::serialize).collect(Collectors.toList());
}
@Override

@ -2,11 +2,13 @@ package de.exlll.configlib;
import de.exlll.configlib.ConfigurationElements.FieldElement;
import de.exlll.configlib.ConfigurationElements.RecordComponentElement;
import de.exlll.configlib.util.PredicateUtil;
import de.exlll.configlib.util.ref.R$Class;
import de.exlll.configlib.util.ref.R$RecordComponent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
@ -17,9 +19,8 @@ import java.util.stream.Collectors;
import static de.exlll.configlib.Validator.requireNonNull;
sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
implements Serializer<T, Map<?, ?>>
permits ConfigurationSerializer, RecordSerializer {
public abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
implements Serializer<T, Map<?, ?>> {
protected final Class<T> type;
protected final ConfigurationProperties properties;
protected final NameFormatter formatter;
@ -39,13 +40,13 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
Class<T> type,
ConfigurationProperties properties
) {
return type.isRecord()
return R$Class.of(type).isRecord()
? new RecordSerializer<>(type, properties)
: new ConfigurationSerializer<>(type, properties);
}
Map<String, Serializer<?, ?>> buildSerializerMap() {
final var selector = new SerializerSelector(properties);
final SerializerSelector selector = new SerializerSelector(properties);
try {
return elements().stream().collect(Collectors.toMap(
ConfigurationElement::name,
@ -79,20 +80,14 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
// This cast can lead to a ClassCastException if an element of type X is
// serialized by a custom serializer that expects a different type Y.
@SuppressWarnings("unchecked")
final var serializer = (Serializer<Object, Object>) serializers.get(element.name());
final Serializer<Object, Object> serializer = (Serializer<Object, Object>) serializers.get(element.name());
try {
return (value != null) ? serializer.serialize(value) : null;
} catch (ClassCastException e) {
String msg = ("Serialization of value '%s' for element '%s' of type '%s' failed.\n" +
"The type of the object to be serialized does not match the type " +
"the custom serializer of type '%s' expects.")
.formatted(
value,
element.element(),
element.declaringType(),
serializer.getClass()
);
throw new ConfigurationException(msg, e);
throw new ConfigurationException(
"Serialization of value '" + value + "' for element '" + element.element() + "' of type '" + element.declaringType() + "' failed.\n" +
"The type of the object to be serialized does not match the type " +
"the custom serializer of type " + serializer.getClass() + "' expects.", e);
}
}
@ -100,17 +95,16 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
// This unchecked cast leads to an exception if the type of the object which
// is deserialized is not a subtype of the type the deserializer expects.
@SuppressWarnings("unchecked")
final var serializer = (Serializer<Object, Object>)
serializers.get(element.name());
final Serializer<Object, Object> serializer = (Serializer<Object, Object>) serializers.get(element.name());
final Object deserialized;
try {
deserialized = serializer.deserialize(value);
} catch (ClassCastException e) {
String msg = baseDeserializeExceptionMessage(element, value) + "\n" +
"The type of the object to be deserialized does not " +
"match the type the deserializer expects.";
throw new ConfigurationException(msg, e);
throw new ConfigurationException(
baseDeserializeExceptionMessage(element, value) + "\n" +
"The type of the object to be deserialized does not " +
"match the type the deserializer expects.", e);
} catch (RuntimeException e) {
String msg = baseDeserializeExceptionMessage(element, value);
throw new ConfigurationException(msg, e);
@ -121,12 +115,12 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
protected final Object[] deserializeConfigurationElements(
Map<?, ?> serializedConfiguration
) {
final var elements = elements();
final var result = new Object[elements.size()];
final List<E> elements = elements();
final Object[] result = new Object[elements.size()];
for (int i = 0, size = elements.size(); i < size; i++) {
final var element = elements.get(i);
final var formattedName = formatter.format(element.name());
final E element = elements.get(i);
final String formattedName = formatter.format(element.name());
if (!serializedConfiguration.containsKey(formattedName)) {
final Object defaultValue = getDefaultValueOf(element);
@ -134,7 +128,7 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
continue;
}
final var serializedValue = serializedConfiguration.get(formattedName);
final Object serializedValue = serializedConfiguration.get(formattedName);
if ((serializedValue == null) && properties.inputNulls()) {
// This statement (and hence the whole block) could be removed,
@ -159,12 +153,12 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
Object result = deserializeValue;
boolean postProcessed = false;
for (final var entry : properties.getPostProcessorsByCondition().entrySet()) {
final var condition = entry.getKey();
for (final Map.Entry<Predicate<? super ConfigurationElement<?>>, UnaryOperator<?>> entry : properties.getPostProcessorsByCondition().entrySet()) {
final Predicate<? super ConfigurationElement<?>> condition = entry.getKey();
if (!condition.test(element)) continue;
final var postProcessor = entry.getValue();
final UnaryOperator<?> postProcessor = entry.getValue();
result = tryApplyPostProcessorForElement(element, postProcessor, result);
postProcessed = true;
}
@ -186,14 +180,13 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
// This cast can lead to a ClassCastException if an element of type X is
// annotated with a post-processor that takes values of some other type Y.
@SuppressWarnings("unchecked")
final var pp = (UnaryOperator<Object>) postProcessor;
final UnaryOperator<Object> pp = (UnaryOperator<Object>) postProcessor;
return pp.apply(value);
} catch (ClassCastException e) {
String msg = ("Deserialization of value '%s' for element '%s' of type '%s' failed.\n" +
"The type of the object to be deserialized does not match the type " +
"post-processor '%s' expects.")
.formatted(value, element.element(), element.declaringType(), postProcessor);
throw new ConfigurationException(msg, e);
throw new ConfigurationException(
"Deserialization of value '" + value + "' for element '" + element.element() + "' of type '" + element.declaringType() + "' failed.\n" +
"The type of the object to be deserialized does not match the type " +
"post-processor '" + postProcessor + "' expects.", e);
}
}
@ -202,22 +195,22 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
) {
if (!element.type().isPrimitive()) return;
if (element instanceof RecordComponentElement recordComponentElement) {
final RecordComponent component = recordComponentElement.element();
String msg = """
Post-processors must not return null for primitive record \
components but some post-processor of component '%s' of \
record type '%s' does.\
""".formatted(component, component.getDeclaringRecord());
throw new ConfigurationException(msg);
if (element instanceof RecordComponentElement) {
RecordComponentElement recordComponentElement = (RecordComponentElement) element;
final R$RecordComponent component = recordComponentElement.element();
throw new ConfigurationException(
"Post-processors must not return null for primitive record " +
"components but some post-processor of component '" + component + "' of " +
"record type '" + component.getDeclaringRecord() + "' does.");
}
if (element instanceof FieldElement fieldElement) {
if (element instanceof FieldElement) {
FieldElement fieldElement = (FieldElement) element;
final Field field = fieldElement.element();
String msg = ("Post-processors must not return null for primitive fields " +
"but some post-processor of field '%s' does.")
.formatted(field);
throw new ConfigurationException(msg);
throw new ConfigurationException(
"Post-processors must not return null for primitive fields " +
"but some post-processor of field '" + field + "' does."
);
}
throw new ConfigurationException("Unhandled ConfigurationElement: " + element);
@ -226,20 +219,22 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
private static void requireNonPrimitiveType(ConfigurationElement<?> element) {
if (!element.type().isPrimitive()) return;
if (element instanceof RecordComponentElement recordComponentElement) {
final RecordComponent component = recordComponentElement.element();
String msg = ("Cannot set component '%s' of record type '%s' to null. " +
"Primitive types cannot be assigned null values.")
.formatted(component, component.getDeclaringRecord());
throw new ConfigurationException(msg);
if (element instanceof RecordComponentElement) {
RecordComponentElement recordComponentElement = (RecordComponentElement) element;
final R$RecordComponent component = recordComponentElement.element();
throw new ConfigurationException(
"Cannot set component '" + component + "' of record type '" + component.getDeclaringRecord() + "' to null. " +
"Primitive types cannot be assigned null values."
);
}
if (element instanceof FieldElement fieldElement) {
if (element instanceof FieldElement) {
FieldElement fieldElement = (FieldElement) element;
final Field field = fieldElement.element();
String msg = ("Cannot set field '%s' to null value. " +
"Primitive types cannot be assigned null.")
.formatted(field);
throw new ConfigurationException(msg);
throw new ConfigurationException(
"Cannot set field '" + field + "' to null value. " +
"Primitive types cannot be assigned null."
);
}
throw new ConfigurationException("Unhandled ConfigurationElement: " + element);
@ -248,45 +243,45 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
final UnaryOperator<T> createPostProcessorFromAnnotatedMethod() {
final List<Method> list = Arrays.stream(type.getDeclaredMethods())
.filter(method -> method.isAnnotationPresent(PostProcess.class))
.filter(Predicate.not(Method::isSynthetic))
.filter(Predicate.not(this::isAccessorMethod))
.toList();
.filter(PredicateUtil.not(Method::isSynthetic))
.filter(PredicateUtil.not(this::isAccessorMethod))
.collect(Collectors.toList());
if (list.isEmpty())
return UnaryOperator.identity();
if (list.size() > 1) {
String methodNames = String.join("\n ", list.stream().map(Method::toString).toList());
String msg = "Configuration types must not define more than one method for " +
"post-processing but type '%s' defines %d:\n %s"
.formatted(type, list.size(), methodNames);
throw new ConfigurationException(msg);
String methodNames = String.join("\n ", list.stream().map(Method::toString).collect(Collectors.toList()));
throw new ConfigurationException(
"Configuration types must not define more than one method for " +
"post-processing but type '" + type + "' defines " + list.size() + ":\n " + methodNames
);
}
final Method method = list.get(0);
final int modifiers = method.getModifiers();
if (Modifier.isAbstract(modifiers) || Modifier.isStatic(modifiers)) {
String msg = "Post-processing methods must be neither abstract nor static, " +
"but post-processing method '%s' of type '%s' is."
.formatted(method, type);
throw new ConfigurationException(msg);
throw new ConfigurationException(
"Post-processing methods must be neither abstract nor static, " +
"but post-processing method '" + method + "' of type '" + type + "' is."
);
}
final int parameterCount = method.getParameterCount();
if (parameterCount > 0) {
String msg = "Post-processing methods must not define any parameters but " +
"post-processing method '%s' of type '%s' defines %d."
.formatted(method, type, parameterCount);
throw new ConfigurationException(msg);
throw new ConfigurationException(
"Post-processing methods must not define any parameters but " +
"post-processing method '" + method + "' of type '" + type + "' defines " + parameterCount + "."
);
}
final Class<?> returnType = method.getReturnType();
if ((returnType != void.class) && (returnType != type)) {
String msg = "The return type of post-processing methods must either be 'void' or " +
"the same type as the configuration type in which the post-processing " +
"method is defined. The return type of the post-processing method of " +
"type '%s' is neither 'void' nor '%s'."
.formatted(type, type.getSimpleName());
throw new ConfigurationException(msg);
throw new ConfigurationException(
"The return type of post-processing methods must either be 'void' or " +
"the same type as the configuration type in which the post-processing " +
"method is defined. The return type of the post-processing method of " +
"type '" + type + "' is neither 'void' nor '" + type.getSimpleName() + "'."
);
}
return object -> {
@ -303,11 +298,11 @@ sealed abstract class TypeSerializer<T, E extends ConfigurationElement<?>>
}
final boolean isAccessorMethod(Method method) {
if (!type.isRecord()) return false;
if (!R$Class.of(type).isRecord()) return false;
if (!method.getDeclaringClass().equals(type)) return false;
if (method.getParameterCount() > 0) return false;
return Arrays.stream(type.getRecordComponents())
.map(RecordComponent::getName)
return Arrays.stream(R$Class.of(type).getRecordComponents())
.map(R$RecordComponent::getName)
.anyMatch(s -> s.equals(method.getName()));
}

@ -1,5 +1,7 @@
package de.exlll.configlib;
import de.exlll.configlib.util.ref.R$Class;
import java.util.Objects;
final class Validator {
@ -29,7 +31,7 @@ final class Validator {
static <T> Class<T> requireRecord(Class<T> cls) {
requireNonNull(cls, "type");
if (!cls.isRecord()) {
if (!R$Class.of(cls).isRecord()) {
String msg = "Class '" + cls.getSimpleName() + "' must be a record.";
throw new ConfigurationException(msg);
}

@ -0,0 +1,14 @@
package de.exlll.configlib.util;
import java.util.AbstractMap;
import java.util.Map;
public final class MapUtil {
private MapUtil() {
throw new UnsupportedOperationException("This class cannot be instantiated");
}
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
return new AbstractMap.SimpleImmutableEntry<>(key, value);
}
}

@ -0,0 +1,15 @@
package de.exlll.configlib.util;
import java.util.Objects;
import java.util.function.Predicate;
public final class PredicateUtil {
private PredicateUtil() {
throw new UnsupportedOperationException("This class cannot be instantiated");
}
public static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>) target.negate();
}
}

@ -0,0 +1,85 @@
package de.exlll.configlib.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public final class RefUtil {
private RefUtil() {
throw new UnsupportedOperationException("This class cannot be instantiated");
}
private static final MethodHandles.Lookup lookup = MethodHandles.lookup();
public static MethodHandle bootstrapStaticMethod(String methodDesc) {
String[] descParts = splitMethodDesc(methodDesc);
Class<?> owner;
try {
owner = Class.forName(descParts[0].replace('/', '.'));
} catch (ClassNotFoundException e) {
return null;
}
MethodType methodType;
try {
methodType = MethodType.fromMethodDescriptorString(descParts[2], owner.getClassLoader());
} catch (TypeNotPresentException e) {
return null;
}
try {
return lookup.findStatic(owner, descParts[1], methodType);
} catch (NoSuchMethodException e) {
return null;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static MethodHandle bootstrapVirtualMethod(String methodDesc) {
String[] descParts = splitMethodDesc(methodDesc);
Class<?> owner;
try {
owner = Class.forName(descParts[0].replace('/', '.'));
} catch (ClassNotFoundException e) {
return null;
}
MethodType methodType;
try {
methodType = MethodType.fromMethodDescriptorString(descParts[2], owner.getClassLoader());
} catch (TypeNotPresentException e) {
return null;
}
try {
return lookup.findVirtual(owner, descParts[1], methodType);
} catch (NoSuchMethodException e) {
return null;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private static String[] splitMethodDesc(String methodDesc) {
if (methodDesc.length() < 5) { // L;()V
throw new IllegalArgumentException("Invalid method descriptor: " + methodDesc);
}
int descSplit = methodDesc.indexOf(';');
if (descSplit == -1) {
throw new IllegalArgumentException("Invalid method descriptor: " + methodDesc);
}
int argsStart = methodDesc.indexOf('(');
if (argsStart == -1) {
throw new IllegalArgumentException("Invalid method descriptor: " + methodDesc);
}
return new String[] {
methodDesc.substring(1, descSplit), // java/lang/Object
methodDesc.substring(descSplit + 1, argsStart), // method name
methodDesc.substring(argsStart) // (Ljava/lang/Object;)V
};
}
public static <T extends Throwable> RuntimeException sneakyThrow(Throwable t) throws T {
throw (T) t;
}
}

@ -0,0 +1,64 @@
package de.exlll.configlib.util;
import java.util.Objects;
public final class StringUtil {
private StringUtil() {
throw new UnsupportedOperationException("This class cannot be instantiated");
}
/**
* Returns a string consisting of a specific number of concatenated copies of an input string. For
* example, {@code repeat("hey", 3)} returns the string {@code "heyheyhey"}.
*
* <p><b>Java 11+ users:</b> use {@code string.repeat(count)} instead.
*
* @param string any non-null string
* @param count the number of times to repeat it; a nonnegative integer
* @return a string containing {@code string} repeated {@code count} times (the empty string if
* {@code count} is zero)
* @throws IllegalArgumentException if {@code count} is negative
*/
public static String repeat(String string, int count) {
if (string == null) {
throw new IllegalArgumentException("string cannot be null");
}
if (count <= 1) {
if (count < 0) {
throw new IllegalArgumentException("invalid count: " + count);
}
return (count == 0) ? "" : string;
}
// IF YOU MODIFY THE CODE HERE, you must update StringsRepeatBenchmark
final int len = string.length();
final long longSize = (long) len * (long) count;
final int size = (int) longSize;
if (size != longSize) {
throw new ArrayIndexOutOfBoundsException("Required array size too large: " + longSize);
}
final char[] array = new char[size];
string.getChars(0, len, array, 0);
int n;
for (n = len; n < size - n; n <<= 1) {
System.arraycopy(array, 0, array, n, n);
}
System.arraycopy(array, 0, array, n, size - n);
return new String(array);
}
public static boolean isBlank(String $this) {
final int strLen = $this.length();
if (strLen == 0) {
return true;
}
for (int i = 0; i < strLen; i++) {
if (!Character.isWhitespace($this.charAt(i))) {
return false;
}
}
return true;
}
}

@ -0,0 +1,54 @@
package de.exlll.configlib.util.ref;
import de.exlll.configlib.util.RefUtil;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Array;
public final class R$Class {
private static final MethodHandle v$isRecord = RefUtil.bootstrapVirtualMethod("Ljava/lang/Class;isRecord()Z");
private static final MethodHandle v$getRecordComponents = RefUtil.bootstrapVirtualMethod("Ljava/lang/Class;getRecordComponents()[Ljava/lang/reflect/RecordComponent;");
private final Class<?> instance;
private R$Class(Class<?> instance) {
this.instance = instance;
}
public static R$Class of(Class<?> instance) {
return new R$Class(instance);
}
public Class<?> instance() {
return instance;
}
public boolean isRecord() {
if (v$isRecord == null) {
return false;
}
try {
return (boolean) v$isRecord.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
}
public R$RecordComponent[] getRecordComponents() {
if (v$getRecordComponents == null) {
throw new UnsupportedOperationException("Method java.lang.Class#getRecordComponents() not found");
}
Object arr;
try {
arr = v$getRecordComponents.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
int arrLength = Array.getLength(arr);
R$RecordComponent[] result = new R$RecordComponent[arrLength];
for (int i = 0; i < arrLength; i++) {
result[i] = R$RecordComponent.of(Array.get(arr, i));
}
return result;
}
}

@ -0,0 +1,122 @@
package de.exlll.configlib.util.ref;
import de.exlll.configlib.util.RefUtil;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
public final class R$RecordComponent implements AnnotatedElement {
private static final MethodHandle v$getName = RefUtil.bootstrapVirtualMethod("Ljava/lang/reflect/RecordComponent;getName()Ljava/lang/String;");
private static final MethodHandle v$getType = RefUtil.bootstrapVirtualMethod("Ljava/lang/reflect/RecordComponent;getType()Ljava/lang/Class;");
private static final MethodHandle v$getAnnotatedType = RefUtil.bootstrapVirtualMethod("Ljava/lang/reflect/RecordComponent;getAnnotatedType()Ljava/lang/reflect/AnnotatedType;");
private static final MethodHandle v$getDeclaringRecord = RefUtil.bootstrapVirtualMethod("Ljava/lang/reflect/RecordComponent;getDeclaringRecord()Ljava/lang/Class;");
private static final MethodHandle v$getAccessor = RefUtil.bootstrapVirtualMethod("Ljava/lang/reflect/RecordComponent;getAccessor()Ljava/lang/reflect/Method;");
private final Object instance;
private R$RecordComponent(Object instance) {
this.instance = instance;
}
public Object instance() {
return instance;
}
public static R$RecordComponent of(Object instance) {
return new R$RecordComponent(instance);
}
public String getName() {
if (v$getName == null) {
throw new UnsupportedOperationException("Method java.lang.reflect.RecordComponent#getName() not found");
}
try {
return (String) v$getName.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
}
public Class<?> getType() {
if (v$getType == null) {
throw new UnsupportedOperationException("Method java.lang.reflect.RecordComponent#getType() not found");
}
try {
return (Class<?>) v$getType.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
}
public AnnotatedType getAnnotatedType() {
if (v$getAnnotatedType == null) {
throw new UnsupportedOperationException("Method java.lang.reflect.RecordComponent#getAnnotatedType() not found");
}
try {
return (AnnotatedType) v$getAnnotatedType.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
}
public Class<?> getDeclaringRecord() {
if (v$getDeclaringRecord == null) {
throw new UnsupportedOperationException("Method java.lang.reflect.RecordComponent#getDeclaringRecord() not found");
}
try {
return (Class<?>) v$getDeclaringRecord.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
}
public Method getAccessor() {
if (v$getAccessor == null) {
throw new UnsupportedOperationException("Method java.lang.reflect.RecordComponent#getAccessor() not found");
}
try {
return (Method) v$getAccessor.invoke(instance);
} catch (Throwable e) {
throw RefUtil.sneakyThrow(e);
}
}
// delegate AnnotatedElement
@Override
public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
return ((AnnotatedElement) instance).getAnnotation(annotationClass);
}
@Override
public Annotation[] getAnnotations() {
return ((AnnotatedElement) instance).getAnnotations();
}
@Override
public Annotation[] getDeclaredAnnotations() {
return ((AnnotatedElement) instance).getDeclaredAnnotations();
}
@Override
public String toString() {
return instance.toString();
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof R$RecordComponent)) return false;
R$RecordComponent that = (R$RecordComponent) o;
return instance.equals(that.instance);
}
@Override
public int hashCode() {
return instance.hashCode();
}
}

@ -4,5 +4,5 @@ plugins {
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT")
compileOnly("com.destroystokyo.paper:paper-api:1.16.5-R0.1-SNAPSHOT")
}

@ -22,8 +22,8 @@ public final class ConfigLib extends JavaPlugin {
return YamlConfigurationProperties
.newBuilder()
.addSerializerByCondition(
type -> type instanceof Class<?> cls &&
ConfigurationSerializable.class.isAssignableFrom(cls),
type -> type instanceof Class<?> &&
ConfigurationSerializable.class.isAssignableFrom((Class<?>) type),
BukkitConfigurationSerializableSerializer.DEFAULT
)
.build();

@ -3,6 +3,11 @@ plugins {
`plugins-config`
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
dependencies {
compileOnly("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")
annotationProcessor("com.velocitypowered:velocity-api:3.2.0-SNAPSHOT")

@ -10,12 +10,14 @@ import org.snakeyaml.engine.v2.nodes.Node;
import org.snakeyaml.engine.v2.nodes.Tag;
import org.snakeyaml.engine.v2.representer.StandardRepresenter;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Queue;
import static de.exlll.configlib.Validator.requireNonNull;
@ -55,9 +57,9 @@ public final class YamlConfigurationStore<T> implements
public void write(T configuration, OutputStream outputStream) {
requireNonNull(configuration, "configuration");
requireNonNull(outputStream, "output stream");
var extractedCommentNodes = extractor.extractCommentNodes(configuration);
var yamlFileWriter = new YamlWriter(outputStream, properties);
var dumpedYaml = tryDump(configuration);
Queue<CommentNode> extractedCommentNodes = extractor.extractCommentNodes(configuration);
YamlWriter yamlFileWriter = new YamlWriter(outputStream, properties);
String dumpedYaml = tryDump(configuration);
yamlFileWriter.writeYaml(dumpedYaml, extractedCommentNodes);
}
@ -66,9 +68,9 @@ public final class YamlConfigurationStore<T> implements
requireNonNull(configuration, "configuration");
requireNonNull(configurationFile, "configuration file");
tryCreateParentDirectories(configurationFile);
var extractedCommentNodes = extractor.extractCommentNodes(configuration);
var yamlFileWriter = new YamlWriter(configurationFile, properties);
var dumpedYaml = tryDump(configuration);
Queue<CommentNode> extractedCommentNodes = extractor.extractCommentNodes(configuration);
YamlWriter yamlFileWriter = new YamlWriter(configurationFile, properties);
String dumpedYaml = tryDump(configuration);
yamlFileWriter.writeYaml(dumpedYaml, extractedCommentNodes);
}
@ -98,8 +100,8 @@ public final class YamlConfigurationStore<T> implements
public T read(InputStream inputStream) {
requireNonNull(inputStream, "input stream");
try {
var yaml = YAML_LOADER.loadFromInputStream(inputStream);
var conf = requireYamlMapForRead(yaml);
Object yaml = YAML_LOADER.loadFromInputStream(inputStream);
Map<?, ?> conf = requireYamlMapForRead(yaml);
return serializer.deserialize(conf);
} catch (YamlEngineException e) {
String msg = "The input stream does not contain valid YAML.";
@ -113,26 +115,25 @@ public final class YamlConfigurationStore<T> implements
throw new ConfigurationException(msg);
}
if (!(yaml instanceof Map<?, ?> map)) {
if (!(yaml instanceof Map<?, ?>)) {
String msg = "The contents of the input stream do not represent a configuration. " +
"A valid configuration contains a YAML map but instead a " +
"'" + yaml.getClass() + "' was found.";
throw new ConfigurationException(msg);
}
return map;
return (Map<?, ?>) yaml;
}
@Override
public T load(Path configurationFile) {
requireNonNull(configurationFile, "configuration file");
try (var reader = Files.newBufferedReader(configurationFile, properties.getCharset())) {
var yaml = YAML_LOADER.loadFromReader(reader);
var conf = requireYamlMapForLoad(yaml, configurationFile);
try (BufferedReader reader = Files.newBufferedReader(configurationFile, properties.getCharset())) {
Object yaml = YAML_LOADER.loadFromReader(reader);
Map<?, ?> conf = requireYamlMapForLoad(yaml, configurationFile);
return serializer.deserialize(conf);
} catch (YamlEngineException e) {
String msg = "The configuration file at %s does not contain valid YAML.";
throw new ConfigurationException(msg.formatted(configurationFile), e);
throw new ConfigurationException("The configuration file at " + configurationFile + " does not contain valid YAML.", e);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -140,18 +141,17 @@ public final class YamlConfigurationStore<T> implements
private Map<?, ?> requireYamlMapForLoad(Object yaml, Path configurationFile) {
if (yaml == null) {
String msg = "The configuration file at %s is empty or only contains null.";
throw new ConfigurationException(msg.formatted(configurationFile));
throw new ConfigurationException("The configuration file at " + configurationFile + " is empty or only contains null.");
}
if (!(yaml instanceof Map<?, ?> map)) {
String msg = "The contents of the YAML file at %s do not represent a configuration. " +
"A valid configuration file contains a YAML map but instead a " +
"'" + yaml.getClass() + "' was found.";
throw new ConfigurationException(msg.formatted(configurationFile));
if (!(yaml instanceof Map<?, ?>)) {
throw new ConfigurationException(
"The contents of the YAML file at " + configurationFile + " do not represent a configuration. " +
"A valid configuration file contains a YAML map but instead a " +
"'" + yaml.getClass() + "' was found.");
}
return map;
return (Map<?, ?>) yaml;
}
@Override

@ -27,7 +27,7 @@ public final class YamlConfigurations {
* @see YamlConfigurationStore#load(Path)
*/
public static <T> T load(Path configurationFile, Class<T> configurationType) {
final var properties = YamlConfigurationProperties.newBuilder().build();
final YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
return load(configurationFile, configurationType, properties);
}
@ -52,7 +52,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
) {
final var builder = YamlConfigurationProperties.newBuilder();
final YamlConfigurationProperties.Builder<?> builder = YamlConfigurationProperties.newBuilder();
propertiesConfigurer.accept(builder);
return load(configurationFile, configurationType, builder.build());
}
@ -77,7 +77,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
YamlConfigurationProperties properties
) {
final var store = new YamlConfigurationStore<>(configurationType, properties);
final YamlConfigurationStore<T> store = new YamlConfigurationStore<>(configurationType, properties);
return store.load(configurationFile);
}
@ -95,7 +95,7 @@ public final class YamlConfigurations {
* @see YamlConfigurationStore#read(InputStream)
*/
public static <T> T read(InputStream inputStream, Class<T> configurationType) {
final var properties = YamlConfigurationProperties.newBuilder().build();
final YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
return read(inputStream, configurationType, properties);
}
@ -119,7 +119,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
) {
final var builder = YamlConfigurationProperties.newBuilder();
final YamlConfigurationProperties.Builder<?> builder = YamlConfigurationProperties.newBuilder();
propertiesConfigurer.accept(builder);
return read(inputStream, configurationType, builder.build());
}
@ -143,7 +143,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
YamlConfigurationProperties properties
) {
final var store = new YamlConfigurationStore<>(configurationType, properties);
final YamlConfigurationStore<T> store = new YamlConfigurationStore<>(configurationType, properties);
return store.read(inputStream);
}
@ -163,7 +163,7 @@ public final class YamlConfigurations {
* @see YamlConfigurationStore#update(Path)
*/
public static <T> T update(Path configurationFile, Class<T> configurationType) {
final var properties = YamlConfigurationProperties.newBuilder().build();
final YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
return update(configurationFile, configurationType, properties);
}
@ -189,7 +189,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
) {
final var builder = YamlConfigurationProperties.newBuilder();
final YamlConfigurationProperties.Builder<?> builder = YamlConfigurationProperties.newBuilder();
propertiesConfigurer.accept(builder);
return update(configurationFile, configurationType, builder.build());
}
@ -215,7 +215,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
YamlConfigurationProperties properties
) {
final var store = new YamlConfigurationStore<>(configurationType, properties);
final YamlConfigurationStore<T> store = new YamlConfigurationStore<>(configurationType, properties);
return store.update(configurationFile);
}
@ -238,7 +238,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
T configuration
) {
final var properties = YamlConfigurationProperties.newBuilder().build();
final YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
save(configurationFile, configurationType, configuration, properties);
}
@ -264,7 +264,7 @@ public final class YamlConfigurations {
T configuration,
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
) {
final var builder = YamlConfigurationProperties.newBuilder();
final YamlConfigurationProperties.Builder<?> builder = YamlConfigurationProperties.newBuilder();
propertiesConfigurer.accept(builder);
save(configurationFile, configurationType, configuration, builder.build());
}
@ -290,7 +290,7 @@ public final class YamlConfigurations {
T configuration,
YamlConfigurationProperties properties
) {
final var store = new YamlConfigurationStore<>(configurationType, properties);
final YamlConfigurationStore<T> store = new YamlConfigurationStore<>(configurationType, properties);
store.save(configuration, configurationFile);
}
@ -313,7 +313,7 @@ public final class YamlConfigurations {
Class<T> configurationType,
T configuration
) {
final var properties = YamlConfigurationProperties.newBuilder().build();
final YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
write(outputStream, configurationType, configuration, properties);
}
@ -339,7 +339,7 @@ public final class YamlConfigurations {
T configuration,
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
) {
final var builder = YamlConfigurationProperties.newBuilder();
final YamlConfigurationProperties.Builder<?> builder = YamlConfigurationProperties.newBuilder();
propertiesConfigurer.accept(builder);
write(outputStream, configurationType, configuration, builder.build());
}
@ -365,7 +365,7 @@ public final class YamlConfigurations {
T configuration,
YamlConfigurationProperties properties
) {
final var store = new YamlConfigurationStore<>(configurationType, properties);
final YamlConfigurationStore<T> store = new YamlConfigurationStore<>(configurationType, properties);
store.write(configuration, outputStream);
}
}

@ -107,8 +107,8 @@ final class YamlWriter {
* of a child. That order ultimately represents the order in which the
* YAML file is structured.
*/
var node = nodes.poll();
var currentIndentLevel = 0;
CommentNode node = nodes.poll();
int currentIndentLevel = 0;
for (final String line : yaml.split("\n")) {
if (node == null) {
@ -116,16 +116,16 @@ final class YamlWriter {
continue;
}
final var elementNames = node.elementNames();
final var indent = " ".repeat(currentIndentLevel);
final List<String> elementNames = node.elementNames();
final String indent = " ".repeat(currentIndentLevel);
final var lineStart = indent + elementNames.get(currentIndentLevel) + ":";
final String lineStart = indent + elementNames.get(currentIndentLevel) + ":";
if (!line.startsWith(lineStart)) {
writeLine(line);
continue;
}
final var commentIndentLevel = elementNames.size() - 1;
final int commentIndentLevel = elementNames.size() - 1;
if (currentIndentLevel++ == commentIndentLevel) {
writeComments(node.comments(), commentIndentLevel);
if ((node = nodes.poll()) != null) {

Loading…
Cancel
Save