|
|
@ -1,36 +1,77 @@
|
|
|
|
package de.exlll.configlib;
|
|
|
|
package de.exlll.configlib;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
|
|
final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
|
|
|
|
final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
|
|
|
|
private final SerializerContext context;
|
|
|
|
private final SerializerContext context;
|
|
|
|
private final Class<?> polymorphicType;
|
|
|
|
private final Class<?> polymorphicType;
|
|
|
|
private final Polymorphic annotation;
|
|
|
|
private final Polymorphic polymorphic;
|
|
|
|
|
|
|
|
private final Map<String, Class<?>> typeByAlias = new HashMap<>();
|
|
|
|
|
|
|
|
private final Map<Class<?>, String> aliasByType = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
public PolymorphicSerializer(SerializerContext context) {
|
|
|
|
public PolymorphicSerializer(SerializerContext context) {
|
|
|
|
this.context = context;
|
|
|
|
this.context = context;
|
|
|
|
// we know it's a class because of SerializerSelector#findMetaSerializerOnType
|
|
|
|
// we know it's a class because of SerializerSelector#findMetaSerializerOnType
|
|
|
|
this.polymorphicType = (Class<?>) context.annotatedType().getType();
|
|
|
|
this.polymorphicType = (Class<?>) context.annotatedType().getType();
|
|
|
|
this.annotation = polymorphicType.getAnnotation(Polymorphic.class);
|
|
|
|
this.polymorphic = polymorphicType.getAnnotation(Polymorphic.class);
|
|
|
|
requireNonBlankProperty();
|
|
|
|
requireNonBlankProperty();
|
|
|
|
|
|
|
|
initAliases();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void requireNonBlankProperty() {
|
|
|
|
private void requireNonBlankProperty() {
|
|
|
|
if (annotation.property().isBlank()) {
|
|
|
|
if (polymorphic.property().isBlank()) {
|
|
|
|
String msg = "The @Polymorphic annotation does not allow a blank property name but " +
|
|
|
|
String msg = "The @Polymorphic annotation does not allow a blank property name but " +
|
|
|
|
"type '%s' uses one.".formatted(polymorphicType.getName());
|
|
|
|
"type '%s' uses one.".formatted(polymorphicType.getName());
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void initAliases() {
|
|
|
|
|
|
|
|
final var polymorphicTypes = polymorphicType.getAnnotation(PolymorphicTypes.class);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (polymorphicTypes == null)
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (PolymorphicTypes.Type pType : polymorphicTypes.value()) {
|
|
|
|
|
|
|
|
final var type = pType.type();
|
|
|
|
|
|
|
|
final var alias = pType.alias().isBlank() ? type.getName() : pType.alias();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requireDistinctAliases(alias);
|
|
|
|
|
|
|
|
requireDistinctTypes(type);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typeByAlias.put(alias, type);
|
|
|
|
|
|
|
|
aliasByType.put(type, alias);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void requireDistinctAliases(String alias) {
|
|
|
|
|
|
|
|
if (typeByAlias.containsKey(alias)) {
|
|
|
|
|
|
|
|
String msg = "The @PolymorphicTypes annotation must not use the same alias for " +
|
|
|
|
|
|
|
|
"multiple types. Alias '%s' appears more than once."
|
|
|
|
|
|
|
|
.formatted(alias);
|
|
|
|
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void requireDistinctTypes(Class<?> type) {
|
|
|
|
|
|
|
|
if (aliasByType.containsKey(type)) {
|
|
|
|
|
|
|
|
String msg = "The @PolymorphicTypes annotation must not contain multiple " +
|
|
|
|
|
|
|
|
"definitions for the same subtype. Type '%s' appears more than once."
|
|
|
|
|
|
|
|
.formatted(type.getName());
|
|
|
|
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public Map<?, ?> serialize(Object element) {
|
|
|
|
public Map<?, ?> serialize(Object element) {
|
|
|
|
|
|
|
|
final Class<?> elementType = element.getClass();
|
|
|
|
// this cast won't cause any exceptions as we only pass objects of types the
|
|
|
|
// this cast won't cause any exceptions as we only pass objects of types the
|
|
|
|
// serializer expects
|
|
|
|
// serializer expects
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
final var serializer = (TypeSerializer<Object, ?>) TypeSerializer.newSerializerFor(
|
|
|
|
final var serializer = (TypeSerializer<Object, ?>) TypeSerializer.newSerializerFor(
|
|
|
|
element.getClass(),
|
|
|
|
elementType,
|
|
|
|
context.properties()
|
|
|
|
context.properties()
|
|
|
|
);
|
|
|
|
);
|
|
|
|
final var serialization = serializer.serialize(element);
|
|
|
|
final var serialization = serializer.serialize(element);
|
|
|
@ -38,34 +79,43 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
|
|
|
|
requireSerializationNotContainsProperty(serialization);
|
|
|
|
requireSerializationNotContainsProperty(serialization);
|
|
|
|
|
|
|
|
|
|
|
|
final var result = new LinkedHashMap<>();
|
|
|
|
final var result = new LinkedHashMap<>();
|
|
|
|
result.put(annotation.property(), element.getClass().getName());
|
|
|
|
result.put(polymorphic.property(), getTypeIdentifierByType(elementType));
|
|
|
|
result.putAll(serialization);
|
|
|
|
result.putAll(serialization);
|
|
|
|
return result;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String getTypeIdentifierByType(Class<?> type) {
|
|
|
|
|
|
|
|
final String alias = aliasByType.get(type);
|
|
|
|
|
|
|
|
return (alias == null) ? type.getName() : alias;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void requireSerializationNotContainsProperty(Map<?, ?> serialization) {
|
|
|
|
private void requireSerializationNotContainsProperty(Map<?, ?> serialization) {
|
|
|
|
if (serialization.containsKey(annotation.property())) {
|
|
|
|
if (serialization.containsKey(polymorphic.property())) {
|
|
|
|
String msg = ("Polymorphic serialization for type '%s' failed. The type contains a " +
|
|
|
|
String msg = ("Polymorphic serialization for type '%s' failed. The type contains a " +
|
|
|
|
"configuration element with name '%s' but that name is " +
|
|
|
|
"configuration element with name '%s' but that name is " +
|
|
|
|
"used by the @Polymorphic property.")
|
|
|
|
"used by the @Polymorphic property.")
|
|
|
|
.formatted(polymorphicType.getName(), annotation.property());
|
|
|
|
.formatted(polymorphicType.getName(), polymorphic.property());
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public Object deserialize(Map<?, ?> element) {
|
|
|
|
public Object deserialize(Map<?, ?> element) {
|
|
|
|
requirePropertyPresent(element);
|
|
|
|
requirePropertyPresent(element);
|
|
|
|
|
|
|
|
|
|
|
|
final var typeIdentifier = element.get(annotation.property());
|
|
|
|
final var typeIdentifier = element.get(polymorphic.property());
|
|
|
|
requireTypeIdentifierString(typeIdentifier);
|
|
|
|
requireTypeIdentifierString(typeIdentifier);
|
|
|
|
|
|
|
|
|
|
|
|
Class<?> type = tryFindClass((String) typeIdentifier);
|
|
|
|
final var type = getTypeByTypeIdentifier((String) typeIdentifier);
|
|
|
|
TypeSerializer<?, ?> serializer = TypeSerializer.newSerializerFor(type, context.properties());
|
|
|
|
final var serializer = TypeSerializer.newSerializerFor(type, context.properties());
|
|
|
|
return serializer.deserialize(element);
|
|
|
|
return serializer.deserialize(element);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Class<?> getTypeByTypeIdentifier(String typeIdentifier) {
|
|
|
|
|
|
|
|
final Class<?> type = typeByAlias.get(typeIdentifier);
|
|
|
|
|
|
|
|
return (type == null) ? tryFindClass(typeIdentifier) : type;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private Class<?> tryFindClass(String className) {
|
|
|
|
private Class<?> tryFindClass(String className) {
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
return Reflect.getClassByName(className);
|
|
|
|
return Reflect.getClassByName(className);
|
|
|
@ -78,7 +128,7 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void requirePropertyPresent(Map<?, ?> element) {
|
|
|
|
private void requirePropertyPresent(Map<?, ?> element) {
|
|
|
|
if (element.get(annotation.property()) != null)
|
|
|
|
if (element.get(polymorphic.property()) != null)
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
String msg = """
|
|
|
|
String msg = """
|
|
|
|
Polymorphic deserialization for type '%s' failed. \
|
|
|
|
Polymorphic deserialization for type '%s' failed. \
|
|
|
@ -88,7 +138,7 @@ final class PolymorphicSerializer implements Serializer<Object, Map<?, ?>> {
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
.formatted(
|
|
|
|
.formatted(
|
|
|
|
polymorphicType.getName(),
|
|
|
|
polymorphicType.getName(),
|
|
|
|
annotation.property(),
|
|
|
|
polymorphic.property(),
|
|
|
|
element
|
|
|
|
element
|
|
|
|
);
|
|
|
|
);
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|
throw new ConfigurationException(msg);
|
|
|
|