diff --git a/README.md b/README.md
index 7eafa54..e683429 100644
--- a/README.md
+++ b/README.md
@@ -218,6 +218,43 @@ Example XML document:
```
+### Map mixed tags within a container to multiple collections
+
+Xjx is able to map repeated mixed tags within a container or
+at the root tag to multiple collections.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+```java
+class WeatherReport {
+
+ @Tag(path = "/WeatherReport/Locations", items = "Town")
+ List towns;
+
+ @Tag(path = "/WeatherReport/Locations", items = "City")
+ List cities;
+}
+
+class Town {
+ @Tag(path = "/WeatherReport/Locations/Town", attribute = "name")
+ String name;
+}
+```
+
### Map types
Maps can be deserialized either as a field or a top-level type. Consider the following XML document:
diff --git a/pom.xml b/pom.xml
index f53acab..78c6a82 100644
--- a/pom.xml
+++ b/pom.xml
@@ -42,14 +42,17 @@
17
17
+ 3.0.0
+ 3.10.1
+ 3.2.0
+ 3.2.1
+ 1.5.0
+ 3.1.2
+ 3.3.0
- 1.5.0
- 3.1.2
- 3.0.0
- 3.10.1
- 3.2.0
- 3.2.1
1.9.0
+ 5.10.1
+ 3.23.1
@@ -57,14 +60,14 @@
org.junit
junit-bom
- 5.10.1
+ ${junit-bom.version}
pom
import
org.assertj
assertj-core
- 3.23.1
+ ${assertj-core.version}
test
@@ -212,6 +215,7 @@
org.apache.maven.plugins
maven-source-plugin
+ ${maven-source-plugin.version}
attach-sources
diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/LazySupplier.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/LazySupplier.java
new file mode 100644
index 0000000..539570d
--- /dev/null
+++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/LazySupplier.java
@@ -0,0 +1,26 @@
+package io.jonasg.xjx.serdes.deserialize;
+
+import java.util.function.Supplier;
+
+public class LazySupplier implements Supplier {
+ private T instance;
+ private Supplier initializer;
+
+ public LazySupplier(Supplier initializer) {
+ this.initializer = initializer;
+ }
+
+ @Override
+ public T get() {
+ if (instance == null) {
+ instance = initializer.get();
+ }
+ return instance;
+ }
+
+ public void reset(Supplier supplier) {
+ this.instance = null;
+ this.initializer = supplier;
+ }
+}
+
diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java
index 6909396..a180a55 100644
--- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java
+++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathBasedSaxHandler.java
@@ -13,7 +13,7 @@
public class PathBasedSaxHandler implements SaxHandler {
- private final Function> indexSupplier;
+ private final Function indexSupplier;
private final XjxConfiguration configuration;
@@ -23,7 +23,7 @@ public class PathBasedSaxHandler implements SaxHandler {
private Path path;
- private Map pathWriterIndex;
+ private PathWriterIndex pathWriterIndex;
private String data;
@@ -31,12 +31,12 @@ public class PathBasedSaxHandler implements SaxHandler {
private String mapStartTag;
- public PathBasedSaxHandler(Function> indexSupplier, XjxConfiguration configuration) {
+ public PathBasedSaxHandler(Function indexSupplier, XjxConfiguration configuration) {
this.indexSupplier = indexSupplier;
this.configuration = configuration;
}
- public PathBasedSaxHandler(Function> indexSupplier, String rootTag, XjxConfiguration configuration) {
+ public PathBasedSaxHandler(Function indexSupplier, String rootTag, XjxConfiguration configuration) {
this.indexSupplier = indexSupplier;
this.rootTag = rootTag;
this.configuration = configuration;
@@ -57,24 +57,26 @@ public void startTag(String namespace, String name, List attributes)
handleRootTag(name);
} else {
this.path = path.append(name);
- var pathWriter = pathWriterIndex.get(path);
- if (pathWriter != null) {
- if (pathWriter.getObjectInitializer() != null) {
- Object object = pathWriter.getObjectInitializer().get();
- if (object instanceof Map) {
- this.mapRootSaxHandlerDelegate = new MapRootSaxHandler((HashMap) object);
- this.mapStartTag = name;
- } else if (object instanceof MapWithTypeInfo mapWithTypeInfo) {
- this.mapRootSaxHandlerDelegate = new TypedValueMapSaxHandler(mapWithTypeInfo, configuration);
- this.mapStartTag = name;
- }
- this.objectInstances.push(object);
- }
+ List pathWriters = pathWriterIndex.get(path);
+ if (pathWriters != null) {
+ pathWriters.forEach(pathWriter -> {
+ if (pathWriter.getObjectInitializer() != null) {
+ Object object = pathWriter.getObjectInitializer().get();
+ if (object instanceof Map) {
+ this.mapRootSaxHandlerDelegate = new MapRootSaxHandler((HashMap) object);
+ this.mapStartTag = name;
+ } else if (object instanceof MapWithTypeInfo mapWithTypeInfo) {
+ this.mapRootSaxHandlerDelegate = new TypedValueMapSaxHandler(mapWithTypeInfo, configuration);
+ this.mapStartTag = name;
+ }
+ this.objectInstances.push(object);
+ }
+ });
}
attributes.forEach(a -> {
- var attributeWriter = pathWriterIndex.get(path.appendAttribute(a.name()));
- if (attributeWriter != null) {
- attributeWriter.getValueInitializer().accept(a.value());
+ List attributeWriters = pathWriterIndex.get(path.appendAttribute(a.name()));
+ if (attributeWriters != null) {
+ attributeWriters.stream().forEach(attributeWriter -> attributeWriter.getValueInitializer().accept(a.value()));
}
});
}
@@ -89,17 +91,19 @@ public void endTag(String namespace, String name) {
this.mapRootSaxHandlerDelegate.endTag(namespace, name);
}
}
- PathWriter pathWriter = pathWriterIndex.get(path);
- if (pathWriter != null) {
- if (data != null) {
- pathWriter.getValueInitializer().accept(data);
- }
- if (pathWriter.getObjectInitializer() != null && !objectInstances.isEmpty() && objectInstances.size() != 1) {
- if (pathWriter.getValueInitializer() != null) {
- pathWriter.getValueInitializer().accept(objectInstances.peek());
+ List pathWriters = pathWriterIndex.get(path);
+ if (pathWriters != null) {
+ pathWriters.forEach(pathWriter -> {
+ if (data != null) {
+ pathWriter.getValueInitializer().accept(data);
}
- objectInstances.pop();
- }
+ if (pathWriter.getObjectInitializer() != null && !objectInstances.isEmpty() && objectInstances.size() != 1) {
+ if (pathWriter.getValueInitializer() != null) {
+ pathWriter.getValueInitializer().accept(objectInstances.peek());
+ }
+ objectInstances.pop();
+ }
+ });
}
data = null;
path = path.pop();
@@ -117,15 +121,17 @@ private void handleRootTag(String name) {
this.pathWriterIndex = indexSupplier.apply(name);
this.rootTag = name;
path = Path.of(name);
- PathWriter pathWriter = pathWriterIndex.get(path);
- if (pathWriter != null) {
- Object parent = pathWriter.getRootInitializer().get();
- if (parent instanceof MapAsRoot mapAsRoot) {
- this.mapRootSaxHandlerDelegate = new MapRootSaxHandler(mapAsRoot.map());
- this.objectInstances.push(mapAsRoot.root());
- } else {
- this.objectInstances.push(parent);
- }
+ List pathWriters = pathWriterIndex.get(path);
+ if (pathWriters != null) {
+ pathWriters.forEach(pathWriter -> {
+ Object parent = pathWriter.getRootInitializer().get();
+ if (parent instanceof MapAsRoot mapAsRoot) {
+ this.mapRootSaxHandlerDelegate = new MapRootSaxHandler(mapAsRoot.map());
+ this.objectInstances.push(mapAsRoot.root());
+ } else {
+ this.objectInstances.push(parent);
+ }
+ });
}
}
diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndex.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndex.java
new file mode 100644
index 0000000..16e22f9
--- /dev/null
+++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndex.java
@@ -0,0 +1,33 @@
+package io.jonasg.xjx.serdes.deserialize;
+
+import io.jonasg.xjx.serdes.Path;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PathWriterIndex {
+
+ private final Map> index = new HashMap<>();
+
+ public void put(Path path, PathWriter pathWriter) {
+ index.compute(path, (p, w) -> {
+ if (w == null) {
+ List pathWriters = new ArrayList<>();
+ pathWriters.add(pathWriter);
+ return pathWriters;
+ }
+ w.add(pathWriter);
+ return w;
+ });
+ }
+
+ public void putAll(PathWriterIndex pathWriterIndex) {
+ index.putAll(pathWriterIndex.index);
+ }
+
+ public List get(Path path) {
+ return index.get(path);
+ }
+}
diff --git a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java
index bd1bf23..f7d8b53 100644
--- a/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java
+++ b/xjx-serdes/src/main/java/io/jonasg/xjx/serdes/deserialize/PathWriterIndexFactory.java
@@ -39,13 +39,13 @@ public PathWriterIndexFactory(XjxConfiguration xjxConfiguration) {
this.configuration = xjxConfiguration;
}
- public Map createIndexForType(Class type, String rootTag) {
+ public PathWriterIndex createIndexForType(Class type, String rootTag) {
Path path = Path.of(rootTag);
return buildIndex(type, path);
}
- private Map buildIndex(Class type, Path path) {
- Map index = new HashMap<>();
+ private PathWriterIndex buildIndex(Class type, Path path) {
+ var index = new PathWriterIndex();
if (type.isRecord()) {
RecordWrapper recordWrapper = new RecordWrapper<>(type);
index.put(path, PathWriter.rootInitializer(() -> recordWrapper));
@@ -57,16 +57,16 @@ private Map buildIndex(Class type, Path path) {
}
}
- private Map doBuildIndex(Class> type,
+ private PathWriterIndex doBuildIndex(Class> type,
Path path,
- Map index,
+ PathWriterIndex index,
Supplier