Move SerializerMapper functionality to TypeSerializer

dev
Exlll 2 years ago
parent 3c25327f7f
commit 0a291fe922

@ -3,6 +3,7 @@ package de.exlll.configlib;
import de.exlll.configlib.TypeComponent.ConfigurationField;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
final class ConfigurationSerializer<T> extends TypeSerializer<T, ConfigurationField> {
@ -51,7 +52,7 @@ final class ConfigurationSerializer<T> extends TypeSerializer<T, ConfigurationFi
}
@Override
protected Iterable<ConfigurationField> components() {
protected List<ConfigurationField> components() {
return FieldExtractors.CONFIGURATION.extract(type)
.filter(properties.getFieldFilter())
.map(ConfigurationField::new)

@ -1,77 +0,0 @@
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());
}
}

@ -16,6 +16,8 @@ import java.time.LocalTime;
import java.util.Map;
import java.util.UUID;
import static de.exlll.configlib.Validator.requireNonNull;
final class SerializerSelector {
private static final Map<Class<?>, Serializer<?, ?>> DEFAULT_SERIALIZERS = Map.ofEntries(
Map.entry(boolean.class, new BooleanSerializer()),
@ -50,7 +52,7 @@ final class SerializerSelector {
private final ConfigurationProperties properties;
public SerializerSelector(ConfigurationProperties properties) {
this.properties = properties;
this.properties = requireNonNull(properties, "configuration properties");
}
public Serializer<?, ?> select(Type type) {

@ -1,7 +1,9 @@
package de.exlll.configlib;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static de.exlll.configlib.Validator.requireNonNull;
@ -17,7 +19,7 @@ sealed abstract class TypeSerializer<T, TC extends TypeComponent<?>>
this.type = requireNonNull(type, "type");
this.properties = requireNonNull(properties, "configuration properties");
this.formatter = properties.getNameFormatter();
this.serializers = new SerializerMapper(type, properties).buildSerializerMap();
this.serializers = buildSerializerMap();
requireSerializableComponents();
}
@ -30,6 +32,19 @@ sealed abstract class TypeSerializer<T, TC extends TypeComponent<?>>
: new ConfigurationSerializer<>(type, properties);
}
Map<String, Serializer<?, ?>> buildSerializerMap() {
final var selector = new SerializerSelector(properties);
try {
return components().stream().collect(Collectors.toMap(
TypeComponent::name,
component -> selector.select(component.genericType())
));
} catch (StackOverflowError error) {
String msg = "Recursive type definitions are not supported.";
throw new ConfigurationException(msg, error);
}
}
@Override
public final Map<?, ?> serialize(T element) {
final Map<String, Object> result = new LinkedHashMap<>();
@ -83,7 +98,7 @@ sealed abstract class TypeSerializer<T, TC extends TypeComponent<?>>
protected abstract String baseDeserializeExceptionMessage(TC component, Object value);
protected abstract Iterable<TC> components();
protected abstract List<TC> components();
abstract T newDefaultInstance();
}

@ -1,5 +1,6 @@
package de.exlll.configlib;
import de.exlll.configlib.Serializers.*;
import de.exlll.configlib.configurations.ExampleConfigurationA2;
import de.exlll.configlib.configurations.ExampleConfigurationB1;
import de.exlll.configlib.configurations.ExampleConfigurationB2;
@ -14,71 +15,39 @@ 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,
class TypeSerializerTest {
private static <T> TypeSerializer<T, ?> newTypeSerializer(
Class<T> type,
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."
);
ConfigurationProperties properties = builder.build();
return TypeSerializer.newSerializerFor(type, properties);
}
@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());
private static <T> TypeSerializer<T, ?> newTypeSerializer(Class<T> type) {
return newTypeSerializer(type, builder -> {});
}
@Test
void buildSerializerMapForConfigurationIgnoresFormatter() {
Map<String, Serializer<?, ?>> serializers = newMapper(
void buildSerializerMapUsesComponentName() {
Map<String, Serializer<?, ?>> serializers = newTypeSerializer(
ExampleConfigurationA2.class,
props -> props.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
builder -> builder.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)
Map<String, Serializer<?, ?>> serializers = newTypeSerializer(ExampleConfigurationA2.class)
.buildSerializerMap();
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("a2_refChar"), instanceOf(CharacterSerializer.class));
@ -116,22 +85,7 @@ class SerializerMapperTest {
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(
private record R1(
boolean primBool,
Character refChar,
String string,
@ -148,7 +102,7 @@ class SerializerMapperTest {
@Test
void buildSerializerMapForRecord() {
Map<String, Serializer<?, ?>> serializers = newMapper(R2.class)
Map<String, Serializer<?, ?>> serializers = newTypeSerializer(R1.class)
.buildSerializerMap();
assertThat(serializers.get("primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("refChar"), instanceOf(CharacterSerializer.class));
@ -199,7 +153,7 @@ class SerializerMapperTest {
@Test
void buildSerializerMapForConfigurationPreventsRecursiveDefinitions() {
assertThrowsConfigurationException(
() -> newMapper(Recursive1.class).buildSerializerMap(),
() -> newTypeSerializer(Recursive1.class),
"Recursive type definitions are not supported."
);
}
@ -213,13 +167,13 @@ class SerializerMapperTest {
@Test
void buildSerializerMapForRecordPreventsRecursiveDefinitions() {
assertThrowsConfigurationException(
() -> newMapper(RecursiveRecord1.class).buildSerializerMap(),
() -> newTypeSerializer(RecursiveRecord1.class),
"Recursive type definitions are not supported."
);
assertThrowsConfigurationException(
() -> newMapper(RecursiveRecord3.class).buildSerializerMap(),
() -> newTypeSerializer(RecursiveRecord3.class),
"Recursive type definitions are not supported."
);
}
}
}
Loading…
Cancel
Save