Skip to content

Commit

Permalink
feat: fail on unknown enum value
Browse files Browse the repository at this point in the history
Configures the XjxSerdes to fail when an enum value cannot be mapped to an enum constant.
When not set, defaults to false and will default to null when a value cannot be mapped to a name.
  • Loading branch information
jonas-grgt committed Mar 9, 2024
1 parent 86f642a commit 2bc0333
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 114 deletions.
13 changes: 11 additions & 2 deletions xjx-serdes/src/main/java/io/jonasg/xjx/serdes/TypeMappers.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package io.jonasg.xjx.serdes;

import io.jonasg.xjx.serdes.deserialize.XjxDeserializationException;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -30,7 +33,7 @@ public final class TypeMappers {
TYPES.add(LocalDate.class);
}

public static Function<Object, Object> forType(Class<?> type) {
public static Function<Object, Object> forType(Class<?> type, XjxConfiguration configuration) {
Function<Object, Object> mapper = Function.identity();
if (type.equals(String.class)) {
mapper = String::valueOf;
Expand Down Expand Up @@ -69,7 +72,13 @@ public static Function<Object, Object> forType(Class<?> type) {
mapper = value -> ZonedDateTime.parse(String.valueOf(value));
}
if (type.isEnum()) {
mapper = value -> toEnum(type, String.valueOf(value));
mapper = value -> {
Object enumValue = toEnum(type, String.valueOf(value));
if (enumValue == null && configuration.failOnUnknownEnumValue()) {
throw new XjxDeserializationException("Cannot map value '" + value + "' to enum " + type.getSimpleName());
}
return enumValue;
};
}
return mapper;
}
Expand Down
30 changes: 24 additions & 6 deletions xjx-serdes/src/main/java/io/jonasg/xjx/serdes/XjxSerdes.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import io.jonasg.xjx.sax.SaxParser;
import io.jonasg.xjx.serdes.deserialize.config.ConfigurationBuilder;
import io.jonasg.xjx.serdes.deserialize.MapOf;
import io.jonasg.xjx.serdes.deserialize.MapRootSaxHandler;
import io.jonasg.xjx.serdes.deserialize.PathBasedSaxHandler;
import io.jonasg.xjx.serdes.deserialize.PathWriterIndexFactory;
import io.jonasg.xjx.serdes.deserialize.XjxDeserializationException;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;
import io.jonasg.xjx.serdes.serialize.XmlNodeStructureFactory;
import io.jonasg.xjx.serdes.serialize.XmlStringBuilder;

Expand All @@ -24,22 +27,37 @@ public class XjxSerdes {
private final PathWriterIndexFactory pathWriterIndexFactory;

private final XmlNodeStructureFactory xmlNodeStructureFactory = new XmlNodeStructureFactory();

private final XmlStringBuilder xmlStringBuilder;

private XjxSerdes(SaxParser saxParser, PathWriterIndexFactory pathWriterIndexFactory, XmlStringBuilder xmlStringBuilder) {
private final XjxConfiguration configuration;

private XjxSerdes(SaxParser saxParser,
XmlStringBuilder xmlStringBuilder,
Consumer<ConfigurationBuilder> configurationBuilder) {
this.configuration = new XjxConfiguration();
configurationBuilder.accept(new ConfigurationBuilder(configuration));
this.saxParser = saxParser;
this.pathWriterIndexFactory = pathWriterIndexFactory;
this.pathWriterIndexFactory = new PathWriterIndexFactory(configuration);
this.xmlStringBuilder = xmlStringBuilder;
}
}

/**
* Constructs an XjxSerdes instance with default configurations.
*/
public XjxSerdes() {
this(new SaxParser(), new PathWriterIndexFactory(), new XmlStringBuilder());
this(new SaxParser(), new XmlStringBuilder(), (builder) -> {});
}

/**
/**
* Constructs an XjxSerdes instance with custom configurations.
* @param configurationBuilder The configuration builder to configure the XjxSerdes instance.
*/
public XjxSerdes(Consumer<ConfigurationBuilder> configurationBuilder) {
this(new SaxParser(), new XmlStringBuilder(), configurationBuilder);
}

/**
* Reads XML data and deserializes it into an object of the specified class.
*
* @param data The XML data to read.
Expand All @@ -60,7 +78,7 @@ public <T> T read(String data, Class<T> clazz) {
* @return The deserialized object.
*/
public <T> T read(Reader data, Class<T> clazz) {
PathBasedSaxHandler<T> saxHandler = new PathBasedSaxHandler<>((rootTag) -> pathWriterIndexFactory.createIndexForType(clazz, rootTag));
PathBasedSaxHandler<T> saxHandler = new PathBasedSaxHandler<>((rootTag) -> pathWriterIndexFactory.createIndexForType(clazz, rootTag), this.configuration);
saxParser.parse(data, saxHandler);
return saxHandler.instance();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.jonasg.xjx.serdes.deserialize;

public enum DeserializationFeature {
FAIL_ON_UNMAPPABLE_ENUM_VALUE

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
import io.jonasg.xjx.sax.Attribute;
import io.jonasg.xjx.sax.SaxHandler;
import io.jonasg.xjx.serdes.Path;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;

public class PathBasedSaxHandler<T> implements SaxHandler {

private final Function<String, Map<Path, PathWriter>> indexSupplier;

private final LinkedList<Object> objectInstances = new LinkedList<>();
private final XjxConfiguration configuration;

private final LinkedList<Object> objectInstances = new LinkedList<>();

private String rootTag;

Expand All @@ -28,13 +31,15 @@ public class PathBasedSaxHandler<T> implements SaxHandler {

private String mapStartTag;

public PathBasedSaxHandler(Function<String, Map<Path, PathWriter>> indexSupplier) {
public PathBasedSaxHandler(Function<String, Map<Path, PathWriter>> indexSupplier, XjxConfiguration configuration) {
this.indexSupplier = indexSupplier;
}
this.configuration = configuration;
}

public PathBasedSaxHandler(Function<String, Map<Path, PathWriter>> indexSupplier, String rootTag) {
public PathBasedSaxHandler(Function<String, Map<Path, PathWriter>> indexSupplier, String rootTag, XjxConfiguration configuration) {
this.indexSupplier = indexSupplier;
this.rootTag = rootTag;
this.configuration = configuration;
handleRootTag(rootTag);
}

Expand All @@ -60,7 +65,7 @@ public void startTag(String namespace, String name, List<Attribute> attributes)
this.mapRootSaxHandlerDelegate = new MapRootSaxHandler((HashMap<String, Object>) object);
this.mapStartTag = name;
} else if (object instanceof MapWithTypeInfo mapWithTypeInfo) {
this.mapRootSaxHandlerDelegate = new TypedValueMapSaxHandler(mapWithTypeInfo);
this.mapRootSaxHandlerDelegate = new TypedValueMapSaxHandler(mapWithTypeInfo, configuration);
this.mapStartTag = name;
}
this.objectInstances.push(object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.jonasg.xjx.serdes.Tag;
import io.jonasg.xjx.serdes.TypeMappers;
import io.jonasg.xjx.serdes.deserialize.accessor.FieldAccessor;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;
import io.jonasg.xjx.serdes.reflector.FieldReflector;
import io.jonasg.xjx.serdes.reflector.TypeReflector;

Expand All @@ -29,9 +30,16 @@ public class PathWriterIndexFactory {
public static final List<Class<?>> BASIC_TYPES = List.of(
String.class, Integer.class, int.class, Boolean.class, boolean.class, Long.class, long.class, BigDecimal.class, Double.class,
double.class, char.class, Character.class, LocalDate.class, LocalDateTime.class, ZonedDateTime.class);
private final Map<Class<?>, Object> collectionCacheType = new HashMap<>();

public <T> Map<Path, PathWriter> createIndexForType(Class<T> type, String rootTag) {
private final XjxConfiguration configuration;

private final Map<Class<?>, Object> collectionCacheType = new HashMap<>();

public PathWriterIndexFactory(XjxConfiguration xjxConfiguration) {
this.configuration = xjxConfiguration;
}

public <T> Map<Path, PathWriter> createIndexForType(Class<T> type, String rootTag) {
Path path = Path.of(rootTag);
return buildIndex(type, path);
}
Expand Down Expand Up @@ -82,7 +90,7 @@ private void indexRecordType(FieldReflector field, Map<Path, PathWriter> index,
return recordWrapper;
}).setValueInitializer((value) -> {
if (value instanceof RecordWrapper<?> recordWrapperValue) {
FieldAccessor.of(field, parent.get()).set(recordWrapperValue.record());
FieldAccessor.of(field, parent.get(), configuration).set(recordWrapperValue.record());
}
}));
doBuildIndex(field.type(), getPathForField(field, path), index, () -> recordWrapper);
Expand All @@ -97,14 +105,14 @@ private void indexMapType(FieldReflector field, Map<Path, PathWriter> index, Pat
}
}

private static void doIndexMapType(FieldReflector field,
private void doIndexMapType(FieldReflector field,
Map<Path, PathWriter> index,
Supplier<Object> parent,
Path pathForField) {
index.put(pathForField, PathWriter.objectInitializer(() -> {
Map<String, Object> map = new HashMap<>();
Class<?> valueType = (Class<?>) ((ParameterizedType) field.genericType()).getActualTypeArguments()[1];
FieldAccessor.of(field, parent.get()).set(map);
FieldAccessor.of(field, parent.get(), configuration).set(map);
if (valueType.equals(Object.class)) {
return map;
} else {
Expand All @@ -113,13 +121,13 @@ private static void doIndexMapType(FieldReflector field,
}));
}

private static void indexMapAsRootType(FieldReflector field,
private void indexMapAsRootType(FieldReflector field,
Map<Path, PathWriter> index,
Supplier<Object> parent,
Path pathForField) {
index.put(pathForField, PathWriter.rootInitializer(() -> {
Map<String, Object> map = new HashMap<>();
FieldAccessor.of(field, parent.get()).set(map);
FieldAccessor.of(field, parent.get(), configuration).set(map);
return new MapAsRoot(parent.get(), map);
}));
}
Expand Down Expand Up @@ -149,7 +157,7 @@ private void doIndexComplexType(FieldReflector field, Map<Path, PathWriter> inde
index.put(getPathForField(field, path), PathWriter.valueInitializer((value) -> {
value = ValueDeserializationHandler.getInstance().handle(field.rawField(), (String) value)
.orElse(value);
FieldAccessor.of(field, parent.get()).set(value);
FieldAccessor.of(field, parent.get(), configuration).set(value);
}));
} else {
Supplier<Object> complexTypeSupplier = () -> {
Expand All @@ -158,7 +166,7 @@ private void doIndexComplexType(FieldReflector field, Map<Path, PathWriter> inde
}
Object complexType = TypeReflector.reflect(field.type()).instanceReflector().instance();
collectionCacheType.put(field.type(), complexType);
FieldAccessor.of(field, parent.get()).set(complexType);
FieldAccessor.of(field, parent.get(), configuration).set(complexType);
return complexType;
};
index.putAll(doBuildIndex(field.type(), getPathForField(field, path), index, complexTypeSupplier));
Expand Down Expand Up @@ -192,7 +200,7 @@ private void indexEnumType(FieldReflector field, Map<Path, PathWriter> index, Pa
value = ValueDeserializationHandler.getInstance().handle(field.rawField(), (String) value)
.orElse(value);
}
FieldAccessor.of(field, parent.get()).set(value);
FieldAccessor.of(field, parent.get(), configuration).set(value);
}));
}

Expand All @@ -203,7 +211,7 @@ private void indexSimpleType(FieldReflector field, Map<Path, PathWriter> index,
value = ValueDeserializationHandler.getInstance().handle(field.rawField(), (String) value)
.orElse(value);
}
FieldAccessor.of(field, parent.get()).set(value);
FieldAccessor.of(field, parent.get(), configuration).set(value);
}));
}
}
Expand All @@ -212,12 +220,12 @@ private void indexSetType(FieldReflector field, Map<Path, PathWriter> index, Pat
Collection<Object> set = new HashSet<>();
Path path = getPathForField(field, parentPath);
var pathWriter = PathWriter.objectInitializer(() -> {
FieldAccessor.of(field, parent.get()).set(set);
FieldAccessor.of(field, parent.get(), configuration).set(set);
return set;
});
if (path.isRoot()) {
pathWriter.setRootInitializer(() -> {
FieldAccessor.of(field, parent.get()).set(set);
FieldAccessor.of(field, parent.get(), configuration).set(set);
return parent.get();
});
}
Expand Down Expand Up @@ -247,12 +255,12 @@ private void indexListType(FieldReflector field, Map<Path, PathWriter> index, Pa
List<Object> list = new ArrayList<>();
Path path = getPathForField(field, parentPath);
var pathWriter = PathWriter.objectInitializer(() -> {
FieldAccessor.of(field, parent.get()).set(list);
FieldAccessor.of(field, parent.get(), configuration).set(list);
return list;
});
if (path.isRoot()) {
pathWriter.setRootInitializer(() -> {
FieldAccessor.of(field, parent.get()).set(list);
FieldAccessor.of(field, parent.get(), configuration).set(list);
return parent.get();
});
}
Expand All @@ -271,10 +279,10 @@ private void indexListTypeArgument(Path path, FieldReflector field, Map<Path, Pa
}
}

private static void indexSimpleTypeListTypeArgument(Path path, Map<Path, PathWriter> index, Collection<Object> list, FieldReflector field, Class<?> typeArgument) {
private void indexSimpleTypeListTypeArgument(Path path, Map<Path, PathWriter> index, Collection<Object> list, FieldReflector field, Class<?> typeArgument) {
Tag tag = field.getAnnotation(Tag.class);
index.put(path.append(Path.parse(tag.items())),
PathWriter.valueInitializer((o) -> list.add(TypeMappers.forType(typeArgument).apply(o))));
PathWriter.valueInitializer((o) -> list.add(TypeMappers.forType(typeArgument, configuration).apply(o))));
}

private void indexComplexListTypeArgument(Map<Path, PathWriter> index, Collection<Object> list, Class<?> typeArgument, FieldReflector field) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@

import io.jonasg.xjx.sax.Attribute;
import io.jonasg.xjx.sax.SaxHandler;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;

public class TypedValueMapSaxHandler implements SaxHandler {

private final Class<?> valueType;

private final XjxConfiguration configuration;

private final Map<String, Object> instance;

private PathBasedSaxHandler<Object> objectPathBasedSaxHandler;

private String activeKey;

public TypedValueMapSaxHandler(MapWithTypeInfo instance) {
public TypedValueMapSaxHandler(MapWithTypeInfo instance, XjxConfiguration configuration) {
this.instance = instance.map();
this.valueType = instance.valueType();
}
this.configuration = configuration;
}

@Override
public void startDocument() {
Expand All @@ -31,7 +35,7 @@ public void startTag(String namespace, String name, List<Attribute> attributes)
if (this.activeKey == null) {
this.activeKey = name;
objectPathBasedSaxHandler = new PathBasedSaxHandler<>(rootTag ->
new PathWriterIndexFactory().createIndexForType(valueType, this.activeKey), this.activeKey);
new PathWriterIndexFactory(configuration).createIndexForType(valueType, this.activeKey), this.activeKey, configuration);
} else {
objectPathBasedSaxHandler.startTag(namespace, name, attributes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

import io.jonasg.xjx.serdes.TypeMappers;
import io.jonasg.xjx.serdes.deserialize.RecordWrapper;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;
import io.jonasg.xjx.serdes.reflector.FieldReflector;

public interface FieldAccessor {

static FieldAccessor of(FieldReflector field, Object instance) {
static FieldAccessor of(FieldReflector field, Object instance, XjxConfiguration configuration) {
if (instance instanceof RecordWrapper<?> recordWrapper) {
return new RecordFieldAccessor(field, recordWrapper);
return new RecordFieldAccessor(field, recordWrapper, configuration);
} else {
var setterFieldAccessor = new SetterFieldAccessor(field, instance);
if (setterFieldAccessor.hasSetterForField()) {
return new SetterFieldAccessor(field, instance);
}
var mapper = TypeMappers.forType(field.type());
var mapper = TypeMappers.forType(field.type(), configuration);
return new ReflectiveFieldAccessor(field, instance, mapper);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.jonasg.xjx.serdes.TypeMappers;
import io.jonasg.xjx.serdes.deserialize.RecordWrapper;
import io.jonasg.xjx.serdes.deserialize.config.XjxConfiguration;
import io.jonasg.xjx.serdes.reflector.FieldReflector;

public class RecordFieldAccessor implements FieldAccessor {
Expand All @@ -10,14 +11,17 @@ public class RecordFieldAccessor implements FieldAccessor {

private final RecordWrapper recordWrapper;

public RecordFieldAccessor(FieldReflector field, RecordWrapper recordWrapper) {
private final XjxConfiguration configuration;

public RecordFieldAccessor(FieldReflector field, RecordWrapper recordWrapper, XjxConfiguration configuration) {
this.field = field;
this.recordWrapper = recordWrapper;
this.configuration = configuration;
}

@Override
public void set(Object value) {
Object mappedValue = TypeMappers.forType(field.type()).apply(value);
Object mappedValue = TypeMappers.forType(field.type(), configuration).apply(value);
recordWrapper.set(field.rawField().getName(), mappedValue);
}
}
Loading

0 comments on commit 2bc0333

Please sign in to comment.