Add support for Record serialization

dev
Exlll 2 years ago
parent 31748db01b
commit 7e79597cdd

@ -21,7 +21,7 @@ the [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial) page on the wik
* Support for enums and POJOs (+ inheritance!)
* Support for Bukkit's `ConfigurationSerializable` types (e.g. `ItemStack`)
* Option to exclude fields from being converted
* Option to format field names before conversion
* Option to format field and component names before conversion
* Option to customize null handling
* Option to customize serialization by providing your own serializers
* Option to add headers and footers to configuration files
@ -395,15 +395,31 @@ Fields that are `final`, `static`, `transient` or annotated with `@Ignore` are n
nor updated during deserialization. You can filter out additional fields by providing an instance of
`FieldFilter` to the configuration properties.
### Handling of `null` values
### Handling of missing and `null` values
#### Missing values
When a configuration file is read, values that correspond to a field of a configuration type or to a
component of a record type might be missing.
That can happen, for example, when somebody deleted that field from the configuration file, when the
definition of a configuration or record type is changed, or when the `NameFormatter` that was used
to create that file is changed.
In such cases, fields of configuration types keep the default value you assigned to them and record
components are initialized with the default value of their corresponding type.
#### `null` values
Configuration properties let you configure how `null` values are handled when serializing and
deserializing a configuration:
deserializing a configuration or record type:
* By setting `outputNulls` to false, fields and collection elements that are null are not output.
Any comments that belong to such fields are also not written.
* By setting `inputNulls` to false, fields and collection elements that are null are not input. That
means that fields will keep their default values.
* By setting `outputNulls` to false, class fields, record components, and collection elements that
are null are not output. Any comments that belong to such fields are also not written.
* By setting `inputNulls` to false, null values read from the configuration file are treated as
missing and are, therefore, handled as described in the section above.
* By setting `inputNulls` to true, null values read from the configuration file override the
corresponding default values of a configuration type with null or set the component value of a
record type to null. If the field or component type is primitive, an exception is thrown.
The following code forbids null values to be output but allows null values to be input. By default,
both are forbidden which makes the call to `outputNulls` in this case redundant.
@ -415,18 +431,18 @@ YamlConfigurationProperties.newBuilder()
.build();
```
### Field formatting
### Field and component name formatting
You can define how fields are formatted by configuring the configuration properties with a custom
formatter. Field formatters are implementations of the `FieldFormatter` interface. You can implement
this interface yourself or use one of the several formatters this library provides. These
pre-defined formatters can be found in the `FieldFormatters` class.
You can define how fields and component names are formatted by configuring the configuration
properties with a custom formatter. Formatters are implementations of the `NameFormatter`
interface. You can implement this interface yourself or use one of the several formatters this
library provides. These pre-defined formatters can be found in the `NameFormatters` class.
The following code formats fields using the `IDENTITY` formatter (which is the default).
```java
YamlConfigurationProperties.newBuilder()
.setFieldFormatter(FieldFormatters.IDENTITY)
.setNameFormatter(NameFormatters.IDENTITY)
.build();
```

@ -5,12 +5,12 @@ import java.util.*;
final class CommentNodeExtractor {
private final FieldFilter fieldFilter;
private final FieldFormatter fieldFormatter;
private final NameFormatter nameFormatter;
private final boolean outputNull;
CommentNodeExtractor(ConfigurationProperties properties) {
this.fieldFilter = Validator.requireNonNull(properties.getFieldFilter(), "field filter");
this.fieldFormatter = Validator.requireNonNull(properties.getFieldFormatter(), "field formatter");
this.nameFormatter = Validator.requireNonNull(properties.getNameFormatter(), "name formatter");
this.outputNull = properties.outputNulls();
}
@ -52,7 +52,7 @@ final class CommentNodeExtractor {
continue;
stateStack.addLast(new State(state.iterator, state.configuration));
fnameStack.addLast(fieldFormatter.format(field));
fnameStack.addLast(nameFormatter.format(field.getName()));
state = new State(configurationFields(value), value);
}
}
@ -66,8 +66,8 @@ final class CommentNodeExtractor {
) {
if (field.isAnnotationPresent(Comment.class)) {
final var comments = field.getAnnotation(Comment.class).value();
final var fieldName = fieldFormatter.format(field);
final var fieldNames = new ArrayList<>(fileNameStack.stream().toList());
final var fieldName = nameFormatter.format(field.getName());
final var fieldNames = new ArrayList<>(fileNameStack);
fieldNames.add(fieldName);
final var result = new CommentNode(Arrays.asList(comments), fieldNames);
return Optional.of(result);

@ -12,7 +12,7 @@ import static de.exlll.configlib.Validator.requireNonNull;
class ConfigurationProperties {
private final Map<Class<?>, Serializer<?, ?>> serializersByType;
private final Map<Predicate<? super Type>, Serializer<?, ?>> serializersByCondition;
private final FieldFormatter formatter;
private final NameFormatter formatter;
private final FieldFilter filter;
private final boolean outputNulls;
private final boolean inputNulls;
@ -29,7 +29,7 @@ class ConfigurationProperties {
this.serializersByCondition = Collections.unmodifiableMap(new LinkedHashMap<>(
builder.serializersByCondition
));
this.formatter = requireNonNull(builder.formatter, "field formatter");
this.formatter = requireNonNull(builder.formatter, "name formatter");
this.filter = requireNonNull(builder.filter, "field filter");
this.outputNulls = builder.outputNulls;
this.inputNulls = builder.inputNulls;
@ -75,7 +75,7 @@ class ConfigurationProperties {
private final Map<Class<?>, Serializer<?, ?>> serializersByType = new HashMap<>();
private final Map<Predicate<? super Type>, Serializer<?, ?>> serializersByCondition =
new LinkedHashMap<>();
private FieldFormatter formatter = FieldFormatters.IDENTITY;
private NameFormatter formatter = NameFormatters.IDENTITY;
private FieldFilter filter = FieldFilters.DEFAULT;
private boolean outputNulls = false;
private boolean inputNulls = false;
@ -107,16 +107,16 @@ class ConfigurationProperties {
}
/**
* Sets the field formatter.
* Sets the name formatter.
* <p>
* The default value is a formatter that returns the name of the field.
* The default value is a formatter that returns the same name that was given to it.
*
* @param formatter the formatter
* @return this builder
* @throws NullPointerException if {@code formatter} is null
*/
public final B setFieldFormatter(FieldFormatter formatter) {
this.formatter = requireNonNull(formatter, "field formatter");
public final B setNameFormatter(NameFormatter formatter) {
this.formatter = requireNonNull(formatter, "name formatter");
return getThis();
}
@ -227,11 +227,12 @@ class ConfigurationProperties {
}
/**
* Returns the field formatter used to format the fields of a configuration.
* Returns the name formatter used to format the names of configuration fields and
* record components.
*
* @return the formatter
*/
public final FieldFormatter getFieldFormatter() {
public final NameFormatter getNameFormatter() {
return formatter;
}

@ -4,26 +4,10 @@ import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
final class ConfigurationSerializer<T> implements Serializer<T, Map<?, ?>> {
private final Class<T> configurationType;
private final ConfigurationProperties properties;
private final Map<String, Serializer<?, ?>> serializers;
final class ConfigurationSerializer<T> extends TypeSerializer<T, Field> {
ConfigurationSerializer(Class<T> configurationType, ConfigurationProperties properties) {
this.configurationType = Validator.requireConfiguration(configurationType);
this.properties = properties;
this.serializers = buildSerializerMap();
requireSerializableFields();
}
private void requireSerializableFields() {
if (serializers.isEmpty()) {
String msg = "Configuration class '" + configurationType.getSimpleName() + "' " +
"does not contain any (de-)serializable fields.";
throw new ConfigurationException(msg);
}
super(configurationType, properties);
}
@Override
@ -36,104 +20,69 @@ final class ConfigurationSerializer<T> implements Serializer<T, Map<?, ?>> {
if ((fieldValue == null) && !properties.outputNulls())
continue;
final Object serializedValue = serialize(field, fieldValue);
final Object serializedValue = serialize(field.getName(), fieldValue);
final String formattedField = properties.getFieldFormatter().format(field);
final String formattedField = properties.getNameFormatter().format(field.getName());
result.put(formattedField, serializedValue);
}
return result;
}
private Object serialize(Field field, Object fieldValue) {
// The following cast won't cause a ClassCastException because we select the
// serializers based on the field type.
@SuppressWarnings("unchecked")
final Serializer<Object, Object> serializer =
(Serializer<Object, Object>) serializers.get(field.getName());
return (fieldValue != null) ? serializer.serialize(fieldValue) : null;
}
@Override
public T deserialize(Map<?, ?> element) {
final T result = Reflect.newInstance(configurationType);
final T result = Reflect.newInstance(type);
for (final Field field : filterFields()) {
final String formattedField = properties.getFieldFormatter().format(field);
final String fieldFormatted = properties.getNameFormatter().format(field.getName());
if (!element.containsKey(formattedField))
if (!element.containsKey(fieldFormatted))
continue;
final Object value = element.get(formattedField);
final Object serializedValue = element.get(fieldFormatted);
if (value == null && properties.inputNulls()) {
if (serializedValue == null && properties.inputNulls()) {
requireNonPrimitiveFieldType(field);
Reflect.setValue(field, result, null);
} else if (value != null) {
final Object deserialized = deserialize(field, value);
} else if (serializedValue != null) {
final Object deserialized = deserialize(field, field.getName(), serializedValue);
Reflect.setValue(field, result, deserialized);
}
}
return result;
}
private Object deserialize(Field field, Object value) {
// 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 Serializer<Object, Object> serializer =
(Serializer<Object, Object>) serializers.get(field.getName());
final Object deserialized;
try {
deserialized = serializer.deserialize(value);
} catch (ClassCastException e) {
String msg = baseDeserializeExceptionMessage(field, value) + "\n" +
"The type of the object to be deserialized does not " +
"match the type the deserializer expects.";
throw new ConfigurationException(msg, e);
} catch (RuntimeException e) {
String msg = baseDeserializeExceptionMessage(field, value);
throw new ConfigurationException(msg, e);
@Override
protected void requireSerializableParts() {
if (serializers.isEmpty()) {
String msg = "Configuration class '" + type.getSimpleName() + "' " +
"does not contain any (de-)serializable fields.";
throw new ConfigurationException(msg);
}
return deserialized;
}
private static String baseDeserializeExceptionMessage(Field field, Object value) {
return "Deserialization of value '" + value + "' with type " + value.getClass() + " " +
"for field " + field + " failed.";
@Override
protected String baseDeserializeExceptionMessage(Field field, Object value) {
return "Deserialization of value '%s' with type '%s' for field '%s' failed."
.formatted(value, value.getClass(), field);
}
private static void requireNonPrimitiveFieldType(Field field) {
if (field.getType().isPrimitive()) {
String msg = "Cannot set " + field + " to null value.\n" +
"Primitive types cannot be assigned null.";
String msg = ("Cannot set field '%s' to null value. Primitive types " +
"cannot be assigned null.").formatted(field);
throw new ConfigurationException(msg);
}
}
private List<Field> filterFields() {
return FieldExtractors.CONFIGURATION.extract(configurationType)
return FieldExtractors.CONFIGURATION.extract(type)
.filter(properties.getFieldFilter())
.toList();
}
Map<String, Serializer<?, ?>> buildSerializerMap() {
SerializerSelector selector = new SerializerSelector(properties);
try {
return filterFields().stream()
.collect(Collectors.toMap(
Field::getName,
field -> selector.select(field.getGenericType()),
(serializer1, serializer2) -> serializer2
));
} catch (StackOverflowError error) {
String msg = "Recursive type definitions are not supported.";
throw new ConfigurationException(msg, error);
}
}
Class<T> getConfigurationType() {
return configurationType;
return type;
}
}

@ -1,24 +0,0 @@
package de.exlll.configlib;
import java.lang.reflect.Field;
import java.util.function.Function;
/**
* Implementations of this interface format the names of fields.
*/
@FunctionalInterface
public interface FieldFormatter extends Function<Field, String> {
/**
* Formats the name of the given field.
*
* @param field the field
* @return formatted field name
* @throws NullPointerException if {@code field} is null
*/
String format(Field field);
@Override
default String apply(Field field) {
return format(field);
}
}

@ -0,0 +1,31 @@
package de.exlll.configlib;
import java.util.function.Function;
/**
* Implementations of this interface format the names of class fields or record components.
*/
@FunctionalInterface
public interface NameFormatter extends Function<String, String> {
/**
* Formats the name of a class field or record component.
*
* @param name the name that is formatted
* @return formatted name
* @throws NullPointerException if {@code name} is null
*/
String format(String name);
/**
* Formats the name of a class field or record component.
*
* @param name the name that is formatted
* @return formatted name
* @throws NullPointerException if {@code name} is null
*/
@Override
default String apply(String name) {
return format(name);
}
}

@ -1,32 +1,29 @@
package de.exlll.configlib;
import java.lang.reflect.Field;
/**
* This class contains instances of ready-to-use {@code FieldFormatter}s.
* This class contains instances of ready-to-use {@code NameFormatter}s.
*/
public enum FieldFormatters implements FieldFormatter {
public enum NameFormatters implements NameFormatter {
/**
* A {@code FieldFormatter} that returns the name of the field.
* A {@code NameFormatter} that simply returns the given name.
*/
IDENTITY {
@Override
public String format(Field field) {
return field.getName();
public String format(String name) {
return name;
}
},
/**
* A {@code FieldFormatter} that transforms <i>camelCase</i> to
* A {@code NameFormatter} that transforms <i>camelCase</i> to
* <i>lower_underscore</i>.
* <p>
* For example, <i>myPrivateField</i> becomes <i>my_private_field</i>.
*/
LOWER_UNDERSCORE {
@Override
public String format(Field field) {
String fn = field.getName();
StringBuilder builder = new StringBuilder(fn.length());
for (char c : fn.toCharArray()) {
public String format(String name) {
StringBuilder builder = new StringBuilder(name.length());
for (char c : name.toCharArray()) {
if (Character.isUpperCase(c)) {
char lower = Character.toLowerCase(c);
builder.append('_').append(lower);
@ -36,17 +33,16 @@ public enum FieldFormatters implements FieldFormatter {
}
},
/**
* A {@code FieldFormatter} that transforms <i>camelCase</i> to
* A {@code NameFormatter} that transforms <i>camelCase</i> to
* <i>UPPER_UNDERSCORE</i>.
* <p>
* For example, <i>myPrivateField</i> becomes <i>MY_PRIVATE_FIELD</i>.
*/
UPPER_UNDERSCORE {
@Override
public String format(Field field) {
String fn = field.getName();
StringBuilder builder = new StringBuilder(fn.length());
for (char c : fn.toCharArray()) {
public String format(String name) {
StringBuilder builder = new StringBuilder(name.length());
for (char c : name.toCharArray()) {
if (Character.isLowerCase(c)) {
builder.append(Character.toUpperCase(c));
} else if (Character.isUpperCase(c)) {

@ -0,0 +1,92 @@
package de.exlll.configlib;
import java.lang.reflect.RecordComponent;
import java.util.LinkedHashMap;
import java.util.Map;
final class RecordSerializer<R extends Record> extends TypeSerializer<R, RecordComponent> {
RecordSerializer(Class<R> recordType, ConfigurationProperties properties) {
super(recordType, properties);
}
@Override
public Map<?, ?> serialize(R element) {
final Map<String, Object> result = new LinkedHashMap<>();
for (final RecordComponent component : type.getRecordComponents()) {
final Object componentValue = Reflect.getValue(component, element);
if (componentValue == null && !properties.outputNulls())
continue;
final Object resultValue = serialize(component.getName(), componentValue);
final String compName = properties.getNameFormatter().format(component.getName());
result.put(compName, resultValue);
}
return result;
}
@Override
public R deserialize(Map<?, ?> element) {
final var components = type.getRecordComponents();
final var constructorArguments = new Object[components.length];
for (int i = 0; i < components.length; i++) {
final var component = components[i];
final var componentFormatted = properties.getNameFormatter()
.format(component.getName());
if (!element.containsKey(componentFormatted)) {
constructorArguments[i] = Reflect.getDefaultValue(component.getType());
continue;
}
final Object serializedArgument = element.get(componentFormatted);
if (serializedArgument == null && properties.inputNulls()) {
requireNonPrimitiveComponentType(component);
constructorArguments[i] = null;
} else if (serializedArgument == null) {
constructorArguments[i] = Reflect.getDefaultValue(component.getType());
} else {
constructorArguments[i] = deserialize(
component,
component.getName(),
serializedArgument
);
}
}
return Reflect.newRecord(type, constructorArguments);
}
@Override
protected void requireSerializableParts() {
if (serializers.isEmpty()) {
String msg = "Record type '%s' does not define any components."
.formatted(type.getSimpleName());
throw new ConfigurationException(msg);
}
}
@Override
protected String baseDeserializeExceptionMessage(RecordComponent component, Object value) {
return "Deserialization of value '%s' with type '%s' for component '%s' of record '%s' failed."
.formatted(value, value.getClass(), component, component.getDeclaringRecord());
}
private static void requireNonPrimitiveComponentType(RecordComponent component) {
if (component.getType().isPrimitive()) {
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);
}
}
Class<R> getRecordType() {
return type;
}
}

@ -1,16 +1,41 @@
package de.exlll.configlib;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
final class Reflect {
private static final Map<Class<?>, Object> DEFAULT_VALUES = initDefaultValues();
private Reflect() {}
private static Map<Class<?>, Object> initDefaultValues() {
return Stream.of(
boolean.class,
char.class,
byte.class,
short.class,
int.class,
long.class,
float.class,
double.class
)
.collect(Collectors.toMap(
type -> type,
type -> Array.get(Array.newInstance(type, 1), 0)
));
}
static <T> T getDefaultValue(Class<T> clazz) {
@SuppressWarnings("unchecked")
T defaultValue = (T) DEFAULT_VALUES.get(clazz);
return defaultValue;
}
static <T> T newInstance(Class<T> cls) {
try {
Constructor<T> constructor = cls.getDeclaredConstructor();
@ -35,6 +60,35 @@ final class Reflect {
}
}
static <R extends Record> R newRecord(Class<R> recordType, Object... constructorArguments) {
try {
Constructor<R> constructor = getCanonicalConstructor(recordType);
constructor.setAccessible(true);
return constructor.newInstance(constructorArguments);
} catch (NoSuchMethodException e) {
// cannot happen because we select the constructor based on the component types
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
// cannot happen because we set the constructor to be accessible.
throw new RuntimeException(e);
} catch (InstantiationException e) {
// cannot happen because records are instantiable
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
String msg = "The canonical constructor of record type '" +
recordType.getSimpleName() + "' threw an exception.";
throw new RuntimeException(msg, e);
}
}
static <R extends Record> Constructor<R> getCanonicalConstructor(Class<R> recordType)
throws NoSuchMethodException {
Class<?>[] parameterTypes = Arrays.stream(recordType.getRecordComponents())
.map(RecordComponent::getType)
.toArray(Class<?>[]::new);
return recordType.getDeclaredConstructor(parameterTypes);
}
static <T> T[] newArray(Class<T> componentType, int length) {
// The following cast won't fail because we just created an array of that type
@SuppressWarnings("unchecked")
@ -55,6 +109,23 @@ final class Reflect {
}
}
static Object getValue(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);
} catch (InvocationTargetException e) {
String msg = "Invocation of method '%s' on record '%s' failed."
.formatted(accessor, recordInstance);
throw new RuntimeException(msg, e);
}
}
static void setValue(Field field, Object instance, Object value) {
try {
field.setAccessible(true);

@ -0,0 +1,77 @@
package de.exlll.configlib;
import java.lang.reflect.Field;
import java.lang.reflect.RecordComponent;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static de.exlll.configlib.Validator.requireNonNull;
/**
* A mapper that maps field or component names to serializers that are selected based on
* the field or component type, respectively.
*/
final class SerializerMapper {
private final Class<?> type;
private final ConfigurationProperties properties;
private final SerializerSelector selector;
SerializerMapper(Class<?> type, ConfigurationProperties properties) {
this.type = requireNonNull(type, "type");
this.properties = requireNonNull(properties, "configuration properties");
this.selector = new SerializerSelector(properties);
requireConfigurationOrRecord();
}
private void requireConfigurationOrRecord() {
if (!type.isRecord() && !Reflect.isConfiguration(type)) {
String msg = "Type '%s' must be a configuration or record type."
.formatted(type.getSimpleName());
throw new ConfigurationException(msg);
}
}
public Map<String, Serializer<?, ?>> buildSerializerMap() {
return type.isRecord()
? buildSerializerMapForRecord()
: buildSerializerMapForConfiguration();
}
private Map<String, Serializer<?, ?>> buildSerializerMapForRecord() {
return tryBuildSerializerMap(
Arrays.stream(type.getRecordComponents()),
RecordComponent::getName,
RecordComponent::getGenericType
);
}
private Map<String, Serializer<?, ?>> buildSerializerMapForConfiguration() {
return tryBuildSerializerMap(filterFields(), Field::getName, Field::getGenericType);
}
private <T> Map<String, Serializer<?, ?>> tryBuildSerializerMap(
Stream<T> stream,
Function<T, String> nameExtractor,
Function<T, Type> typeExtractor
) {
try {
return stream.collect(Collectors.toMap(
nameExtractor,
element -> selector.select(typeExtractor.apply(element))
));
} catch (StackOverflowError error) {
String msg = "Recursive type definitions are not supported.";
throw new ConfigurationException(msg, error);
}
}
private Stream<Field> filterFields() {
return FieldExtractors.CONFIGURATION.extract(type)
.filter(properties.getFieldFilter());
}
}

@ -93,11 +93,17 @@ final class SerializerSelector {
if (Reflect.isEnumType(cls)) {
// The following cast won't fail because we just checked that it's an enum.
@SuppressWarnings("unchecked")
var enumCls = (Class<? extends Enum<?>>) cls;
return new Serializers.EnumSerializer(enumCls);
final var enumType = (Class<? extends Enum<?>>) cls;
return new Serializers.EnumSerializer(enumType);
}
if (Reflect.isArrayType(cls))
return selectForArray(cls.getComponentType());
if (cls.isRecord()) {
// The following cast won't fail because we just checked that it's a record.
@SuppressWarnings("unchecked")
final var recordType = (Class<? extends Record>) cls;
return new RecordSerializer<>(recordType, properties);
}
if (Reflect.isConfiguration(cls))
return new ConfigurationSerializer<>(cls, properties);

@ -0,0 +1,51 @@
package de.exlll.configlib;
import java.util.Map;
import static de.exlll.configlib.Validator.requireNonNull;
abstract class TypeSerializer<T, P> implements Serializer<T, Map<?, ?>> {
protected final Class<T> type;
protected final ConfigurationProperties properties;
protected final Map<String, Serializer<?, ?>> serializers;
protected TypeSerializer(Class<T> type, ConfigurationProperties properties) {
this.type = requireNonNull(type, "type");
this.properties = requireNonNull(properties, "configuration properties");
this.serializers = new SerializerMapper(type, properties).buildSerializerMap();
requireSerializableParts();
}
protected final Object serialize(String partName, Object value) {
// The following cast won't cause a ClassCastException because the serializers
// are selected based on the part type.
@SuppressWarnings("unchecked")
final var serializer = (Serializer<Object, Object>) serializers.get(partName);
return (value != null) ? serializer.serialize(value) : null;
}
protected final Object deserialize(P part, String partName, Object value) {
// 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(partName);
final Object deserialized;
try {
deserialized = serializer.deserialize(value);
} catch (ClassCastException e) {
String msg = baseDeserializeExceptionMessage(part, value) + "\n" +
"The type of the object to be deserialized does not " +
"match the type the deserializer expects.";
throw new ConfigurationException(msg, e);
} catch (RuntimeException e) {
String msg = baseDeserializeExceptionMessage(part, value);
throw new ConfigurationException(msg, e);
}
return deserialized;
}
protected abstract void requireSerializableParts();
protected abstract String baseDeserializeExceptionMessage(P part, Object value);
}

@ -26,6 +26,14 @@ final class Validator {
return cls;
}
static <T> Class<T> requireRecord(Class<T> cls) {
if (!cls.isRecord()) {
String msg = "Class '" + cls.getSimpleName() + "' must be a record.";
throw new ConfigurationException(msg);
}
return cls;
}
static void requirePrimitiveOrWrapperNumberType(Class<?> cls) {
if (!Reflect.isIntegerType(cls) && !Reflect.isFloatingPointType(cls)) {
String msg = "Class " + cls.getSimpleName() + " is not a byte, short, int, long, " +

@ -256,7 +256,7 @@ class CommentNodeExtractorTest {
String name = field.getName();
return !name.equals("a1") && !name.equals("a");
})
.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
.build();
CommentNodeExtractor extractor = new CommentNodeExtractor(properties);

@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test;
import java.awt.Point;
import java.lang.reflect.Type;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
@ -25,13 +24,13 @@ class ConfigurationPropertiesTest {
assertThat(properties.inputNulls(), is(false));
assertThat(properties.getSerializers().entrySet(), empty());
assertThat(properties.getSerializersByCondition().entrySet(), empty());
assertThat(properties.getFieldFormatter(), is(FieldFormatters.IDENTITY));
assertThat(properties.getNameFormatter(), is(NameFormatters.IDENTITY));
assertThat(properties.getFieldFilter(), is(FieldFilters.DEFAULT));
}
@Test
void builderCopiesValues() {
FieldFormatter formatter = field -> field.getName().toLowerCase(Locale.ROOT);
NameFormatter formatter = String::toLowerCase;
FieldFilter filter = field -> field.getName().startsWith("f");
TestUtils.PointSerializer serializer = new TestUtils.PointSerializer();
Predicate<? super Type> predicate = type -> true;
@ -39,7 +38,7 @@ class ConfigurationPropertiesTest {
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
.addSerializer(Point.class, serializer)
.addSerializerByCondition(predicate, serializer)
.setFieldFormatter(formatter)
.setNameFormatter(formatter)
.setFieldFilter(filter)
.outputNulls(true)
.inputNulls(true)
@ -51,13 +50,13 @@ class ConfigurationPropertiesTest {
assertThat(properties.outputNulls(), is(true));
assertThat(properties.inputNulls(), is(true));
assertThat(properties.serializeSetsAsLists(), is(false));
assertThat(properties.getFieldFormatter(), sameInstance(formatter));
assertThat(properties.getNameFormatter(), sameInstance(formatter));
assertThat(properties.getFieldFilter(), sameInstance(filter));
}
@Test
void builderCtorCopiesValues() {
FieldFormatter formatter = field -> field.getName().toLowerCase(Locale.ROOT);
NameFormatter formatter = String::toLowerCase;
FieldFilter filter = field -> field.getName().startsWith("f");
TestUtils.PointSerializer serializer = new TestUtils.PointSerializer();
Predicate<? super Type> predicate = type -> true;
@ -65,7 +64,7 @@ class ConfigurationPropertiesTest {
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
.addSerializer(Point.class, serializer)
.addSerializerByCondition(predicate, serializer)
.setFieldFormatter(formatter)
.setNameFormatter(formatter)
.setFieldFilter(filter)
.outputNulls(true)
.inputNulls(true)
@ -79,7 +78,7 @@ class ConfigurationPropertiesTest {
assertThat(properties.outputNulls(), is(true));
assertThat(properties.inputNulls(), is(true));
assertThat(properties.serializeSetsAsLists(), is(false));
assertThat(properties.getFieldFormatter(), sameInstance(formatter));
assertThat(properties.getNameFormatter(), sameInstance(formatter));
assertThat(properties.getFieldFilter(), sameInstance(filter));
}
@ -111,10 +110,10 @@ class ConfigurationPropertiesTest {
}
@Test
void setFieldFormatterRequiresNonNull() {
void setNameFormatterRequiresNonNull() {
assertThrowsNullPointerException(
() -> builder.setFieldFormatter(null),
"field formatter"
() -> builder.setNameFormatter(null),
"name formatter"
);
}

@ -1,8 +1,10 @@
package de.exlll.configlib;
import de.exlll.configlib.ConfigurationProperties.Builder;
import de.exlll.configlib.Serializers.*;
import de.exlll.configlib.configurations.*;
import de.exlll.configlib.configurations.ExampleConfigurationB1;
import de.exlll.configlib.configurations.ExampleConfigurationB2;
import de.exlll.configlib.configurations.ExampleEnum;
import de.exlll.configlib.configurations.ExampleInitializer;
import org.junit.jupiter.api.Test;
import java.awt.Point;
@ -11,101 +13,54 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Consumer;
import static de.exlll.configlib.TestUtils.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("FieldMayBeFinal")
class ConfigurationSerializerTest {
private static <T> ConfigurationSerializer<T> newSerializer(Class<T> cls) {
return newSerializer(cls, builder -> builder);
return newSerializer(cls, builder -> {});
}
private static <T> ConfigurationSerializer<T> newSerializer(
Class<T> cls,
Function<Builder<?>, Builder<?>> propertiesConfigurer
Consumer<Builder<?>> propertiesConfigurer
) {
var builder = ConfigurationProperties.newBuilder()
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
var builder = ConfigurationProperties.newBuilder();
builder.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
builder = propertiesConfigurer.apply(builder);
propertiesConfigurer.accept(builder);
return new ConfigurationSerializer<>(cls, builder.build());
}
@Test
void buildSerializerMapFiltersFields() {
Map<String, Serializer<?, ?>> serializers = newSerializer(ExampleConfigurationA2.class)
.buildSerializerMap();
assertThat(serializers.get("a1_staticFinalInt"), nullValue());
assertThat(serializers.get("a1_staticInt"), nullValue());
assertThat(serializers.get("a1_finalInt"), nullValue());
assertThat(serializers.get("a1_transientInt"), nullValue());
assertThat(serializers.get("a1_ignoredInt"), nullValue());
assertThat(serializers.get("a1_ignoredString"), nullValue());
assertThat(serializers.get("a1_ignoredListString"), nullValue());
assertThat(serializers.get("a2_staticFinalInt"), nullValue());
assertThat(serializers.get("a2_staticInt"), nullValue());
assertThat(serializers.get("a2_finalInt"), nullValue());
assertThat(serializers.get("a2_transientInt"), nullValue());
assertThat(serializers.get("a2_ignoredInt"), nullValue());
assertThat(serializers.get("a2_ignoredString"), nullValue());
assertThat(serializers.get("a2_ignoredListString"), nullValue());
@Configuration
private static final class B5 {
@Ignore
private int ignored = 1;
}
@Test
void buildSerializerMapIgnoresFormatter() {
Map<String, Serializer<?, ?>> serializers = newSerializer(
ExampleConfigurationA2.class,
props -> props.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
).buildSerializerMap();
void ctorRequiresConfigurationWithFields() {
assertThrowsConfigurationException(
() -> newSerializer(B5.class),
"Configuration class 'B5' does not contain any (de-)serializable fields."
);
}
assertThat(serializers.get("A2_PRIM_BOOL"), nullValue());
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
private static final class B6 {
@Ignore
private int ignored = 1;
}
@Test
void buildSerializerMap() {
Map<String, Serializer<?, ?>> serializers = newSerializer(ExampleConfigurationA2.class)
.buildSerializerMap();
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("a2_refChar"), instanceOf(CharacterSerializer.class));
assertThat(serializers.get("a2_string"), instanceOf(StringSerializer.class));
assertThat(serializers.get("a2_Enm"), instanceOf(EnumSerializer.class));
ConfigurationSerializer<?> serializerB1 =
(ConfigurationSerializer<?>) serializers.get("a2_b1");
ConfigurationSerializer<?> serializerB2 =
(ConfigurationSerializer<?>) serializers.get("a2_b2");
assertThat(serializerB1.getConfigurationType(), equalTo(ExampleConfigurationB1.class));
assertThat(serializerB2.getConfigurationType(), equalTo(ExampleConfigurationB2.class));
ListSerializer<?, ?> serializerList =
(ListSerializer<?, ?>) serializers.get("a2_listByte");
ArraySerializer<?, ?> serializerArray =
(ArraySerializer<?, ?>) serializers.get("a2_arrayString");
SetAsListSerializer<?, ?> serializerSet =
(SetAsListSerializer<?, ?>) serializers.get("a2_setBigInteger");
MapSerializer<?, ?, ?, ?> serializerMap =
(MapSerializer<?, ?, ?, ?>) serializers.get("a2_mapLocalTimeLocalTime");
assertThat(
serializers.get("a2_arrayPrimDouble"),
instanceOf(PrimitiveDoubleArraySerializer.class)
void ctorRequiresConfiguration() {
assertThrowsConfigurationException(
() -> newSerializer(B6.class),
"Type 'B6' must be a configuration or record type."
);
assertThat(serializerList.getElementSerializer(), instanceOf(NumberSerializer.class));
assertThat(serializerArray.getElementSerializer(), instanceOf(StringSerializer.class));
assertThat(serializerSet.getElementSerializer(), instanceOf(BigIntegerSerializer.class));
assertThat(serializerMap.getKeySerializer(), instanceOf(LocalTimeSerializer.class));
assertThat(serializerMap.getValueSerializer(), instanceOf(LocalTimeSerializer.class));
assertThat(serializers.get("a2_point"), sameInstance(TestUtils.POINT_SERIALIZER));
}
@Test
@ -117,7 +72,7 @@ class ConfigurationSerializerTest {
}
ConfigurationSerializer<A> serializer = newSerializer(
A.class,
builder -> builder.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
builder -> builder.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
);
Map<?, ?> map = serializer.serialize(new A());
assertThat(map.remove("VALUE1"), is(1L));
@ -135,7 +90,7 @@ class ConfigurationSerializerTest {
void deserializeAppliesFormatter() {
ConfigurationSerializer<B1> serializer = newSerializer(
B1.class,
builder -> builder.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
builder -> builder.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
);
Map<String, ?> map = Map.of(
"value1", 3,
@ -171,7 +126,7 @@ class ConfigurationSerializerTest {
Map<String, Object> map = asMap(fieldName, null);
assertThrowsConfigurationException(
() -> serializer.deserialize(map),
"Cannot set " + getField(B2.class, fieldName) + " to null value.\n" +
"Cannot set field '" + getField(B2.class, fieldName) + "' to null value. " +
"Primitive types cannot be assigned null."
);
}
@ -188,16 +143,16 @@ class ConfigurationSerializerTest {
ConfigurationSerializer<B3> serializer = newSerializer(B3.class);
assertThrowsConfigurationException(
() -> serializer.deserialize(Map.of("s", (byte) 3)),
"Deserialization of value '3' with type class java.lang.Byte for field " +
"java.lang.String de.exlll.configlib.ConfigurationSerializerTest$B3.s " +
"Deserialization of value '3' with type 'class java.lang.Byte' for field " +
"'java.lang.String de.exlll.configlib.ConfigurationSerializerTest$B3.s' " +
"failed.\nThe type of the object to be deserialized does not match the type " +
"the deserializer expects."
);
assertThrowsConfigurationException(
() -> serializer.deserialize(Map.of("l", List.of(List.of(3)))),
"Deserialization of value '[[3]]' with type class " +
"java.util.ImmutableCollections$List12 for field java.util.List " +
"de.exlll.configlib.ConfigurationSerializerTest$B3.l failed.\n" +
"Deserialization of value '[[3]]' with type 'class " +
"java.util.ImmutableCollections$List12' for field 'java.util.List " +
"de.exlll.configlib.ConfigurationSerializerTest$B3.l' failed.\n" +
"The type of the object to be deserialized does not match the type the " +
"deserializer expects."
);
@ -252,51 +207,6 @@ class ConfigurationSerializerTest {
assertSame(B4.B4_NULL_POINT, config.nullPoint);
}
@Configuration
private static final class B5 {
@Ignore
private int ignored = 1;
}
@Test
void ctorRequiresConfigurationWithFields() {
assertThrowsConfigurationException(
() -> newSerializer(B5.class),
"Configuration class 'B5' does not contain any (de-)serializable fields."
);
}
private static final class B6 {
@Ignore
private int ignored = 1;
}
@Test
void ctorRequiresConfiguration() {
assertThrowsConfigurationException(
() -> newSerializer(B6.class),
"Class 'B6' must be a configuration."
);
}
@Test
void buildSerializerMapPreventsRecursiveDefinitions() {
assertThrowsConfigurationException(
() -> newSerializer(R1.class),
"Recursive type definitions are not supported."
);
}
@Configuration
static final class R1 {
R2 r2;
}
@Configuration
static final class R2 {
R1 r1;
}
@Test
void serializeTypeWithAbstractParent() {
ConfigurationSerializer<B8> serializer = newSerializer(B8.class);

@ -217,6 +217,68 @@ class ExampleConfigurationTests {
);
}
@Test
void serializeExampleRecord1() {
RecordSerializer<ExampleRecord1> serializer =
new RecordSerializer<>(ExampleRecord1.class, PROPERTIES_DENY_NULL);
assertEquals(EXAMPLE_RECORD1_1, serializer.serialize(ExampleInitializer.EXAMPLE_RECORD1_1));
assertEquals(EXAMPLE_RECORD1_2, serializer.serialize(ExampleInitializer.EXAMPLE_RECORD1_2));
}
@Test
void deserializeExampleRecord1() {
RecordSerializer<ExampleRecord1> serializer =
new RecordSerializer<>(ExampleRecord1.class, PROPERTIES_DENY_NULL);
ExampleRecord1 deserialize1 = serializer.deserialize(EXAMPLE_RECORD1_1);
assertExampleRecord1Equal(ExampleInitializer.EXAMPLE_RECORD1_1, deserialize1);
ExampleRecord1 deserialize2 = serializer.deserialize(EXAMPLE_RECORD1_2);
assertExampleRecord1Equal(ExampleInitializer.EXAMPLE_RECORD1_2, deserialize2);
}
@Test
void serializeExampleRecord2() {
RecordSerializer<ExampleRecord2> serializer =
new RecordSerializer<>(ExampleRecord2.class, PROPERTIES_DENY_NULL);
assertEquals(EXAMPLE_RECORD2_1, serializer.serialize(ExampleInitializer.EXAMPLE_RECORD2_1));
assertEquals(EXAMPLE_RECORD2_2, serializer.serialize(ExampleInitializer.EXAMPLE_RECORD2_2));
}
@Test
void deserializeExampleRecord2() {
RecordSerializer<ExampleRecord2> serializer =
new RecordSerializer<>(ExampleRecord2.class, PROPERTIES_DENY_NULL);
ExampleRecord2 deserialize1 = serializer.deserialize(EXAMPLE_RECORD2_1);
assertExampleRecord2Equal(ExampleInitializer.EXAMPLE_RECORD2_1, deserialize1);
ExampleRecord2 deserialize2 = serializer.deserialize(EXAMPLE_RECORD2_2);
assertExampleRecord2Equal(ExampleInitializer.EXAMPLE_RECORD2_2, deserialize2);
}
private static void assertExampleRecord1Equal(
ExampleRecord1 expected,
ExampleRecord1 actual
) {
assertThat(actual.i(), is(expected.i()));
assertThat(actual.d(), is(expected.d()));
assertThat(actual.enm(), is(expected.enm()));
assertThat(actual.listUuid(), is(expected.listUuid()));
assertThat(actual.arrayArrayFloat(), is(expected.arrayArrayFloat()));
assertExampleConfigurationsB1Equal(expected.b1(), actual.b1());
}
private static void assertExampleRecord2Equal(
ExampleRecord2 expected,
ExampleRecord2 actual
) {
assertEquals(expected.b(), actual.b());
assertExampleRecord1Equal(expected.r1(), actual.r1());
}
private static void assertExampleConfigurationsNullsEqual(
ExampleConfigurationNulls expected,
ExampleConfigurationNulls actual
@ -282,6 +344,8 @@ class ExampleConfigurationTests {
assertThat(a1_1.getA1_Enm(), is(a1_2.getA1_Enm()));
assertThat(a1_1.getA1_b1(), is(a1_2.getA1_b1()));
assertThat(a1_1.getA1_b2(), is(a1_2.getA1_b2()));
assertThat(a1_1.getA1_r1(), is(a1_2.getA1_r1()));
assertThat(a1_1.getA1_r2(), is(a1_2.getA1_r2()));
assertThat(a1_1.getA1_listBoolean(), is(a1_2.getA1_listBoolean()));
assertThat(a1_1.getA1_listChar(), is(a1_2.getA1_listChar()));
assertThat(a1_1.getA1_listByte(), is(a1_2.getA1_listByte()));
@ -305,6 +369,8 @@ class ExampleConfigurationTests {
assertThat(a1_1.getA1_listEnm(), is(a1_2.getA1_listEnm()));
assertThat(a1_1.getA1_listB1(), is(a1_2.getA1_listB1()));
assertThat(a1_1.getA1_listB2(), is(a1_2.getA1_listB2()));
assertThat(a1_1.getA1_listR1(), is(a1_2.getA1_listR1()));
assertThat(a1_1.getA1_listR2(), is(a1_2.getA1_listR2()));
assertThat(a1_1.getA1_arrayPrimBoolean(), is(a1_2.getA1_arrayPrimBoolean()));
assertThat(a1_1.getA1_arrayPrimChar(), is(a1_2.getA1_arrayPrimChar()));
assertThat(a1_1.getA1_arrayPrimByte(), is(a1_2.getA1_arrayPrimByte()));
@ -331,6 +397,8 @@ class ExampleConfigurationTests {
assertThat(a1_1.getA1_arrayEnm(), is(a1_2.getA1_arrayEnm()));
assertThat(a1_1.getA1_arrayB1(), is(a1_2.getA1_arrayB1()));
assertThat(a1_1.getA1_arrayB2(), is(a1_2.getA1_arrayB2()));
assertThat(a1_1.getA1_arrayR1(), is(a1_2.getA1_arrayR1()));
assertThat(a1_1.getA1_arrayR2(), is(a1_2.getA1_arrayR2()));
assertThat(a1_1.getA1_setBoolean(), is(a1_2.getA1_setBoolean()));
assertThat(a1_1.getA1_setChar(), is(a1_2.getA1_setChar()));
assertThat(a1_1.getA1_setByte(), is(a1_2.getA1_setByte()));
@ -349,6 +417,8 @@ class ExampleConfigurationTests {
assertThat(a1_1.getA1_setEnm(), is(a1_2.getA1_setEnm()));
assertThat(a1_1.getA1_setB1(), is(a1_2.getA1_setB1()));
assertThat(a1_1.getA1_setB2(), is(a1_2.getA1_setB2()));
assertThat(a1_1.getA1_setR1(), is(a1_2.getA1_setR1()));
assertThat(a1_1.getA1_setR2(), is(a1_2.getA1_setR2()));
assertThat(a1_1.getA1_mapBooleanBoolean(), is(a1_2.getA1_mapBooleanBoolean()));
assertThat(a1_1.getA1_mapCharChar(), is(a1_2.getA1_mapCharChar()));
assertThat(a1_1.getA1_mapByteByte(), is(a1_2.getA1_mapByteByte()));
@ -367,6 +437,8 @@ class ExampleConfigurationTests {
assertThat(a1_1.getA1_mapEnmEnm(), is(a1_2.getA1_mapEnmEnm()));
assertThat(a1_1.getA1_mapIntegerB1(), is(a1_2.getA1_mapIntegerB1()));
assertThat(a1_1.getA1_mapEnmB2(), is(a1_2.getA1_mapEnmB2()));
assertThat(a1_1.getA1_mapStringR1(), is(a1_2.getA1_mapStringR1()));
assertThat(a1_1.getA1_mapStringR2(), is(a1_2.getA1_mapStringR2()));
assertThat(a1_1.getA1_listEmpty(), is(a1_2.getA1_listEmpty()));
assertThat(a1_1.getA1_arrayEmpty(), is(a1_2.getA1_arrayEmpty()));
assertThat(a1_1.getA1_setEmpty(), is(a1_2.getA1_setEmpty()));
@ -411,6 +483,8 @@ class ExampleConfigurationTests {
assertThat(a1_1.getA1_arrayArrayEnm(), is(a1_2.getA1_arrayArrayEnm()));
assertThat(a1_1.getA1_arrayArrayB1(), is(a1_2.getA1_arrayArrayB1()));
assertThat(a1_1.getA1_arrayArrayB2(), is(a1_2.getA1_arrayArrayB2()));
assertThat(a1_1.getA1_arrayArrayR1(), is(a1_2.getA1_arrayArrayR1()));
assertThat(a1_1.getA1_arrayArrayR2(), is(a1_2.getA1_arrayArrayR2()));
assertThat(a1_1.getA1_point(), is(a1_2.getA1_point()));
assertThat(a1_1.getA1_listPoint(), is(a1_2.getA1_listPoint()));
assertThat(a1_1.getA1_arrayPoint(), is(a1_2.getA1_arrayPoint()));
@ -463,6 +537,8 @@ class ExampleConfigurationTests {
assertThat(a2_1.getA2_Enm(), is(a2_2.getA2_Enm()));
assertThat(a2_1.getA2_b1(), is(a2_2.getA2_b1()));
assertThat(a2_1.getA2_b2(), is(a2_2.getA2_b2()));
assertThat(a2_1.getA2_r1(), is(a2_2.getA2_r1()));
assertThat(a2_1.getA2_r2(), is(a2_2.getA2_r2()));
assertThat(a2_1.getA2_listBoolean(), is(a2_2.getA2_listBoolean()));
assertThat(a2_1.getA2_listChar(), is(a2_2.getA2_listChar()));
assertThat(a2_1.getA2_listByte(), is(a2_2.getA2_listByte()));
@ -486,6 +562,8 @@ class ExampleConfigurationTests {
assertThat(a2_1.getA2_listEnm(), is(a2_2.getA2_listEnm()));
assertThat(a2_1.getA2_listB1(), is(a2_2.getA2_listB1()));
assertThat(a2_1.getA2_listB2(), is(a2_2.getA2_listB2()));
assertThat(a2_1.getA2_listR1(), is(a2_2.getA2_listR1()));
assertThat(a2_1.getA2_listR2(), is(a2_2.getA2_listR2()));
assertThat(a2_1.getA2_arrayPrimBoolean(), is(a2_2.getA2_arrayPrimBoolean()));
assertThat(a2_1.getA2_arrayPrimChar(), is(a2_2.getA2_arrayPrimChar()));
assertThat(a2_1.getA2_arrayPrimByte(), is(a2_2.getA2_arrayPrimByte()));
@ -512,6 +590,8 @@ class ExampleConfigurationTests {
assertThat(a2_1.getA2_arrayEnm(), is(a2_2.getA2_arrayEnm()));
assertThat(a2_1.getA2_arrayB1(), is(a2_2.getA2_arrayB1()));
assertThat(a2_1.getA2_arrayB2(), is(a2_2.getA2_arrayB2()));
assertThat(a2_1.getA2_arrayR1(), is(a2_2.getA2_arrayR1()));
assertThat(a2_1.getA2_arrayR2(), is(a2_2.getA2_arrayR2()));
assertThat(a2_1.getA2_setBoolean(), is(a2_2.getA2_setBoolean()));
assertThat(a2_1.getA2_setChar(), is(a2_2.getA2_setChar()));
assertThat(a2_1.getA2_setByte(), is(a2_2.getA2_setByte()));
@ -530,6 +610,8 @@ class ExampleConfigurationTests {
assertThat(a2_1.getA2_setEnm(), is(a2_2.getA2_setEnm()));
assertThat(a2_1.getA2_setB1(), is(a2_2.getA2_setB1()));
assertThat(a2_1.getA2_setB2(), is(a2_2.getA2_setB2()));
assertThat(a2_1.getA2_setR1(), is(a2_2.getA2_setR1()));
assertThat(a2_1.getA2_setR2(), is(a2_2.getA2_setR2()));
assertThat(a2_1.getA2_mapBooleanBoolean(), is(a2_2.getA2_mapBooleanBoolean()));
assertThat(a2_1.getA2_mapCharChar(), is(a2_2.getA2_mapCharChar()));
assertThat(a2_1.getA2_mapByteByte(), is(a2_2.getA2_mapByteByte()));
@ -548,6 +630,8 @@ class ExampleConfigurationTests {
assertThat(a2_1.getA2_mapEnmEnm(), is(a2_2.getA2_mapEnmEnm()));
assertThat(a2_1.getA2_mapIntegerB1(), is(a2_2.getA2_mapIntegerB1()));
assertThat(a2_1.getA2_mapEnmB2(), is(a2_2.getA2_mapEnmB2()));
assertThat(a2_1.getA2_mapStringR1(), is(a2_2.getA2_mapStringR1()));
assertThat(a2_1.getA2_mapStringR2(), is(a2_2.getA2_mapStringR2()));
assertThat(a2_1.getA2_listEmpty(), is(a2_2.getA2_listEmpty()));
assertThat(a2_1.getA2_arrayEmpty(), is(a2_2.getA2_arrayEmpty()));
assertThat(a2_1.getA2_setEmpty(), is(a2_2.getA2_setEmpty()));
@ -592,6 +676,8 @@ class ExampleConfigurationTests {
assertThat(a2_1.getA2_arrayArrayEnm(), is(a2_2.getA2_arrayArrayEnm()));
assertThat(a2_1.getA2_arrayArrayB1(), is(a2_2.getA2_arrayArrayB1()));
assertThat(a2_1.getA2_arrayArrayB2(), is(a2_2.getA2_arrayArrayB2()));
assertThat(a2_1.getA2_arrayArrayR1(), is(a2_2.getA2_arrayArrayR1()));
assertThat(a2_1.getA2_arrayArrayR2(), is(a2_2.getA2_arrayArrayR2()));
assertThat(a2_1.getA2_point(), is(a2_2.getA2_point()));
assertThat(a2_1.getA2_listPoint(), is(a2_2.getA2_listPoint()));
assertThat(a2_1.getA2_arrayPoint(), is(a2_2.getA2_arrayPoint()));

@ -1,22 +0,0 @@
package de.exlll.configlib;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.mockito.Mockito.*;
class FieldFormatterTest {
@Test
void applyCallsFormat() {
class A {
int i;
}
FieldFormatter formatter = mock(FieldFormatter.class, CALLS_REAL_METHODS);
Field field = TestUtils.getField(A.class, "i");
formatter.apply(field);
verify(formatter).format(field);
}
}

@ -1,58 +0,0 @@
package de.exlll.configlib;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static de.exlll.configlib.TestUtils.getField;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
class FieldFormattersTest {
private static class A {
String lowercase;
String camelCase;
String withNumber123;
String with123Number;
String with_$;
}
private static final Field f1 = getField(A.class, "lowercase");
private static final Field f2 = getField(A.class, "camelCase");
private static final Field f3 = getField(A.class, "withNumber123");
private static final Field f4 = getField(A.class, "with123Number");
private static final Field f5 = getField(A.class, "with_$");
@Test
void formatIdentity() {
FieldFormatters formatter = FieldFormatters.IDENTITY;
assertThat(formatter.format(f1), is("lowercase"));
assertThat(formatter.format(f2), is("camelCase"));
assertThat(formatter.format(f3), is("withNumber123"));
assertThat(formatter.format(f4), is("with123Number"));
assertThat(formatter.format(f5), is("with_$"));
}
@Test
void formatLowerUnderscore() {
FieldFormatters formatter = FieldFormatters.LOWER_UNDERSCORE;
assertThat(formatter.format(f1), is("lowercase"));
assertThat(formatter.format(f2), is("camel_case"));
assertThat(formatter.format(f3), is("with_number123"));
assertThat(formatter.format(f4), is("with123_number"));
assertThat(formatter.format(f5), is("with_$"));
}
@Test
void formatUpperUnderscore() {
FieldFormatters formatter = FieldFormatters.UPPER_UNDERSCORE;
assertThat(formatter.format(f1), is("LOWERCASE"));
assertThat(formatter.format(f2), is("CAMEL_CASE"));
assertThat(formatter.format(f3), is("WITH_NUMBER123"));
assertThat(formatter.format(f4), is("WITH123_NUMBER"));
assertThat(formatter.format(f5), is("WITH_$"));
}
}

@ -0,0 +1,15 @@
package de.exlll.configlib;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.*;
class NameFormatterTest {
@Test
void applyCallsFormat() {
NameFormatter formatter = mock(NameFormatter.class, CALLS_REAL_METHODS);
String name = "i";
formatter.apply(name);
verify(formatter).format(name);
}
}

@ -0,0 +1,47 @@
package de.exlll.configlib;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
class NameFormattersTest {
private static final String NAME_1 = "lowercase";
private static final String NAME_2 = "camelCase";
private static final String NAME_3 = "withNumber123";
private static final String NAME_4 = "with123Number";
private static final String NAME_5 = "with_$";
@Test
void formatIdentity() {
NameFormatters formatter = NameFormatters.IDENTITY;
assertThat(formatter.format(NAME_1), is("lowercase"));
assertThat(formatter.format(NAME_2), is("camelCase"));
assertThat(formatter.format(NAME_3), is("withNumber123"));
assertThat(formatter.format(NAME_4), is("with123Number"));
assertThat(formatter.format(NAME_5), is("with_$"));
}
@Test
void formatLowerUnderscore() {
NameFormatters formatter = NameFormatters.LOWER_UNDERSCORE;
assertThat(formatter.format(NAME_1), is("lowercase"));
assertThat(formatter.format(NAME_2), is("camel_case"));
assertThat(formatter.format(NAME_3), is("with_number123"));
assertThat(formatter.format(NAME_4), is("with123_number"));
assertThat(formatter.format(NAME_5), is("with_$"));
}
@Test
void formatUpperUnderscore() {
NameFormatters formatter = NameFormatters.UPPER_UNDERSCORE;
assertThat(formatter.format(NAME_1), is("LOWERCASE"));
assertThat(formatter.format(NAME_2), is("CAMEL_CASE"));
assertThat(formatter.format(NAME_3), is("WITH_NUMBER123"));
assertThat(formatter.format(NAME_4), is("WITH123_NUMBER"));
assertThat(formatter.format(NAME_5), is("WITH_$"));
}
}

@ -0,0 +1,214 @@
package de.exlll.configlib;
import org.junit.jupiter.api.Test;
import java.awt.Point;
import java.lang.reflect.RecordComponent;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import static de.exlll.configlib.TestUtils.asMap;
import static de.exlll.configlib.TestUtils.assertThrowsConfigurationException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RecordSerializerTest {
private static <R extends Record> RecordSerializer<R> newSerializer(Class<R> cls) {
return newSerializer(cls, builder -> {});
}
private static <R extends Record> RecordSerializer<R> newSerializer(
Class<R> cls,
Consumer<ConfigurationProperties.Builder<?>> propertiesConfigurer
) {
var builder = ConfigurationProperties.newBuilder();
builder.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
propertiesConfigurer.accept(builder);
return new RecordSerializer<>(cls, builder.build());
}
@Test
void ctorRequiresRecordWithComponents() {
record Empty() {}
assertThrowsConfigurationException(
() -> newSerializer(Empty.class),
"Record type 'Empty' does not define any components."
);
}
record R1(int value1, int someValue2) {}
@Test
void serializeAppliesFormatter() {
RecordSerializer<R1> serializer = newSerializer(
R1.class,
builder -> builder.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
);
Map<?, ?> map = serializer.serialize(new R1(1, 2));
assertThat(map.remove("VALUE1"), is(1L));
assertThat(map.remove("SOME_VALUE2"), is(2L));
assertTrue(map.isEmpty());
}
@Test
void deserializeAppliesFormatter() {
RecordSerializer<R1> serializer = newSerializer(
R1.class,
builder -> builder.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
);
Map<String, ?> map = Map.of(
"value1", 3,
"someValue2", 4,
"VALUE1", 5,
"SOME_VALUE2", 6
);
R1 r1 = serializer.deserialize(map);
assertThat(r1.value1, is(5));
assertThat(r1.someValue2, is(6));
}
@Test
void serializeOutputNullsTrue() {
record R(Integer integer) {}
RecordSerializer<R> serializer = newSerializer(R.class, b -> b.outputNulls(true));
Map<?, ?> serialize = serializer.serialize(new R(null));
assertThat(serialize, is(asMap("integer", null)));
}
@Test
void serializeOutputNullsFalse() {
record R(Integer integer) {}
RecordSerializer<R> serializer = newSerializer(R.class, b -> b.outputNulls(false));
Map<?, ?> serialize = serializer.serialize(new R(null));
assertThat(serialize.entrySet(), empty());
}
record R2(
boolean f1,
char f2,
byte f3,
short f4,
int f5,
long f6,
float f7,
double f8,
Integer f9,
R1 f10
) {}
@Test
void deserializeMissingValuesAsDefaultValues() {
RecordSerializer<R2> serializer = newSerializer(R2.class);
R2 r2 = serializer.deserialize(Map.of());
assertThat(r2.f1, is(false));
assertThat(r2.f2, is('\0'));
assertThat(r2.f3, is((byte) 0));
assertThat(r2.f4, is((short) 0));
assertThat(r2.f5, is(0));
assertThat(r2.f6, is(0L));
assertThat(r2.f7, is(0f));
assertThat(r2.f8, is(0d));
assertThat(r2.f9, nullValue());
assertThat(r2.f10, nullValue());
}
@Test
void deserializeNullValuesAsDefaultValuesIfInputNullsIsFalse() {
RecordSerializer<R2> serializer = newSerializer(R2.class, b -> b.inputNulls(false));
Map<String, Object> serialized = new HashMap<>();
for (int i = 1; i <= 10; i++) {
serialized.put("f" + i, null);
}
R2 r2 = serializer.deserialize(serialized);
assertThat(r2.f1, is(false));
assertThat(r2.f2, is('\0'));
assertThat(r2.f3, is((byte) 0));
assertThat(r2.f4, is((short) 0));
assertThat(r2.f5, is(0));
assertThat(r2.f6, is(0L));
assertThat(r2.f7, is(0f));
assertThat(r2.f8, is(0d));
assertThat(r2.f9, nullValue());
assertThat(r2.f10, nullValue());
}
@Test
void deserializeNullValuesAsNullIfInputNullsIsTrue() {
record R(Integer i, String s, R1 r1) {}
RecordSerializer<R> serializer = newSerializer(R.class, b -> b.inputNulls(true));
Map<String, Object> serialized = new HashMap<>();
for (int i = 1; i <= 10; i++) {
serialized.put("f" + i, null);
}
R r = serializer.deserialize(serialized);
assertThat(r.i, nullValue());
assertThat(r.s, nullValue());
assertThat(r.r1, nullValue());
}
@Test
void deserializeNullValuesAsNullIfInputNullsIsTrueFailsForPrimitiveFields() {
RecordSerializer<R2> serializer = newSerializer(R2.class, builder -> builder.inputNulls(true));
RecordComponent[] components = R2.class.getRecordComponents();
Map<String, Object> serialized = new LinkedHashMap<>();
// initialize map to be deserialized with default values
for (int i = 1; i <= 10; i++) {
RecordComponent component = components[i - 1];
Class<?> componentType = component.getType();
Object value = Reflect.getDefaultValue(componentType);
// CharacterSerializer expects String
serialized.put(component.getName(), componentType == char.class ? "\0" : value);
}
for (int i = 1; i <= 8; i++) {
RecordComponent component = components[i - 1];
Class<?> componentType = component.getType();
Object tmp = serialized.remove(component.getName());
serialized.put(component.getName(), null);
assertThrowsConfigurationException(
() -> serializer.deserialize(serialized),
("Cannot set component '%s %s' of record type " +
"'class de.exlll.configlib.RecordSerializerTest$R2' to null. " +
"Primitive types cannot be assigned null values.")
.formatted(componentType.getSimpleName(), component.getName())
);
serialized.put(component.getName(), tmp);
}
}
@Test
void deserializeInvalidType() {
record B3(String s, List<List<String>> l) {}
RecordSerializer<B3> serializer = newSerializer(B3.class);
assertThrowsConfigurationException(
() -> serializer.deserialize(Map.of("s", (byte) 3)),
"Deserialization of value '3' with type 'class java.lang.Byte' for component " +
"'java.lang.String s' of record 'class de.exlll.configlib.RecordSerializerTest$1B3' " +
"failed.\nThe type of the object to be deserialized does not match the type " +
"the deserializer expects."
);
assertThrowsConfigurationException(
() -> serializer.deserialize(Map.of("l", List.of(List.of(3)))),
"Deserialization of value '[[3]]' with type " +
"'class java.util.ImmutableCollections$List12' for component " +
"'java.util.List l' of record 'class de.exlll.configlib.RecordSerializerTest$1B3' " +
"failed.\nThe type of the object to be deserialized does not match the type " +
"the deserializer expects."
);
}
}

@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
@ -12,11 +13,28 @@ import java.util.HashSet;
import static de.exlll.configlib.TestUtils.assertThrowsRuntimeException;
import static de.exlll.configlib.TestUtils.getField;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.*;
class ReflectTest {
@Test
void defaultValues() {
assertThat(Reflect.getDefaultValue(boolean.class), is(false));
assertThat(Reflect.getDefaultValue(byte.class), is((byte) 0));
assertThat(Reflect.getDefaultValue(char.class), is('\0'));
assertThat(Reflect.getDefaultValue(short.class), is((short) 0));
assertThat(Reflect.getDefaultValue(int.class), is(0));
assertThat(Reflect.getDefaultValue(long.class), is(0L));
assertThat(Reflect.getDefaultValue(float.class), is(0f));
assertThat(Reflect.getDefaultValue(double.class), is(0d));
assertThat(Reflect.getDefaultValue(Boolean.class), nullValue());
assertThat(Reflect.getDefaultValue(Integer.class), nullValue());
assertThat(Reflect.getDefaultValue(Character.class), nullValue());
assertThat(Reflect.getDefaultValue(Double.class), nullValue());
assertThat(Reflect.getDefaultValue(Object.class), nullValue());
}
static class B1 {
B1(int i) {}
}
@ -83,6 +101,28 @@ class ReflectTest {
assertThat(value, is(10));
}
@Test
void getValueRecord() {
record R(float f) {}
float value = (float) Reflect.getValue(R.class.getRecordComponents()[0], new R(10f));
assertThat(value, is(10f));
}
@Test
void getValueRecordThrowsException() {
record R(float f) {
public float f() {
throw new ConfigurationException("TEST");
}
}
assertThrowsRuntimeException(
() -> Reflect.getValue(R.class.getRecordComponents()[0], new R(10f)),
"Invocation of method 'public float de.exlll.configlib.ReflectTest$2R.f()' " +
"on record 'R[f=10.0]' failed."
);
}
@Test
void setValue() {
class A {
@ -214,4 +254,55 @@ class ReflectTest {
assertThat(Reflect.isIgnored(fieldA), is(true));
assertThat(Reflect.isIgnored(fieldB), is(false));
}
record R1(int i, float f) {
R1(int i) {
this(i, 0);
}
R1(float f) {
this(0, f);
}
R1(int i, float f, String s) {
this(i, f);
}
}
@Test
void getCanonicalConstructor() throws NoSuchMethodException {
Constructor<R1> constructor = Reflect.getCanonicalConstructor(R1.class);
Class<?>[] classes = constructor.getParameterTypes();
assertThat(classes.length, is(2));
assertThat(classes[0], equalTo(int.class));
assertThat(classes[1], equalTo(float.class));
}
@Test
void newRecord1() {
R1 r = Reflect.newRecord(R1.class, 1, 2f);
assertThat(r.i, is(1));
assertThat(r.f, is(2f));
}
record R2() {}
@Test
void newRecord2() {
Reflect.newRecord(R2.class);
}
record R3(String s) {
R3 {
throw new IllegalArgumentException("Illegal: " + s);
}
}
@Test
void newRecordWithThrowingCtor() {
assertThrowsRuntimeException(
() -> Reflect.newRecord(R3.class, ""),
"The canonical constructor of record type 'R3' threw an exception."
);
}
}

@ -0,0 +1,225 @@
package de.exlll.configlib;
import de.exlll.configlib.configurations.ExampleConfigurationA2;
import de.exlll.configlib.configurations.ExampleConfigurationB1;
import de.exlll.configlib.configurations.ExampleConfigurationB2;
import de.exlll.configlib.configurations.ExampleEnum;
import org.junit.jupiter.api.Test;
import java.awt.Point;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import static de.exlll.configlib.Serializers.*;
import static de.exlll.configlib.TestUtils.assertThrowsConfigurationException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
class SerializerMapperTest {
private static SerializerMapper newMapper(Class<?> cls) {
return newMapper(cls, builder -> {});
}
private static SerializerMapper newMapper(
Class<?> cls,
Consumer<ConfigurationProperties.Builder<?>> propertiesConfigurer
) {
var builder = ConfigurationProperties.newBuilder();
builder.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
propertiesConfigurer.accept(builder);
return new SerializerMapper(cls, builder.build());
}
@Test
void requireConfigurationOrRecord() {
ConfigurationProperties properties = ConfigurationProperties.newBuilder().build();
TestUtils.assertThrowsConfigurationException(
() -> new SerializerMapper(Object.class, properties),
"Type 'Object' must be a configuration or record type."
);
}
@Test
void buildSerializerMapForConfigurationFiltersFields() {
Map<String, Serializer<?, ?>> serializers = newMapper(ExampleConfigurationA2.class)
.buildSerializerMap();
assertThat(serializers.get("a1_staticFinalInt"), nullValue());
assertThat(serializers.get("a1_staticInt"), nullValue());
assertThat(serializers.get("a1_finalInt"), nullValue());
assertThat(serializers.get("a1_transientInt"), nullValue());
assertThat(serializers.get("a1_ignoredInt"), nullValue());
assertThat(serializers.get("a1_ignoredString"), nullValue());
assertThat(serializers.get("a1_ignoredListString"), nullValue());
assertThat(serializers.get("a2_staticFinalInt"), nullValue());
assertThat(serializers.get("a2_staticInt"), nullValue());
assertThat(serializers.get("a2_finalInt"), nullValue());
assertThat(serializers.get("a2_transientInt"), nullValue());
assertThat(serializers.get("a2_ignoredInt"), nullValue());
assertThat(serializers.get("a2_ignoredString"), nullValue());
assertThat(serializers.get("a2_ignoredListString"), nullValue());
}
@Test
void buildSerializerMapForConfigurationIgnoresFormatter() {
Map<String, Serializer<?, ?>> serializers = newMapper(
ExampleConfigurationA2.class,
props -> props.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
).buildSerializerMap();
assertThat(serializers.get("A2_PRIM_BOOL"), nullValue());
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
}
@Test
void buildSerializerMapForConfiguration() {
Map<String, Serializer<?, ?>> serializers = newMapper(ExampleConfigurationA2.class)
.buildSerializerMap();
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("a2_refChar"), instanceOf(CharacterSerializer.class));
assertThat(serializers.get("a2_string"), instanceOf(StringSerializer.class));
assertThat(serializers.get("a2_Enm"), instanceOf(EnumSerializer.class));
ConfigurationSerializer<?> serializerB1 =
(ConfigurationSerializer<?>) serializers.get("a2_b1");
ConfigurationSerializer<?> serializerB2 =
(ConfigurationSerializer<?>) serializers.get("a2_b2");
assertThat(serializerB1.getConfigurationType(), equalTo(ExampleConfigurationB1.class));
assertThat(serializerB2.getConfigurationType(), equalTo(ExampleConfigurationB2.class));
Serializers.ListSerializer<?, ?> serializerList =
(Serializers.ListSerializer<?, ?>) serializers.get("a2_listByte");
Serializers.ArraySerializer<?, ?> serializerArray =
(Serializers.ArraySerializer<?, ?>) serializers.get("a2_arrayString");
Serializers.SetAsListSerializer<?, ?> serializerSet =
(Serializers.SetAsListSerializer<?, ?>) serializers.get("a2_setBigInteger");
Serializers.MapSerializer<?, ?, ?, ?> serializerMap =
(Serializers.MapSerializer<?, ?, ?, ?>) serializers.get("a2_mapLocalTimeLocalTime");
assertThat(
serializers.get("a2_arrayPrimDouble"),
instanceOf(PrimitiveDoubleArraySerializer.class)
);
assertThat(serializerList.getElementSerializer(), instanceOf(NumberSerializer.class));
assertThat(serializerArray.getElementSerializer(), instanceOf(StringSerializer.class));
assertThat(serializerSet.getElementSerializer(), instanceOf(BigIntegerSerializer.class));
assertThat(serializerMap.getKeySerializer(), instanceOf(LocalTimeSerializer.class));
assertThat(serializerMap.getValueSerializer(), instanceOf(LocalTimeSerializer.class));
assertThat(serializers.get("a2_point"), sameInstance(TestUtils.POINT_SERIALIZER));
}
private record R1(int integer, boolean bool) {}
@Test
void buildSerializerMapForRecordIgnoresFormatter() {
Map<String, Serializer<?, ?>> serializers = newMapper(
R1.class,
props -> props.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
).buildSerializerMap();
assertThat(serializers.get("INTEGER"), nullValue());
assertThat(serializers.get("BOOL"), nullValue());
assertThat(serializers.get("integer"), instanceOf(NumberSerializer.class));
assertThat(serializers.get("bool"), instanceOf(BooleanSerializer.class));
}
private record R2(
boolean primBool,
Character refChar,
String string,
ExampleEnum enm,
ExampleConfigurationB1 b1,
ExampleConfigurationB2 b2,
List<Byte> listByte,
String[] arrayString,
Set<BigInteger> setBigInteger,
Map<UUID, UUID> mapUuidUuid,
double[] arrayDouble,
Point point
) {}
@Test
void buildSerializerMapForRecord() {
Map<String, Serializer<?, ?>> serializers = newMapper(R2.class)
.buildSerializerMap();
assertThat(serializers.get("primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("refChar"), instanceOf(CharacterSerializer.class));
assertThat(serializers.get("string"), instanceOf(StringSerializer.class));
assertThat(serializers.get("enm"), instanceOf(EnumSerializer.class));
ConfigurationSerializer<?> serializerB1 =
(ConfigurationSerializer<?>) serializers.get("b1");
ConfigurationSerializer<?> serializerB2 =
(ConfigurationSerializer<?>) serializers.get("b2");
assertThat(serializerB1.getConfigurationType(), equalTo(ExampleConfigurationB1.class));
assertThat(serializerB2.getConfigurationType(), equalTo(ExampleConfigurationB2.class));
Serializers.ListSerializer<?, ?> serializerList =
(Serializers.ListSerializer<?, ?>) serializers.get("listByte");
Serializers.ArraySerializer<?, ?> serializerArray =
(Serializers.ArraySerializer<?, ?>) serializers.get("arrayString");
Serializers.SetAsListSerializer<?, ?> serializerSet =
(Serializers.SetAsListSerializer<?, ?>) serializers.get("setBigInteger");
Serializers.MapSerializer<?, ?, ?, ?> serializerMap =
(Serializers.MapSerializer<?, ?, ?, ?>) serializers.get("mapUuidUuid");
assertThat(
serializers.get("arrayDouble"),
instanceOf(PrimitiveDoubleArraySerializer.class)
);
assertThat(serializerList.getElementSerializer(), instanceOf(NumberSerializer.class));
assertThat(serializerArray.getElementSerializer(), instanceOf(StringSerializer.class));
assertThat(serializerSet.getElementSerializer(), instanceOf(BigIntegerSerializer.class));
assertThat(serializerMap.getKeySerializer(), instanceOf(UuidSerializer.class));
assertThat(serializerMap.getValueSerializer(), instanceOf(UuidSerializer.class));
assertThat(serializers.get("point"), sameInstance(TestUtils.POINT_SERIALIZER));
}
@Configuration
static final class Recursive1 {
Recursive2 recursive2;
}
@Configuration
static final class Recursive2 {
Recursive1 recursive1;
}
@Test
void buildSerializerMapForConfigurationPreventsRecursiveDefinitions() {
assertThrowsConfigurationException(
() -> newMapper(Recursive1.class).buildSerializerMap(),
"Recursive type definitions are not supported."
);
}
record RecursiveRecord1(RecursiveRecord2 recursiveRecord2) {}
record RecursiveRecord2(RecursiveRecord1 recursiveRecord1) {}
record RecursiveRecord3(RecursiveRecord3 recursiveRecord3) {}
@Test
void buildSerializerMapForRecordPreventsRecursiveDefinitions() {
assertThrowsConfigurationException(
() -> newMapper(RecursiveRecord1.class).buildSerializerMap(),
"Recursive type definitions are not supported."
);
assertThrowsConfigurationException(
() -> newMapper(RecursiveRecord3.class).buildSerializerMap(),
"Recursive type definitions are not supported."
);
}
}

@ -210,6 +210,21 @@ class SerializerSelectorTest {
assertThat(serializer.getConfigurationType(), equalTo(A.class));
}
@Test
void selectSerializerRecord() {
record R(int i) {}
var serializer = (RecordSerializer<?>) SELECTOR.select(R.class);
assertThat(serializer.getRecordType(), equalTo(R.class));
}
@Test
void recordSerializerTakesPrecedenceOverConfigurationSerializer() {
@Configuration
record R(int i) {}
var serializer = (RecordSerializer<?>) SELECTOR.select(R.class);
assertThat(serializer.getRecordType(), equalTo(R.class));
}
@Test
void selectSerializerMissingType() {
assertThrowsConfigurationException(

@ -41,7 +41,7 @@ class YamlConfigurationStoreTest {
.header("The\nHeader")
.footer("The\nFooter")
.outputNulls(true)
.setFieldFormatter(field -> field.getName().toUpperCase())
.setNameFormatter(String::toUpperCase)
.build();
YamlConfigurationStore<A> store = new YamlConfigurationStore<>(A.class, properties);
store.save(new A(), yamlFile);
@ -72,7 +72,7 @@ class YamlConfigurationStoreTest {
void load() throws IOException {
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
.inputNulls(true)
.setFieldFormatter(field -> field.getName().toUpperCase())
.setNameFormatter(String::toUpperCase)
.build();
YamlConfigurationStore<B> store = new YamlConfigurationStore<>(B.class, properties);

@ -72,6 +72,8 @@ public class ExampleConfigurationA1 {
/* OTHER CONFIGURATIONS */
private ExampleConfigurationB1 a1_b1;
private ExampleConfigurationB2 a1_b2;
private ExampleRecord1 a1_r1;
private ExampleRecord2 a1_r2;
/* COLLECTIONS: Lists */
private List<Boolean> a1_listBoolean;
@ -97,6 +99,8 @@ public class ExampleConfigurationA1 {
private List<ExampleEnum> a1_listEnm;
private List<ExampleConfigurationB1> a1_listB1;
private List<ExampleConfigurationB2> a1_listB2;
private List<ExampleRecord1> a1_listR1;
private List<ExampleRecord2> a1_listR2;
/* COLLECTIONS: Arrays */
private boolean[] a1_arrayPrimBoolean;
@ -125,6 +129,8 @@ public class ExampleConfigurationA1 {
private ExampleEnum[] a1_arrayEnm;
private ExampleConfigurationB1[] a1_arrayB1;
private ExampleConfigurationB2[] a1_arrayB2;
private ExampleRecord1[] a1_arrayR1;
private ExampleRecord2[] a1_arrayR2;
/* COLLECTIONS: Sets */
private Set<Boolean> a1_setBoolean;
@ -145,6 +151,8 @@ public class ExampleConfigurationA1 {
private Set<ExampleEnum> a1_setEnm;
private Set<ExampleConfigurationB1> a1_setB1;
private Set<ExampleConfigurationB2> a1_setB2;
private Set<ExampleRecord1> a1_setR1;
private Set<ExampleRecord2> a1_setR2;
/* COLLECTIONS: Maps */
private Map<Boolean, Boolean> a1_mapBooleanBoolean;
@ -166,6 +174,8 @@ public class ExampleConfigurationA1 {
private Map<Integer, ExampleConfigurationB1> a1_mapIntegerB1;
private Map<ExampleEnum, ExampleConfigurationB2> a1_mapEnmB2;
private Map<String, ExampleRecord1> a1_mapStringR1;
private Map<String, ExampleRecord2> a1_mapStringR2;
/* COLLECTIONS: Empty */
private List<Boolean> a1_listEmpty;
@ -218,6 +228,8 @@ public class ExampleConfigurationA1 {
private ExampleEnum[][] a1_arrayArrayEnm;
private ExampleConfigurationB1[][] a1_arrayArrayB1;
private ExampleConfigurationB2[][] a1_arrayArrayB2;
private ExampleRecord1[][] a1_arrayArrayR1;
private ExampleRecord2[][] a1_arrayArrayR2;
/* CUSTOM CONVERTERS */
private Point a1_point;
@ -1565,4 +1577,100 @@ public class ExampleConfigurationA1 {
public void setA1_listUri(List<URI> a1_listUri) {
this.a1_listUri = a1_listUri;
}
public ExampleRecord1 getA1_r1() {
return a1_r1;
}
public void setA1_r1(ExampleRecord1 a1_r1) {
this.a1_r1 = a1_r1;
}
public ExampleRecord2 getA1_r2() {
return a1_r2;
}
public void setA1_r2(ExampleRecord2 a1_r2) {
this.a1_r2 = a1_r2;
}
public List<ExampleRecord1> getA1_listR1() {
return a1_listR1;
}
public void setA1_listR1(List<ExampleRecord1> a1_listR1) {
this.a1_listR1 = a1_listR1;
}
public List<ExampleRecord2> getA1_listR2() {
return a1_listR2;
}
public void setA1_listR2(List<ExampleRecord2> a1_listR2) {
this.a1_listR2 = a1_listR2;
}
public ExampleRecord1[] getA1_arrayR1() {
return a1_arrayR1;
}
public void setA1_arrayR1(ExampleRecord1[] a1_arrayR1) {
this.a1_arrayR1 = a1_arrayR1;
}
public ExampleRecord2[] getA1_arrayR2() {
return a1_arrayR2;
}
public void setA1_arrayR2(ExampleRecord2[] a1_arrayR2) {
this.a1_arrayR2 = a1_arrayR2;
}
public Set<ExampleRecord1> getA1_setR1() {
return a1_setR1;
}
public void setA1_setR1(Set<ExampleRecord1> a1_setR1) {
this.a1_setR1 = a1_setR1;
}
public Set<ExampleRecord2> getA1_setR2() {
return a1_setR2;
}
public void setA1_setR2(Set<ExampleRecord2> a1_setR2) {
this.a1_setR2 = a1_setR2;
}
public Map<String, ExampleRecord1> getA1_mapStringR1() {
return a1_mapStringR1;
}
public void setA1_mapStringR1(Map<String, ExampleRecord1> a1_mapStringR1) {
this.a1_mapStringR1 = a1_mapStringR1;
}
public Map<String, ExampleRecord2> getA1_mapStringR2() {
return a1_mapStringR2;
}
public void setA1_mapStringR2(Map<String, ExampleRecord2> a1_mapStringR2) {
this.a1_mapStringR2 = a1_mapStringR2;
}
public ExampleRecord1[][] getA1_arrayArrayR1() {
return a1_arrayArrayR1;
}
public void setA1_arrayArrayR1(ExampleRecord1[][] a1_arrayArrayR1) {
this.a1_arrayArrayR1 = a1_arrayArrayR1;
}
public ExampleRecord2[][] getA1_arrayArrayR2() {
return a1_arrayArrayR2;
}
public void setA1_arrayArrayR2(ExampleRecord2[][] a1_arrayArrayR2) {
this.a1_arrayArrayR2 = a1_arrayArrayR2;
}
}

@ -70,6 +70,8 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
/* OTHER CONFIGURATIONS */
private ExampleConfigurationB1 a2_b1;
private ExampleConfigurationB2 a2_b2;
private ExampleRecord1 a2_r1;
private ExampleRecord2 a2_r2;
/* COLLECTIONS: Lists */
private List<Boolean> a2_listBoolean;
@ -95,6 +97,8 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
private List<ExampleEnum> a2_listEnm;
private List<ExampleConfigurationB1> a2_listB1;
private List<ExampleConfigurationB2> a2_listB2;
private List<ExampleRecord1> a2_listR1;
private List<ExampleRecord2> a2_listR2;
/* COLLECTIONS: Arrays */
private boolean[] a2_arrayPrimBoolean;
@ -123,6 +127,8 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
private ExampleEnum[] a2_arrayEnm;
private ExampleConfigurationB1[] a2_arrayB1;
private ExampleConfigurationB2[] a2_arrayB2;
private ExampleRecord1[] a2_arrayR1;
private ExampleRecord2[] a2_arrayR2;
/* COLLECTIONS: Sets */
private Set<Boolean> a2_setBoolean;
@ -143,6 +149,8 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
private Set<ExampleEnum> a2_setEnm;
private Set<ExampleConfigurationB1> a2_setB1;
private Set<ExampleConfigurationB2> a2_setB2;
private Set<ExampleRecord1> a2_setR1;
private Set<ExampleRecord2> a2_setR2;
/* COLLECTIONS: Maps */
private Map<Boolean, Boolean> a2_mapBooleanBoolean;
@ -164,6 +172,8 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
private Map<Integer, ExampleConfigurationB1> a2_mapIntegerB1;
private Map<ExampleEnum, ExampleConfigurationB2> a2_mapEnmB2;
private Map<String, ExampleRecord1> a2_mapStringR1;
private Map<String, ExampleRecord2> a2_mapStringR2;
/* COLLECTIONS: Empty */
private List<Boolean> a2_listEmpty;
@ -216,6 +226,8 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
private ExampleEnum[][] a2_arrayArrayEnm;
private ExampleConfigurationB1[][] a2_arrayArrayB1;
private ExampleConfigurationB2[][] a2_arrayArrayB2;
private ExampleRecord1[][] a2_arrayArrayR1;
private ExampleRecord2[][] a2_arrayArrayR2;
/* CUSTOM CONVERTERS */
private Point a2_point;
@ -1597,4 +1609,100 @@ public final class ExampleConfigurationA2 extends ExampleConfigurationA1 {
public void setA2_listUri(List<URI> a2_listUri) {
this.a2_listUri = a2_listUri;
}
public ExampleRecord1 getA2_r1() {
return a2_r1;
}
public void setA2_r1(ExampleRecord1 a2_r1) {
this.a2_r1 = a2_r1;
}
public ExampleRecord2 getA2_r2() {
return a2_r2;
}
public void setA2_r2(ExampleRecord2 a2_r2) {
this.a2_r2 = a2_r2;
}
public List<ExampleRecord1> getA2_listR1() {
return a2_listR1;
}
public void setA2_listR1(List<ExampleRecord1> a2_listR1) {
this.a2_listR1 = a2_listR1;
}
public List<ExampleRecord2> getA2_listR2() {
return a2_listR2;
}
public void setA2_listR2(List<ExampleRecord2> a2_listR2) {
this.a2_listR2 = a2_listR2;
}
public ExampleRecord1[] getA2_arrayR1() {
return a2_arrayR1;
}
public void setA2_arrayR1(ExampleRecord1[] a2_arrayR1) {
this.a2_arrayR1 = a2_arrayR1;
}
public ExampleRecord2[] getA2_arrayR2() {
return a2_arrayR2;
}
public void setA2_arrayR2(ExampleRecord2[] a2_arrayR2) {
this.a2_arrayR2 = a2_arrayR2;
}
public Set<ExampleRecord1> getA2_setR1() {
return a2_setR1;
}
public void setA2_setR1(Set<ExampleRecord1> a2_setR1) {
this.a2_setR1 = a2_setR1;
}
public Set<ExampleRecord2> getA2_setR2() {
return a2_setR2;
}
public void setA2_setR2(Set<ExampleRecord2> a2_setR2) {
this.a2_setR2 = a2_setR2;
}
public Map<String, ExampleRecord1> getA2_mapStringR1() {
return a2_mapStringR1;
}
public void setA2_mapStringR1(Map<String, ExampleRecord1> a2_mapStringR1) {
this.a2_mapStringR1 = a2_mapStringR1;
}
public Map<String, ExampleRecord2> getA2_mapStringR2() {
return a2_mapStringR2;
}
public void setA2_mapStringR2(Map<String, ExampleRecord2> a2_mapStringR2) {
this.a2_mapStringR2 = a2_mapStringR2;
}
public ExampleRecord1[][] getA2_arrayArrayR1() {
return a2_arrayArrayR1;
}
public void setA2_arrayArrayR1(ExampleRecord1[][] a2_arrayArrayR1) {
this.a2_arrayArrayR1 = a2_arrayArrayR1;
}
public ExampleRecord2[][] getA2_arrayArrayR2() {
return a2_arrayArrayR2;
}
public void setA2_arrayArrayR2(ExampleRecord2[][] a2_arrayArrayR2) {
this.a2_arrayArrayR2 = a2_arrayArrayR2;
}
}

@ -70,6 +70,33 @@ public final class ExampleConfigurationsSerialized {
)),
entry("b2_listPoint", List.of("2:3", "4:5"))
);
public static final Map<String, ?> EXAMPLE_RECORD1_1 = entriesAsMap(
entry("i", 1L),
entry("d", 2d),
entry("enm", "A"),
entry("listUuid", List.of("d50f3bdd-ac66-4b74-a01f-4617b24d68c0", "d50f3bdd-ac66-4b74-a01f-4617b24d68c1")),
entry("arrayArrayFloat", List.of(List.of(1d, 2d), List.of(3d, 4d))),
entry("b1", EXAMPLE_CONFIGURATION_B1_1)
);
public static final Map<String, ?> EXAMPLE_RECORD1_2 = entriesAsMap(
entry("i", 2L),
entry("d", 3d),
entry("enm", "B"),
entry("listUuid", List.of("d50f3bdd-ac66-4b74-a01f-4617b24d68c1", "d50f3bdd-ac66-4b74-a01f-4617b24d68c2")),
entry("arrayArrayFloat", List.of(List.of(2d, 3d), List.of(4d, 5d))),
entry("b1", EXAMPLE_CONFIGURATION_B1_2)
);
public static final Map<String, ?> EXAMPLE_RECORD2_1 = entriesAsMap(
entry("b", true),
entry("r1", EXAMPLE_RECORD1_1)
);
public static final Map<String, ?> EXAMPLE_RECORD2_2 = entriesAsMap(
entry("b", false),
entry("r1", EXAMPLE_RECORD1_2)
);
public static final Map<String, ?> EXAMPLE_CONFIGURATION_A1 = TestUtils.entriesAsMap(
entry("a1_primBool", true),
entry("a1_primChar", "a"),
@ -102,6 +129,8 @@ public final class ExampleConfigurationsSerialized {
entry("a1_Enm", "A"),
entry("a1_b1", EXAMPLE_CONFIGURATION_B1_1),
entry("a1_b2", EXAMPLE_CONFIGURATION_B2_1),
entry("a1_r1", EXAMPLE_RECORD1_1),
entry("a1_r2", EXAMPLE_RECORD2_1),
entry("a1_listBoolean", List.of(true, false, true)),
entry("a1_listChar", List.of("a", "b", "c")),
entry("a1_listByte", List.of(1L, 2L, 3L)),
@ -125,6 +154,8 @@ public final class ExampleConfigurationsSerialized {
entry("a1_listEnm", List.of("A", "B", "C")),
entry("a1_listB1", List.of(EXAMPLE_CONFIGURATION_B1_1)),
entry("a1_listB2", List.of(EXAMPLE_CONFIGURATION_B2_1)),
entry("a1_listR1", List.of(EXAMPLE_RECORD1_1)),
entry("a1_listR2", List.of(EXAMPLE_RECORD2_1)),
entry("a1_arrayPrimBoolean", List.of(true, false, true)),
entry("a1_arrayPrimChar", List.of("a", "b", "c")),
entry("a1_arrayPrimByte", List.of(1L, 2L, 3L)),
@ -151,6 +182,8 @@ public final class ExampleConfigurationsSerialized {
entry("a1_arrayEnm", List.of("A", "B", "C")),
entry("a1_arrayB1", List.of(EXAMPLE_CONFIGURATION_B1_1)),
entry("a1_arrayB2", List.of(EXAMPLE_CONFIGURATION_B2_1)),
entry("a1_arrayR1", List.of(EXAMPLE_RECORD1_1)),
entry("a1_arrayR2", List.of(EXAMPLE_RECORD2_1)),
entry("a1_setBoolean", List.of(true)),
entry("a1_setChar", asList("a", "b", "c")),
entry("a1_setByte", asList(1L, 2L, 3L)),
@ -169,6 +202,8 @@ public final class ExampleConfigurationsSerialized {
entry("a1_setEnm", asList("A", "B", "C")),
entry("a1_setB1", List.of(EXAMPLE_CONFIGURATION_B1_1)),
entry("a1_setB2", List.of(EXAMPLE_CONFIGURATION_B2_1)),
entry("a1_setR1", List.of(EXAMPLE_RECORD1_1)),
entry("a1_setR2", List.of(EXAMPLE_RECORD2_1)),
entry("a1_mapBooleanBoolean", asMap(true, true, false, false)),
entry("a1_mapCharChar", asMap("a", "b", "c", "d")),
entry("a1_mapByteByte", asMap(1L, 2L, 3L, 4L)),
@ -187,6 +222,8 @@ public final class ExampleConfigurationsSerialized {
entry("a1_mapEnmEnm", asMap("A", "B", "C", "D")),
entry("a1_mapIntegerB1", asMap(1L, EXAMPLE_CONFIGURATION_B1_1, 2L, EXAMPLE_CONFIGURATION_B1_2)),
entry("a1_mapEnmB2", asMap("A", EXAMPLE_CONFIGURATION_B2_1, "B", EXAMPLE_CONFIGURATION_B2_2)),
entry("a1_mapStringR1", asMap("1", EXAMPLE_RECORD1_1, "2", EXAMPLE_RECORD1_2)),
entry("a1_mapStringR2", asMap("1", EXAMPLE_RECORD2_1, "2", EXAMPLE_RECORD2_2)),
entry("a1_listEmpty", Collections.emptyList()),
entry("a1_arrayEmpty", Collections.emptyList()),
entry("a1_setEmpty", Collections.emptyList()),
@ -230,6 +267,8 @@ public final class ExampleConfigurationsSerialized {
entry("a1_arrayArrayEnm", List.of(List.of(), List.of("A"), List.of("A", "B"))),
entry("a1_arrayArrayB1", List.of(List.of(), List.of(EXAMPLE_CONFIGURATION_B1_1))),
entry("a1_arrayArrayB2", List.of(List.of(), List.of(EXAMPLE_CONFIGURATION_B2_1))),
entry("a1_arrayArrayR1", List.of(List.of(), List.of(EXAMPLE_RECORD1_1))),
entry("a1_arrayArrayR2", List.of(List.of(), List.of(EXAMPLE_RECORD2_1))),
entry("a1_point", "0:1"),
entry("a1_listPoint", List.of("0:1", "0:2", "0:3")),
entry("a1_arrayPoint", List.of("0:1", "0:2", "0:3")),
@ -268,6 +307,8 @@ public final class ExampleConfigurationsSerialized {
entry("a2_Enm", "B"),
entry("a2_b1", EXAMPLE_CONFIGURATION_B1_2),
entry("a2_b2", EXAMPLE_CONFIGURATION_B2_2),
entry("a2_r1", EXAMPLE_RECORD1_2),
entry("a2_r2", EXAMPLE_RECORD2_2),
entry("a2_listBoolean", List.of(false, true, false)),
entry("a2_listChar", List.of("d", "e", "f")),
entry("a2_listByte", List.of(2L, 3L, 4L)),
@ -291,6 +332,8 @@ public final class ExampleConfigurationsSerialized {
entry("a2_listEnm", List.of("B", "C", "D")),
entry("a2_listB1", List.of(EXAMPLE_CONFIGURATION_B1_1, EXAMPLE_CONFIGURATION_B1_2)),
entry("a2_listB2", List.of(EXAMPLE_CONFIGURATION_B2_1, EXAMPLE_CONFIGURATION_B2_2)),
entry("a2_listR1", List.of(EXAMPLE_RECORD1_1, EXAMPLE_RECORD1_2)),
entry("a2_listR2", List.of(EXAMPLE_RECORD2_1, EXAMPLE_RECORD2_2)),
entry("a2_arrayPrimBoolean", List.of(false, true, false)),
entry("a2_arrayPrimChar", List.of("d", "e", "f")),
entry("a2_arrayPrimByte", List.of(2L, 3L, 4L)),
@ -317,6 +360,8 @@ public final class ExampleConfigurationsSerialized {
entry("a2_arrayEnm", List.of("B", "C", "D")),
entry("a2_arrayB1", List.of(EXAMPLE_CONFIGURATION_B1_1, EXAMPLE_CONFIGURATION_B1_2)),
entry("a2_arrayB2", List.of(EXAMPLE_CONFIGURATION_B2_1, EXAMPLE_CONFIGURATION_B2_2)),
entry("a2_arrayR1", List.of(EXAMPLE_RECORD1_1, EXAMPLE_RECORD1_2)),
entry("a2_arrayR2", List.of(EXAMPLE_RECORD2_1, EXAMPLE_RECORD2_2)),
entry("a2_setBoolean", List.of(false)),
entry("a2_setChar", asList("d", "e", "f")),
entry("a2_setByte", asList(2L, 3L, 4L)),
@ -335,6 +380,8 @@ public final class ExampleConfigurationsSerialized {
entry("a2_setEnm", asList("B", "C", "D")),
entry("a2_setB1", asList(EXAMPLE_CONFIGURATION_B1_1, EXAMPLE_CONFIGURATION_B1_2)),
entry("a2_setB2", asList(EXAMPLE_CONFIGURATION_B2_1, EXAMPLE_CONFIGURATION_B2_2)),
entry("a2_setR1", asList(EXAMPLE_RECORD1_1, EXAMPLE_RECORD1_2)),
entry("a2_setR2", asList(EXAMPLE_RECORD2_1, EXAMPLE_RECORD2_2)),
entry("a2_mapBooleanBoolean", asMap(true, true, false, false)),
entry("a2_mapCharChar", asMap("b", "c", "d", "e")),
entry("a2_mapByteByte", asMap(2L, 3L, 4L, 5L)),
@ -353,6 +400,8 @@ public final class ExampleConfigurationsSerialized {
entry("a2_mapEnmEnm", asMap("B", "C", "D", "E")),
entry("a2_mapIntegerB1", asMap(2L, EXAMPLE_CONFIGURATION_B1_1, 3L, EXAMPLE_CONFIGURATION_B1_2)),
entry("a2_mapEnmB2", asMap("B", EXAMPLE_CONFIGURATION_B2_1, "C", EXAMPLE_CONFIGURATION_B2_2)),
entry("a2_mapStringR1", asMap("2", EXAMPLE_RECORD1_1, "3", EXAMPLE_RECORD1_2)),
entry("a2_mapStringR2", asMap("2", EXAMPLE_RECORD2_1, "3", EXAMPLE_RECORD2_2)),
entry("a2_listEmpty", Collections.emptyList()),
entry("a2_arrayEmpty", Collections.emptyList()),
entry("a2_setEmpty", Collections.emptyList()),
@ -396,6 +445,8 @@ public final class ExampleConfigurationsSerialized {
entry("a2_arrayArrayEnm", List.of(List.of("A"), List.of("A", "B"), List.of("A", "B", "C"))),
entry("a2_arrayArrayB1", List.of(List.of(EXAMPLE_CONFIGURATION_B1_1), List.of(EXAMPLE_CONFIGURATION_B1_2))),
entry("a2_arrayArrayB2", List.of(List.of(EXAMPLE_CONFIGURATION_B2_1), List.of(EXAMPLE_CONFIGURATION_B2_2))),
entry("a2_arrayArrayR1", List.of(List.of(EXAMPLE_RECORD1_1), List.of(EXAMPLE_RECORD1_2))),
entry("a2_arrayArrayR2", List.of(List.of(EXAMPLE_RECORD2_1), List.of(EXAMPLE_RECORD2_2))),
entry("a2_point", "0:2"),
entry("a2_listPoint", List.of("0:2", "0:3", "0:4")),
entry("a2_arrayPoint", List.of("0:2", "0:3", "0:4")),

@ -97,6 +97,27 @@ public final class ExampleInitializer {
}
}
public static final ExampleRecord1 EXAMPLE_RECORD1_1 = new ExampleRecord1(
1,
2d,
ExampleEnum.A,
List.of(UUID_1, UUID_2),
new float[][]{{1f, 2f}, {3f, 4f}},
B1_1
);
public static final ExampleRecord1 EXAMPLE_RECORD1_2 = new ExampleRecord1(
2,
3d,
ExampleEnum.B,
List.of(UUID_2, UUID_3),
new float[][]{{2f, 3f}, {4f, 5f}},
B1_2
);
public static final ExampleRecord2 EXAMPLE_RECORD2_1 = new ExampleRecord2(true, EXAMPLE_RECORD1_1);
public static final ExampleRecord2 EXAMPLE_RECORD2_2 = new ExampleRecord2(false, EXAMPLE_RECORD1_2);
public static ExampleConfigurationA2 newExampleConfigurationA2() {
ExampleConfigurationA2 a2 = new ExampleConfigurationA2();
@ -193,6 +214,12 @@ public final class ExampleInitializer {
a2.setA1_b2(B2_1);
a2.setA2_b2(B2_2);
a2.setA1_r1(EXAMPLE_RECORD1_1);
a2.setA2_r1(EXAMPLE_RECORD1_2);
a2.setA1_r2(EXAMPLE_RECORD2_1);
a2.setA2_r2(EXAMPLE_RECORD2_2);
a2.setA1_listBoolean(List.of(true, false, true));
a2.setA2_listBoolean(List.of(false, true, false));
@ -262,6 +289,12 @@ public final class ExampleInitializer {
a2.setA1_listB2(List.of(B2_1));
a2.setA2_listB2(List.of(B2_1, B2_2));
a2.setA1_listR1(List.of(EXAMPLE_RECORD1_1));
a2.setA2_listR1(List.of(EXAMPLE_RECORD1_1, EXAMPLE_RECORD1_2));
a2.setA1_listR2(List.of(EXAMPLE_RECORD2_1));
a2.setA2_listR2(List.of(EXAMPLE_RECORD2_1, EXAMPLE_RECORD2_2));
a2.setA1_arrayPrimBoolean(new boolean[]{true, false, true});
a2.setA2_arrayPrimBoolean(new boolean[]{false, true, false});
@ -340,6 +373,12 @@ public final class ExampleInitializer {
a2.setA1_arrayB2(new ExampleConfigurationB2[]{B2_1});
a2.setA2_arrayB2(new ExampleConfigurationB2[]{B2_1, B2_2});
a2.setA1_arrayR1(new ExampleRecord1[]{EXAMPLE_RECORD1_1});
a2.setA2_arrayR1(new ExampleRecord1[]{EXAMPLE_RECORD1_1, EXAMPLE_RECORD1_2});
a2.setA1_arrayR2(new ExampleRecord2[]{EXAMPLE_RECORD2_1});
a2.setA2_arrayR2(new ExampleRecord2[]{EXAMPLE_RECORD2_1, EXAMPLE_RECORD2_2});
a2.setA1_setBoolean(asSet(true));
a2.setA2_setBoolean(asSet(false));
@ -394,6 +433,12 @@ public final class ExampleInitializer {
a2.setA1_setB2(asSet(B2_1));
a2.setA2_setB2(asSet(B2_1, B2_2));
a2.setA1_setR1(asSet(EXAMPLE_RECORD1_1));
a2.setA2_setR1(asSet(EXAMPLE_RECORD1_1, EXAMPLE_RECORD1_2));
a2.setA1_setR2(asSet(EXAMPLE_RECORD2_1));
a2.setA2_setR2(asSet(EXAMPLE_RECORD2_1, EXAMPLE_RECORD2_2));
a2.setA1_mapBooleanBoolean(asMap(true, true, false, false));
a2.setA2_mapBooleanBoolean(asMap(true, true, false, false));
@ -448,6 +493,12 @@ public final class ExampleInitializer {
a2.setA1_mapEnmB2(asMap(A, B2_1, B, B2_2));
a2.setA2_mapEnmB2(asMap(B, B2_1, C, B2_2));
a2.setA1_mapStringR1(asMap("1", EXAMPLE_RECORD1_1, "2", EXAMPLE_RECORD1_2));
a2.setA2_mapStringR1(asMap("2", EXAMPLE_RECORD1_1, "3", EXAMPLE_RECORD1_2));
a2.setA1_mapStringR2(asMap("1", EXAMPLE_RECORD2_1, "2", EXAMPLE_RECORD2_2));
a2.setA2_mapStringR2(asMap("2", EXAMPLE_RECORD2_1, "3", EXAMPLE_RECORD2_2));
a2.setA1_listEmpty(Collections.emptyList());
a2.setA2_listEmpty(List.of());
@ -880,6 +931,12 @@ public final class ExampleInitializer {
a2.setA1_arrayArrayB2(new ExampleConfigurationB2[][]{{}, {B2_1}});
a2.setA2_arrayArrayB2(new ExampleConfigurationB2[][]{{B2_1}, {B2_2}});
a2.setA1_arrayArrayR1(new ExampleRecord1[][]{{}, {EXAMPLE_RECORD1_1}});
a2.setA2_arrayArrayR1(new ExampleRecord1[][]{{EXAMPLE_RECORD1_1}, {EXAMPLE_RECORD1_2}});
a2.setA1_arrayArrayR2(new ExampleRecord2[][]{{}, {EXAMPLE_RECORD2_1}});
a2.setA2_arrayArrayR2(new ExampleRecord2[][]{{EXAMPLE_RECORD2_1}, {EXAMPLE_RECORD2_2}});
a2.setA1_point(P1);
a2.setA2_point(P2);

@ -0,0 +1,35 @@
package de.exlll.configlib.configurations;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public record ExampleRecord1(
int i,
Double d,
ExampleEnum enm,
List<UUID> listUuid,
float[][] arrayArrayFloat,
ExampleConfigurationB1 b1
) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ExampleRecord1 that = (ExampleRecord1) o;
return i == that.i &&
Objects.equals(d, that.d) &&
enm == that.enm &&
Objects.equals(listUuid, that.listUuid) &&
Arrays.deepEquals(arrayArrayFloat, that.arrayArrayFloat) &&
Objects.equals(b1, that.b1);
}
@Override
public int hashCode() {
int result = Objects.hash(i, d, enm, listUuid, b1);
result = 31 * result + Arrays.deepHashCode(arrayArrayFloat);
return result;
}
}

@ -0,0 +1,3 @@
package de.exlll.configlib.configurations;
public record ExampleRecord2(boolean b, ExampleRecord1 r1) {}
Loading…
Cancel
Save