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 de.exlll.configlib.TypeComponent.ConfigurationField;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List;
import java.util.Map; import java.util.Map;
final class ConfigurationSerializer<T> extends TypeSerializer<T, ConfigurationField> { final class ConfigurationSerializer<T> extends TypeSerializer<T, ConfigurationField> {
@ -51,7 +52,7 @@ final class ConfigurationSerializer<T> extends TypeSerializer<T, ConfigurationFi
} }
@Override @Override
protected Iterable<ConfigurationField> components() { protected List<ConfigurationField> components() {
return FieldExtractors.CONFIGURATION.extract(type) return FieldExtractors.CONFIGURATION.extract(type)
.filter(properties.getFieldFilter()) .filter(properties.getFieldFilter())
.map(ConfigurationField::new) .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.Map;
import java.util.UUID; import java.util.UUID;
import static de.exlll.configlib.Validator.requireNonNull;
final class SerializerSelector { final class SerializerSelector {
private static final Map<Class<?>, Serializer<?, ?>> DEFAULT_SERIALIZERS = Map.ofEntries( private static final Map<Class<?>, Serializer<?, ?>> DEFAULT_SERIALIZERS = Map.ofEntries(
Map.entry(boolean.class, new BooleanSerializer()), Map.entry(boolean.class, new BooleanSerializer()),
@ -50,7 +52,7 @@ final class SerializerSelector {
private final ConfigurationProperties properties; private final ConfigurationProperties properties;
public SerializerSelector(ConfigurationProperties properties) { public SerializerSelector(ConfigurationProperties properties) {
this.properties = properties; this.properties = requireNonNull(properties, "configuration properties");
} }
public Serializer<?, ?> select(Type type) { public Serializer<?, ?> select(Type type) {

@ -1,7 +1,9 @@
package de.exlll.configlib; package de.exlll.configlib;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import static de.exlll.configlib.Validator.requireNonNull; 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.type = requireNonNull(type, "type");
this.properties = requireNonNull(properties, "configuration properties"); this.properties = requireNonNull(properties, "configuration properties");
this.formatter = properties.getNameFormatter(); this.formatter = properties.getNameFormatter();
this.serializers = new SerializerMapper(type, properties).buildSerializerMap(); this.serializers = buildSerializerMap();
requireSerializableComponents(); requireSerializableComponents();
} }
@ -30,6 +32,19 @@ sealed abstract class TypeSerializer<T, TC extends TypeComponent<?>>
: new ConfigurationSerializer<>(type, properties); : 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 @Override
public final Map<?, ?> serialize(T element) { public final Map<?, ?> serialize(T element) {
final Map<String, Object> result = new LinkedHashMap<>(); 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 String baseDeserializeExceptionMessage(TC component, Object value);
protected abstract Iterable<TC> components(); protected abstract List<TC> components();
abstract T newDefaultInstance(); abstract T newDefaultInstance();
} }

@ -1,5 +1,6 @@
package de.exlll.configlib; package de.exlll.configlib;
import de.exlll.configlib.Serializers.*;
import de.exlll.configlib.configurations.ExampleConfigurationA2; import de.exlll.configlib.configurations.ExampleConfigurationA2;
import de.exlll.configlib.configurations.ExampleConfigurationB1; import de.exlll.configlib.configurations.ExampleConfigurationB1;
import de.exlll.configlib.configurations.ExampleConfigurationB2; import de.exlll.configlib.configurations.ExampleConfigurationB2;
@ -14,71 +15,39 @@ import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
import static de.exlll.configlib.Serializers.*;
import static de.exlll.configlib.TestUtils.assertThrowsConfigurationException; import static de.exlll.configlib.TestUtils.assertThrowsConfigurationException;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
class SerializerMapperTest { class TypeSerializerTest {
private static SerializerMapper newMapper(Class<?> cls) { private static <T> TypeSerializer<T, ?> newTypeSerializer(
return newMapper(cls, builder -> {}); Class<T> type,
}
private static SerializerMapper newMapper(
Class<?> cls,
Consumer<ConfigurationProperties.Builder<?>> propertiesConfigurer Consumer<ConfigurationProperties.Builder<?>> propertiesConfigurer
) { ) {
var builder = ConfigurationProperties.newBuilder(); var builder = ConfigurationProperties.newBuilder();
builder.addSerializer(Point.class, TestUtils.POINT_SERIALIZER); builder.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
propertiesConfigurer.accept(builder); propertiesConfigurer.accept(builder);
return new SerializerMapper(cls, builder.build()); ConfigurationProperties properties = builder.build();
} return TypeSerializer.newSerializerFor(type, properties);
@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 private static <T> TypeSerializer<T, ?> newTypeSerializer(Class<T> type) {
void buildSerializerMapForConfigurationFiltersFields() { return newTypeSerializer(type, builder -> {});
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 @Test
void buildSerializerMapForConfigurationIgnoresFormatter() { void buildSerializerMapUsesComponentName() {
Map<String, Serializer<?, ?>> serializers = newMapper( Map<String, Serializer<?, ?>> serializers = newTypeSerializer(
ExampleConfigurationA2.class, ExampleConfigurationA2.class,
props -> props.setNameFormatter(NameFormatters.UPPER_UNDERSCORE) builder -> builder.setNameFormatter(NameFormatters.UPPER_UNDERSCORE)
).buildSerializerMap(); ).buildSerializerMap();
assertThat(serializers.get("A2_PRIM_BOOL"), nullValue()); assertThat(serializers.get("A2_PRIM_BOOL"), nullValue());
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class)); assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
} }
@Test @Test
void buildSerializerMapForConfiguration() { void buildSerializerMapForConfiguration() {
Map<String, Serializer<?, ?>> serializers = newMapper(ExampleConfigurationA2.class) Map<String, Serializer<?, ?>> serializers = newTypeSerializer(ExampleConfigurationA2.class)
.buildSerializerMap(); .buildSerializerMap();
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class)); assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("a2_refChar"), instanceOf(CharacterSerializer.class)); assertThat(serializers.get("a2_refChar"), instanceOf(CharacterSerializer.class));
@ -116,22 +85,7 @@ class SerializerMapperTest {
assertThat(serializers.get("a2_point"), sameInstance(TestUtils.POINT_SERIALIZER)); assertThat(serializers.get("a2_point"), sameInstance(TestUtils.POINT_SERIALIZER));
} }
private record R1(int integer, boolean bool) {} private record R1(
@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, boolean primBool,
Character refChar, Character refChar,
String string, String string,
@ -148,7 +102,7 @@ class SerializerMapperTest {
@Test @Test
void buildSerializerMapForRecord() { void buildSerializerMapForRecord() {
Map<String, Serializer<?, ?>> serializers = newMapper(R2.class) Map<String, Serializer<?, ?>> serializers = newTypeSerializer(R1.class)
.buildSerializerMap(); .buildSerializerMap();
assertThat(serializers.get("primBool"), instanceOf(BooleanSerializer.class)); assertThat(serializers.get("primBool"), instanceOf(BooleanSerializer.class));
assertThat(serializers.get("refChar"), instanceOf(CharacterSerializer.class)); assertThat(serializers.get("refChar"), instanceOf(CharacterSerializer.class));
@ -199,7 +153,7 @@ class SerializerMapperTest {
@Test @Test
void buildSerializerMapForConfigurationPreventsRecursiveDefinitions() { void buildSerializerMapForConfigurationPreventsRecursiveDefinitions() {
assertThrowsConfigurationException( assertThrowsConfigurationException(
() -> newMapper(Recursive1.class).buildSerializerMap(), () -> newTypeSerializer(Recursive1.class),
"Recursive type definitions are not supported." "Recursive type definitions are not supported."
); );
} }
@ -213,12 +167,12 @@ class SerializerMapperTest {
@Test @Test
void buildSerializerMapForRecordPreventsRecursiveDefinitions() { void buildSerializerMapForRecordPreventsRecursiveDefinitions() {
assertThrowsConfigurationException( assertThrowsConfigurationException(
() -> newMapper(RecursiveRecord1.class).buildSerializerMap(), () -> newTypeSerializer(RecursiveRecord1.class),
"Recursive type definitions are not supported." "Recursive type definitions are not supported."
); );
assertThrowsConfigurationException( assertThrowsConfigurationException(
() -> newMapper(RecursiveRecord3.class).buildSerializerMap(), () -> newTypeSerializer(RecursiveRecord3.class),
"Recursive type definitions are not supported." "Recursive type definitions are not supported."
); );
} }
Loading…
Cancel
Save