diff --git a/README.md b/README.md
index 0c9cdc1..021e4ff 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,6 @@ You can find the current docs on our [website](https://docs.javawebstack.org/fra
org.javawebstack
abstract-data
- 1.0.5
+ 1.0.6
```
diff --git a/pom.xml b/pom.xml
index f7b062f..401f5c6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
8
8
- 1.0.5-SNAPSHOT
+ 1.0.6-SNAPSHOT
org.javawebstack
@@ -44,18 +44,18 @@
org.yaml
snakeyaml
- 1.33
+ 2.2
org.mongodb
bson
- 4.7.1
+ 4.10.2
true
org.junit.jupiter
junit-jupiter
- 5.8.1
+ 5.10.0
test
diff --git a/src/main/java/org/javawebstack/abstractdata/AbstractArray.java b/src/main/java/org/javawebstack/abstractdata/AbstractArray.java
index 20a613b..2d97ea4 100644
--- a/src/main/java/org/javawebstack/abstractdata/AbstractArray.java
+++ b/src/main/java/org/javawebstack/abstractdata/AbstractArray.java
@@ -352,6 +352,8 @@ public boolean equals(Object obj, boolean strict) {
AbstractArray arr = ((AbstractElement) obj).array(strict);
if (size() != arr.size())
return false;
+ if (size() == 0 || arr.size() == 0)
+ return false;
for (int i = 0; i < size(); i++) {
if (!(get(i).equals(arr.get(i), strict)))
diff --git a/src/main/java/org/javawebstack/abstractdata/AbstractMapper.java b/src/main/java/org/javawebstack/abstractdata/AbstractMapper.java
deleted file mode 100644
index 5069e8a..0000000
--- a/src/main/java/org/javawebstack/abstractdata/AbstractMapper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package org.javawebstack.abstractdata;
-
-import org.javawebstack.abstractdata.mapper.Mapper;
-
-/**
- * @deprecated will be removed in the next release, replaced by org.javawebstack.abstractdata.mapper.Mapper
- */
-@Deprecated
-public class AbstractMapper {
-
- private final Mapper mapper = new Mapper();
- private NamingPolicy namingPolicy = NamingPolicy.NONE;
- private String dateFormat = "yyyy-MM-dd HH:mm:ss";
-
- public AbstractMapper setNamingPolicy(NamingPolicy namingPolicy) {
- this.namingPolicy = namingPolicy;
- mapper.namingPolicy(namingPolicy.getMapperPolicy());
- return this;
- }
-
- public boolean shouldOmitNull() {
- return mapper.shouldOmitNull();
- }
-
- public AbstractMapper setOmitNull(boolean omitNull) {
- this.mapper.omitNull(omitNull);
- return this;
- }
-
- public NamingPolicy getNamingPolicy() {
- return namingPolicy;
- }
-
- public AbstractMapper setExposeRequired(boolean exposeRequired) {
- mapper.requireExpose(exposeRequired);
- return this;
- }
-
- public boolean isExposeRequired() {
- return mapper.isExposeRequired();
- }
-
- public AbstractMapper setDateFormat(String dateFormat) {
- this.dateFormat = dateFormat;
- mapper.dateFormat(dateFormat);
- return this;
- }
-
- public String getDateFormat() {
- return dateFormat;
- }
-
- public AbstractElement toAbstract(Object object) {
- return mapper.map(object);
- }
-
- public T fromAbstract(AbstractElement element, Class type) {
- if (element == null)
- return null;
- return mapper.map(element, type);
- }
-
-
-}
diff --git a/src/main/java/org/javawebstack/abstractdata/mapper/DefaultMappers.java b/src/main/java/org/javawebstack/abstractdata/mapper/DefaultMappers.java
index d8f49d5..2ed0b60 100644
--- a/src/main/java/org/javawebstack/abstractdata/mapper/DefaultMappers.java
+++ b/src/main/java/org/javawebstack/abstractdata/mapper/DefaultMappers.java
@@ -9,7 +9,6 @@
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
-import java.sql.Time;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
@@ -28,7 +27,7 @@ public final class DefaultMappers {
public static final FallbackMapper FALLBACK = new FallbackMapper();
public static Map, MapperTypeAdapter> create() {
- Map, MapperTypeAdapter> map = new HashMap<>();
+ Map, MapperTypeAdapter> map = new LinkedHashMap<>();
for (MapperTypeAdapter adapter : new MapperTypeAdapter[]{
ABSTRACT,
@@ -64,20 +63,20 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
}
if (type.equals(char.class) || type.equals(Character.class)) {
if (type.isPrimitive() && element.isNull())
- throw new MapperWrongTypeException(context.getField().getName(), "number", "null");
+ throw new MapperWrongTypeException(context.getFieldName(), "number", "null");
String s = element.string(context.getMapper().isStrict());
if (s.length() != 1)
- throw new MapperException("Expected string of length 1 for field " + context.getField().getName() + " but received " + s.length());
+ throw new MapperException("Expected string of length 1 for field " + context.getFieldName() + " but received " + s.length());
return s.charAt(0);
}
if (type.equals(Boolean.class) || type.equals(boolean.class)) {
if (type.isPrimitive() && element.isNull())
- throw new MapperWrongTypeException(context.getField().getName(), "number", "null");
+ throw new MapperWrongTypeException(context.getFieldName(), "number", "null");
return element.bool(context.getMapper().isStrict());
}
if (Number.class.isAssignableFrom(type) || type.isPrimitive()) {
if (type.isPrimitive() && element.isNull())
- throw new MapperWrongTypeException(context.getField().getName(), "number", "null");
+ throw new MapperWrongTypeException(context.getFieldName(), "number", "null");
if (type.equals(int.class) || type.equals(Integer.class))
return element.number(context.getMapper().isStrict()).intValue();
if (type.equals(long.class) || type.equals(Long.class))
@@ -95,9 +94,9 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
return element.number(context.getMapper().isStrict());
}
} catch (AbstractCoercingException ex) {
- throw new MapperWrongTypeException(context.getField().getName(), "string", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getField() != null ? context.getFieldName() : "", "string", Helpers.typeName(element));
}
- throw new MapperWrongTypeException(context.getField().getName(), "primitive", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "primitive", Helpers.typeName(element));
}
public Class>[] getSupportedTypes() {
@@ -136,7 +135,7 @@ public AbstractElement toAbstract(MapperContext context, Object value) throws Ma
}
public Object fromAbstract(MapperContext context, AbstractElement element, Class> type) throws MapperException {
- if (type.equals(List.class) || type.equals(AbstractList.class))
+ if (type.equals(List.class) || type.equals(Collection.class) || type.equals(AbstractList.class))
type = ArrayList.class;
if (type.equals(Set.class))
type = HashSet.class;
@@ -157,12 +156,13 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
} catch (AbstractCoercingException ex) {
- throw new MapperWrongTypeException(context.getField().getName(), "array", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "array", Helpers.typeName(element));
}
}
public Class>[] getSupportedTypes() {
return new Class[]{
+ Collection.class,
List.class,
ArrayList.class,
CopyOnWriteArrayList.class,
@@ -216,7 +216,7 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
} catch (AbstractCoercingException ex) {
- throw new MapperWrongTypeException(context.getField().getName(), "object", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "object", Helpers.typeName(element));
}
}
@@ -284,7 +284,7 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
return new Timestamp(date.getTime());
throw new MapperException("Unsupported date type '" + type.getName() + "'");
} catch (ParseException | NumberFormatException | AbstractCoercingException ex) {
- throw new MapperException("Failed to parse date '" + element.string() + "'" + (context.getField() != null ? (" for field '" + context.getField().getName() + "'") : ""));
+ throw new MapperException("Failed to parse date '" + element.string() + "'" + (context.getField() != null ? (" for field '" + context.getFieldName() + "'") : ""));
}
}
@@ -311,13 +311,13 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
if (type.equals(AbstractElement.class))
return element;
if (type.equals(AbstractNull.class) && !(element instanceof AbstractNull))
- throw new MapperWrongTypeException(context.getField().getName(), "null", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "null", Helpers.typeName(element));
if (type.equals(AbstractPrimitive.class) && !(element instanceof AbstractPrimitive))
- throw new MapperWrongTypeException(context.getField().getName(), "primitive", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "primitive", Helpers.typeName(element));
if (type.equals(AbstractObject.class) && !(element instanceof AbstractObject))
- throw new MapperWrongTypeException(context.getField().getName(), "object", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "object", Helpers.typeName(element));
if (type.equals(AbstractArray.class) && !(element instanceof AbstractArray))
- throw new MapperWrongTypeException(context.getField().getName(), "array", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "array", Helpers.typeName(element));
return element;
}
@@ -368,7 +368,7 @@ public AbstractElement toAbstract(MapperContext context, Object value) throws Ma
public Object fromAbstract(MapperContext context, AbstractElement element, Class type) throws MapperException {
if (type.isEnum()) {
if (!element.isString())
- throw new MapperWrongTypeException(context.getField().getName(), "string", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "string", Helpers.typeName(element));
try {
return Enum.valueOf(type, element.string());
} catch (IllegalArgumentException ex) {
@@ -378,7 +378,7 @@ public Object fromAbstract(MapperContext context, AbstractElement element, Class
if (type.equals(UUID.class))
return UUID.fromString(element.string());
if (!element.isObject())
- throw new MapperWrongTypeException(context.getField().getName(), "object", Helpers.typeName(element));
+ throw new MapperWrongTypeException(context.getFieldName(), "object", Helpers.typeName(element));
MapperTypeSpec spec = MapperTypeSpec.get(type);
if (spec == null)
throw new MapperException("Unmappable type '" + type.getName() + "'");
diff --git a/src/main/java/org/javawebstack/abstractdata/mapper/Mapper.java b/src/main/java/org/javawebstack/abstractdata/mapper/Mapper.java
index 0163804..ad2f235 100644
--- a/src/main/java/org/javawebstack/abstractdata/mapper/Mapper.java
+++ b/src/main/java/org/javawebstack/abstractdata/mapper/Mapper.java
@@ -41,7 +41,19 @@ public T map(MapperContext context, AbstractElement element, Class type)
}
if(context.getAdapter() != null)
return (T) context.getAdapter().fromAbstract(context, element, type);
- return (T) adapters.getOrDefault(type, DefaultMappers.FALLBACK).fromAbstract(context, element, type);
+ return (T) findAdapter(type).fromAbstract(context, element, type);
+ }
+
+ private MapperTypeAdapter findAdapter(Class> type) {
+ MapperTypeAdapter adapter = adapters.get(type);
+ if(adapter != null)
+ return adapter;
+ for(Class> t : adapters.keySet()) {
+ if(t.isAssignableFrom(type)) {
+ return adapters.get(t);
+ }
+ }
+ return DefaultMappers.FALLBACK;
}
public AbstractElement map(Object obj) throws MapperException {
@@ -59,7 +71,7 @@ public AbstractElement map(MapperContext context, Object obj) throws MapperExcep
}
if(context.getAdapter() != null)
return context.getAdapter().toAbstract(context, obj);
- return adapters.getOrDefault(obj.getClass(), DefaultMappers.FALLBACK).toAbstract(context, obj);
+ return findAdapter(obj.getClass()).toAbstract(context, obj);
}
public Mapper strict() {
diff --git a/src/main/java/org/javawebstack/abstractdata/mapper/MapperContext.java b/src/main/java/org/javawebstack/abstractdata/mapper/MapperContext.java
index 94492f0..baecb73 100644
--- a/src/main/java/org/javawebstack/abstractdata/mapper/MapperContext.java
+++ b/src/main/java/org/javawebstack/abstractdata/mapper/MapperContext.java
@@ -34,6 +34,10 @@ public Field getField() {
return field;
}
+ public String getFieldName() {
+ return field != null ? field.getName() : null;
+ }
+
public boolean isNested() {
return field != null;
}
diff --git a/src/main/java/org/javawebstack/abstractdata/xml/LegacyXMLParser.java b/src/main/java/org/javawebstack/abstractdata/xml/LegacyXMLParser.java
new file mode 100644
index 0000000..ddde196
--- /dev/null
+++ b/src/main/java/org/javawebstack/abstractdata/xml/LegacyXMLParser.java
@@ -0,0 +1,59 @@
+package org.javawebstack.abstractdata.xml;
+
+import org.w3c.dom.*;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+/*
+This is a temporary implementation using Java's built-in xml implementation
+ */
+public class LegacyXMLParser {
+
+ @Deprecated
+ protected static XMLElement parse(String s) {
+ try {
+ Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8)));
+ return convert(document.getDocumentElement());
+ } catch (SAXException | IOException | ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static XMLElement convert(Element element) {
+ String tag = element.getTagName();
+ XMLElement e = new XMLElement(tag);
+ NamedNodeMap attrNodeMap = element.getAttributes();
+ for(int i=0; i dumpLines(XMLNode node) {
+ if(node instanceof XMLTextNode) {
+ XMLTextNode textNode = (XMLTextNode) node;
+ return Arrays.asList(escape(textNode.getText(), true));
+ }
+ XMLElement element = (XMLElement) node;
+ List lines = new ArrayList<>();
+ boolean selfClosing = useSelfClosing && element.getChildNodes().size() == 0;
+ String openingTag = renderOpeningTag(element.tagName(), element.getAttributes(), selfClosing);
+ String closingTag = selfClosing ? "" : "" + escape(element.tagName(), false) + ">";
+ if(pretty) {
+ if(element.getChildNodes().size() == 0) {
+ lines.add(openingTag + closingTag);
+ } else if(element.getChildNodes().size() == 1 && element.getChildNodes().get(0) instanceof XMLTextNode) {
+ lines.add(openingTag + escape(((XMLTextNode) element.getChildNodes().get(0)).getText(), true) + closingTag);
+ } else {
+ lines.add(openingTag);
+ for(XMLNode child : element.getChildNodes()) {
+ lines.addAll(dumpLines(child).stream().map(l -> indent + l).collect(Collectors.toList()));
+ }
+ lines.add(closingTag);
+ }
+ } else {
+ StringBuilder sb = new StringBuilder();
+ sb.append(openingTag);
+ for(XMLNode child : element.getChildNodes())
+ dumpLines(child).forEach(sb::append);
+ sb.append(closingTag);
+ lines.add(sb.toString());
+ }
+ return lines;
+ }
+
+ public String renderOpeningTag(String tagName, Map attributes, boolean selfClosing) {
+ StringBuilder sb = new StringBuilder();
+ sb.append('<');
+ sb.append(escape(tagName, false));
+ for(String attrName : attributes.keySet()) {
+ sb.append(" ");
+ sb.append(escape(attrName, false));
+ sb.append('=');
+ sb.append('"');
+ sb.append(escape(attributes.get(attrName), false));
+ sb.append('"');
+ }
+ if(selfClosing)
+ sb.append("/");
+ sb.append('>');
+ return sb.toString();
+ }
+
+ private String escape(String value, boolean text) {
+ value = value.replace("<", "<");
+ value = value.replace("&", "&");
+ if(!text) {
+ value = value.replace("\"", """);
+ value = value.replace("'", "'");
+ value = value.replace(">", ">");
+ }
+ return value;
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/abstractdata/xml/XMLElement.java b/src/main/java/org/javawebstack/abstractdata/xml/XMLElement.java
new file mode 100644
index 0000000..c9771ab
--- /dev/null
+++ b/src/main/java/org/javawebstack/abstractdata/xml/XMLElement.java
@@ -0,0 +1,98 @@
+package org.javawebstack.abstractdata.xml;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class XMLElement implements XMLNode {
+
+ private String tagName;
+ private final Map attributes = new LinkedHashMap<>();
+ private final List childNodes = new ArrayList<>();
+
+ public XMLElement(String tagName) {
+ if(tagName == null)
+ throw new IllegalArgumentException("tagName can not be null");
+ this.tagName = tagName;
+ }
+
+ public XMLElement(String tagName, String text) {
+ this(tagName);
+ if(text == null)
+ throw new IllegalArgumentException("text can not be null");
+ text(text);
+ }
+
+ public String tagName() {
+ return tagName;
+ }
+
+ public XMLElement tagName(String tagName) {
+ this.tagName = tagName;
+ return this;
+ }
+
+ public String text() {
+ return childNodes.stream()
+ .filter(n -> n instanceof XMLTextNode)
+ .map(n -> ((XMLTextNode) n))
+ .map(XMLTextNode::getText)
+ .collect(Collectors.joining());
+ }
+
+ public XMLElement text(String text) {
+ childNodes.clear();
+ childNodes.add(new XMLTextNode(text));
+ return this;
+ }
+
+ public String attr(String name) {
+ return attributes.get(name);
+ }
+
+ public XMLElement attr(String name, String value) {
+ if(value == null) {
+ attributes.remove(name);
+ return this;
+ }
+ attributes.put(name, value);
+ return this;
+ }
+
+ public XMLElement ns(String namespace) {
+ attr("xmlns", namespace);
+ return this;
+ }
+
+ public XMLElement ns(String alias, String namespace) {
+ attr("xmlns:" + alias, namespace);
+ return this;
+ }
+
+ public XMLElement child(XMLNode childNode) {
+ childNodes.add(childNode);
+ return this;
+ }
+
+ public XMLElement onlyIf(boolean condition, Consumer fn) {
+ if(condition)
+ fn.accept(this);
+ return this;
+ }
+
+ public Map getAttributes() {
+ return attributes;
+ }
+
+ public List getChildNodes() {
+ return childNodes;
+ }
+
+ public static XMLElement from(String xmlString) {
+ return new XMLParser().parse(xmlString);
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/abstractdata/xml/XMLNode.java b/src/main/java/org/javawebstack/abstractdata/xml/XMLNode.java
new file mode 100644
index 0000000..aef7525
--- /dev/null
+++ b/src/main/java/org/javawebstack/abstractdata/xml/XMLNode.java
@@ -0,0 +1,21 @@
+package org.javawebstack.abstractdata.xml;
+
+public interface XMLNode {
+
+ default String toXML() {
+ return new XMLDumper().dump(this);
+ }
+
+ default String toXML(boolean pretty) {
+ return new XMLDumper().setPretty(pretty).dump(this);
+ }
+
+ default XMLElement asElement() {
+ return (XMLElement) this;
+ }
+
+ default XMLTextNode asTextNode() {
+ return (XMLTextNode) this;
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/abstractdata/xml/XMLParser.java b/src/main/java/org/javawebstack/abstractdata/xml/XMLParser.java
new file mode 100644
index 0000000..da3aa3e
--- /dev/null
+++ b/src/main/java/org/javawebstack/abstractdata/xml/XMLParser.java
@@ -0,0 +1,9 @@
+package org.javawebstack.abstractdata.xml;
+
+public class XMLParser {
+
+ public XMLElement parse(String source) {
+ return LegacyXMLParser.parse(source);
+ }
+
+}
diff --git a/src/main/java/org/javawebstack/abstractdata/xml/XMLTextNode.java b/src/main/java/org/javawebstack/abstractdata/xml/XMLTextNode.java
new file mode 100644
index 0000000..ce63d17
--- /dev/null
+++ b/src/main/java/org/javawebstack/abstractdata/xml/XMLTextNode.java
@@ -0,0 +1,19 @@
+package org.javawebstack.abstractdata.xml;
+
+public class XMLTextNode implements XMLNode {
+
+ private String text;
+
+ public XMLTextNode(String text) {
+ this.text = text;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public void setText(String text) {
+ this.text = text;
+ }
+
+}