From 56d9c1bf3e529a15c9a082ce18c76f608fd1a2db Mon Sep 17 00:00:00 2001 From: Lars Knickrehm Date: Thu, 13 Dec 2018 08:59:27 +0100 Subject: [PATCH] First commit --- .gitignore | 8 + .travis.yml | 2 + LICENSE.txt | 7 + README.md | 57 +++ pom.xml | 63 ++++ .../de/larssh/json/dom/GsonDomValue.java | 99 ++++++ .../de/larssh/json/dom/JacksonDomValue.java | 92 +++++ .../de/larssh/json/dom/JsonDomAttribute.java | 177 ++++++++++ .../de/larssh/json/dom/JsonDomDocument.java | 329 ++++++++++++++++++ .../de/larssh/json/dom/JsonDomElement.java | 306 ++++++++++++++++ .../larssh/json/dom/JsonDomNamedNodeMap.java | 97 ++++++ .../de/larssh/json/dom/JsonDomNode.java | 265 ++++++++++++++ .../de/larssh/json/dom/JsonDomNodeList.java | 41 +++ .../dom/JsonDomNotSupportedException.java | 22 ++ .../de/larssh/json/dom/JsonDomType.java | 48 +++ .../de/larssh/json/dom/JsonDomValue.java | 47 +++ .../json/dom/JsonDomXPathExpressions.java | 61 ++++ .../de/larssh/json/dom/package-info.java | 64 ++++ 18 files changed, 1785 insertions(+) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/lombok/de/larssh/json/dom/GsonDomValue.java create mode 100644 src/main/lombok/de/larssh/json/dom/JacksonDomValue.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomAttribute.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomDocument.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomElement.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomNamedNodeMap.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomNode.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomNodeList.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomNotSupportedException.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomType.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomValue.java create mode 100644 src/main/lombok/de/larssh/json/dom/JsonDomXPathExpressions.java create mode 100644 src/main/lombok/de/larssh/json/dom/package-info.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f8593f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/.settings/ +/src/main/lombok/lombok.config +/src/test/lombok/lombok.config +/src/lombok.config +/target/ +/.checkstyle +/.classpath +/.project \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8ccadd4 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,2 @@ +language: java +install: true \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..544af89 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright (c) Lars Knickrehm + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..78b93c5 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# JSON DOM +A DOM implementation for JSON. While DOM is widely used for XML structured data, it can be useful for JSON data, too. These classes wrap generic JSON elements to fit the DOM interfaces. + +## Getting started +Here's a Maven dependency example: + + + de.lars-sh + json-dom + + + +To learn more about the JSON DOM structure check out JavaDoc of the package `de.larssh.json.dom`. + +## JSON Parsers +JSON DOM does not come with its own JSON parser or JSON element objects. Instead the interface `JsonDomValue` is made up to integrate existing parsers. + +### Using GSON +JSON DOM comes with a GSON implementation called `GsonDomValue`. Take a look at the following example to get a DOM Document out of a GSON JSON element. + + // The JsonElement is part of GSON + JsonElement jsonElement = ...; + + // At first the GSON object need to be wrapped using its JSON DOM implementation: GsonDomValue + GsonDomValue gsonDomValue = new GsonDomValue(jsonElement); + + // Finally you can either create a DOM Document out of it... + JsonDomDocument jsonDomDocument = new JsonDomDocument(gsonDomValue); + + // ...or even use the helper methods inside JsonDomXPathExpressions to evaluate XPathExpressions to JSON elements. + XPathExpression xPathExpression = ...; + JsonElement jsonElement = JsonDomXPathExpressions.getJsonElement(jsonDomDocument, xPathExpression); + +Note: JSON DOM does not come with GSON dependencies itself. To use `GsonDomValue` please add GSON to your dependencies. + +### Using Jackson +JSON DOM comes with a Jackson implementation called `JacksonDomValue`. Take a look at the following example coding to get a DOM Document out of a Jackson JSON node. + + // The JsonNode is part of Jackson + JsonNode jsonNode = ...; + + // At first the Jackson object need to be wrapped using its JSON DOM implementation: JacksonDomValue + JacksonDomValue jacksonDomValue = new JacksonDomValue(jsonNode); + + // Finally you can either create a DOM Document out of it... + JsonDomDocument jsonDomDocument = new JsonDomDocument(jacksonDomValue); + + // ...or even use the helper methods inside JsonDomXPathExpressions to evaluate XPathExpressions to JSON elements. + XPathExpression xPathExpression = ...; + JsonNode jsonNode = JsonDomXPathExpressions.getJsonElement(jsonDomDocument, xPathExpression); + +Note: JSON DOM does not come with Jackson dependencies itself. To use `JacksonDomValue` please add Jackson to your dependencies. + +### Using any other JSON parser +The interface `JsonDomValue` is used to wrap elements as JSON DOM compatible value. Implement it for your concerns and feel free to push your code back to this repository. + +Working with your custom JSON DOM value implementation works similar to the above GSON and Jackson implementations. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..48d5dd9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + json-dom + 0.9.0 + + JSON DOM + A DOM implementation for JSON. While DOM is widely used for XML structured data, it can be useful for JSON data, too. These classes wrap generic JSON elements to fit the DOM interfaces. + https://github.com/lars-sh/json-dom + + + de.lars-sh + parent + 0.9.1 + + + + + MIT License + https://opensource.org/licenses/MIT + + + + + + Lars Knickrehm + mail@lars-sh.de + https://lars-sh.de/ + + + + + scm:git:git@github.com:lars-sh/json-dom.git + scm:git:git@github.com:lars-sh/json-dom.git + https://github.com/lars-sh/json-dom + + + + GitHub Issues + https://github.com/lars-sh/json-dom/issues + + + + Travis + https://travis-ci.org/lars-sh/json-dom + + + + + com.fasterxml.jackson.core + jackson-databind + true + + + com.google.code.gson + gson + true + + + \ No newline at end of file diff --git a/src/main/lombok/de/larssh/json/dom/GsonDomValue.java b/src/main/lombok/de/larssh/json/dom/GsonDomValue.java new file mode 100644 index 0000000..9af57bb --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/GsonDomValue.java @@ -0,0 +1,99 @@ +package de.larssh.json.dom; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.w3c.dom.DOMException; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Implementation of {@link JsonDomValue} for {@link com.google.gson.Gson} and + * its {@link JsonElement}. + */ +@Getter +@RequiredArgsConstructor +@EqualsAndHashCode(onParam_ = { @Nullable }) +public class GsonDomValue implements JsonDomValue { + /** + * Wrapped JSON element + * + * @return wrapped JSON element + */ + JsonElement jsonElement; + + /** {@inheritDoc} */ + @NonNull + @Override + public Map getChildren() { + final Map children = new LinkedHashMap<>(); + final JsonElement element = getJsonElement(); + if (element.isJsonArray()) { + int index = 0; + for (final JsonElement child : element.getAsJsonArray()) { + children.put(JsonDomElement.ARRAY_ITEM_NODE_NAME_PREFIX + Integer.toString(index), + new GsonDomValue(child)); + index += 1; + } + } else if (element.isJsonObject()) { + for (final Entry entry : element.getAsJsonObject().entrySet()) { + children.put(entry.getKey(), new GsonDomValue(entry.getValue())); + } + } + return children; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getTextValue() { + final JsonElement element = getJsonElement(); + return element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() + ? element.getAsString() + : element.toString(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomType getType() { + final JsonElement element = getJsonElement(); + if (element.isJsonArray()) { + return JsonDomType.ARRAY; + } + if (element.isJsonNull()) { + return JsonDomType.NULL; + } + if (element.isJsonObject()) { + return JsonDomType.OBJECT; + } + + final JsonPrimitive jsonPrimitive = element.getAsJsonPrimitive(); + if (jsonPrimitive.isBoolean()) { + return JsonDomType.BOOLEAN; + } + if (jsonPrimitive.isNumber()) { + return JsonDomType.NUMBER; + } + if (jsonPrimitive.isString()) { + return JsonDomType.STRING; + } + + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Unknown JSON data type."); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String toString() { + return getJsonElement().toString(); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JacksonDomValue.java b/src/main/lombok/de/larssh/json/dom/JacksonDomValue.java new file mode 100644 index 0000000..b4c48bb --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JacksonDomValue.java @@ -0,0 +1,92 @@ +package de.larssh.json.dom; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.w3c.dom.DOMException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeType; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Implementation of {@link JsonDomValue} for {@link com.fasterxml.jackson} and + * its {@link JsonNode}. + */ +@Getter +@RequiredArgsConstructor +@EqualsAndHashCode(onParam_ = { @Nullable }) +public class JacksonDomValue implements JsonDomValue { + /** + * Wrapped JSON element + * + * @return wrapped JSON element + */ + JsonNode jsonElement; + + /** {@inheritDoc} */ + @NonNull + @Override + public Map getChildren() { + final Map children = new LinkedHashMap<>(); + final JsonNode node = getJsonElement(); + if (node.isArray()) { + int index = 0; + for (final JsonNode child : node) { + children.put(JsonDomElement.ARRAY_ITEM_NODE_NAME_PREFIX + Integer.toString(index), + new JacksonDomValue(child)); + index += 1; + } + } else if (node.isObject()) { + node.fields() + .forEachRemaining(entry -> children.put(entry.getKey(), new JacksonDomValue(entry.getValue()))); + } + return children; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getTextValue() { + final JsonNode node = getJsonElement(); + return node.isTextual() ? node.asText() : node.toString(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomType getType() { + final JsonNodeType nodeType = getJsonElement().getNodeType(); + if (nodeType == JsonNodeType.ARRAY) { + return JsonDomType.ARRAY; + } + if (nodeType == JsonNodeType.BOOLEAN) { + return JsonDomType.BOOLEAN; + } + if (nodeType == JsonNodeType.NULL) { + return JsonDomType.NULL; + } + if (nodeType == JsonNodeType.NUMBER) { + return JsonDomType.NUMBER; + } + if (nodeType == JsonNodeType.OBJECT) { + return JsonDomType.OBJECT; + } + if (nodeType == JsonNodeType.STRING) { + return JsonDomType.STRING; + } + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Unknown JSON node type."); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String toString() { + return getJsonElement().toString(); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomAttribute.java b/src/main/lombok/de/larssh/json/dom/JsonDomAttribute.java new file mode 100644 index 0000000..26dccb0 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomAttribute.java @@ -0,0 +1,177 @@ +package de.larssh.json.dom; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; +import java.util.function.Supplier; + +import org.w3c.dom.Attr; +import org.w3c.dom.Node; +import org.w3c.dom.TypeInfo; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +/** + * JSON DOM implementation of {@link Attr}. + * + * @param implementation specific JSON element type + */ +@Getter +@ToString +@EqualsAndHashCode(callSuper = true, onParam_ = { @Nullable }) +public class JsonDomAttribute extends JsonDomNode implements Attr { + /** + * Supplier providing the attribute value when requested. The value is not + * cached. + * + * @return value provider + */ + Supplier valueProvider; + + /** + * Constructor of {@link JsonDomAttribute}. + * + * @param parentNode parent node + * @param nodeName node name + * @param valueProvider value provider + */ + public JsonDomAttribute(final JsonDomElement parentNode, + final String nodeName, + final Supplier valueProvider) { + super(parentNode, nodeName); + + this.valueProvider = valueProvider; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomNamedNodeMap> getAttributes() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getChildNodes() { + return new JsonDomNodeList<>(Collections.emptyList()); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public T getJsonElement() { + final JsonDomNode parentNode = Objects.requireNonNull(getParentNode()); + return parentNode.getJsonElement(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getName() { + return getNodeName(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomAttribute getNextSibling() { + final JsonDomNode parentNode = Objects.requireNonNull(getParentNode()); + final JsonDomNamedNodeMap> attributes = Objects.requireNonNull(parentNode.getAttributes()); + final Iterator> iterator = attributes.values().iterator(); + + while (iterator.next() != this) { + // do nothing but iterating + } + return iterator.hasNext() ? iterator.next() : null; + } + + /** {@inheritDoc} */ + @Override + public short getNodeType() { + return Node.ATTRIBUTE_NODE; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getNodeValue() { + return getValue(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomDocument getOwnerDocument() { + final JsonDomNode parentNode = Objects.requireNonNull(getParentNode()); + return parentNode.getOwnerDocument(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomElement getOwnerElement() { + return (JsonDomElement) Objects.requireNonNull(getParentNode()); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomAttribute getPreviousSibling() { + JsonDomAttribute previousSibling = null; + JsonDomAttribute currentSibling = null; + + final JsonDomNode parentNode = Objects.requireNonNull(getParentNode()); + final JsonDomNamedNodeMap> attributes = Objects.requireNonNull(parentNode.getAttributes()); + final Iterator> iterator = attributes.values().iterator(); + + while (currentSibling != this) { + previousSibling = currentSibling; + currentSibling = iterator.next(); + } + return previousSibling; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public TypeInfo getSchemaTypeInfo() { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean getSpecified() { + return true; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getTextContent() { + return getValue(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getValue() { + return getValueProvider().get(); + } + + /** {@inheritDoc} */ + @Override + public boolean isId() { + return false; + } + + /** {@inheritDoc} */ + @Override + public void setValue(@Nullable @SuppressWarnings("unused") final String value) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomDocument.java b/src/main/lombok/de/larssh/json/dom/JsonDomDocument.java new file mode 100644 index 0000000..676caf0 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomDocument.java @@ -0,0 +1,329 @@ +package de.larssh.json.dom; + +import static de.larssh.utils.Finals.constant; + +import java.util.Arrays; + +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.DocumentType; +import org.w3c.dom.EntityReference; +import org.w3c.dom.Node; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +/** + * JSON DOM implementation of {@link Document}. + * + * @param implementation specific JSON element type + */ +@Getter +@ToString +@EqualsAndHashCode(callSuper = true, onParam_ = { @Nullable }) +public class JsonDomDocument extends JsonDomNode implements Document { + /** + * Node name of the document element + */ + public static final String DOCUMENT_ELEMENT_NODE_NAME = constant("root"); + + /** + * Document element with node name {@code root} + * + * @return document element + */ + JsonDomElement documentElement; + + /** + * Constructor of {@link JsonDomDocument}. + * + * @param jsonDomValue wrapped JSON element + */ + public JsonDomDocument(final JsonDomValue jsonDomValue) { + super(null, "#document"); + + documentElement = new JsonDomElement<>(this, DOCUMENT_ELEMENT_NODE_NAME, jsonDomValue); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public Node adoptNode(@Nullable @SuppressWarnings("unused") final Node source) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomAttribute createAttribute(@Nullable @SuppressWarnings("unused") final String name) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomAttribute createAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String qualifiedName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public CDATASection createCDATASection(@Nullable @SuppressWarnings("unused") final String data) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public Comment createComment(@Nullable @SuppressWarnings("unused") final String data) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public DocumentFragment createDocumentFragment() { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomElement createElement(@Nullable @SuppressWarnings("unused") final String tagName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomElement createElementNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String qualifiedName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public EntityReference createEntityReference(@Nullable @SuppressWarnings("unused") final String name) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public ProcessingInstruction createProcessingInstruction(@Nullable @SuppressWarnings("unused") final String target, + @Nullable @SuppressWarnings("unused") final String data) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public Text createTextNode(@Nullable @SuppressWarnings("unused") final String data) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomNamedNodeMap> getAttributes() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getChildNodes() { + return new JsonDomNodeList<>(Arrays.asList(getDocumentElement())); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public DocumentType getDoctype() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getDocumentURI() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public DOMConfiguration getDomConfig() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomElement getElementById(@Nullable @SuppressWarnings("unused") final String elementId) { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getElementsByTagName(@Nullable final String tagName) { + return getDocumentElement().getElementsByTagName(tagName); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getElementsByTagNameNS( + @Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public DOMImplementation getImplementation() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getInputEncoding() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public T getJsonElement() { + return getDocumentElement().getJsonElement(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomNode getNextSibling() { + return null; + } + + /** {@inheritDoc} */ + @Override + public short getNodeType() { + return Node.DOCUMENT_NODE; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getNodeValue() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomDocument getOwnerDocument() { + return this; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomNode getPreviousSibling() { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean getStrictErrorChecking() { + return true; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getTextContent() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getXmlEncoding() { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean getXmlStandalone() { + return false; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getXmlVersion() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode importNode(@Nullable @SuppressWarnings("unused") final Node importedNode, + @SuppressWarnings("unused") final boolean deep) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void normalizeDocument() { + // do nothing + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode renameNode(@Nullable @SuppressWarnings("unused") final Node node, + @Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String qualifiedName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setDocumentURI(@Nullable @SuppressWarnings("unused") final String documentURI) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setStrictErrorChecking(@SuppressWarnings("unused") final boolean strictErrorChecking) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setXmlStandalone(@SuppressWarnings("unused") final boolean xmlStandalone) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setXmlVersion(@Nullable @SuppressWarnings("unused") final String xmlVersion) { + throw new JsonDomNotSupportedException(); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomElement.java b/src/main/lombok/de/larssh/json/dom/JsonDomElement.java new file mode 100644 index 0000000..7316603 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomElement.java @@ -0,0 +1,306 @@ +package de.larssh.json.dom; + +import static de.larssh.utils.Collectors.toLinkedHashMap; +import static de.larssh.utils.Finals.constant; +import static java.util.stream.Collectors.toList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.xml.soap.Node; + +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.TypeInfo; + +import de.larssh.utils.Finals; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +/** + * JSON DOM implementation of {@link Element}. + * + * @param implementation specific JSON element type + */ +@Getter +@EqualsAndHashCode(callSuper = true, onParam_ = { @Nullable }) +public class JsonDomElement extends JsonDomNode implements Element { + /** + * Prefix for array items node names + */ + public static final String ARRAY_ITEM_NODE_NAME_PREFIX = constant("item"); + + /** + * Wrapped JSON element + * + * @return wrapped JSON element + */ + JsonDomValue jsonDomValue; + + /** + * List of child element nodes + * + * @return list of child element nodes + */ + Supplier>> childNodes + = Finals.lazy(() -> new JsonDomNodeList<>(getJsonDomValue().getChildren() + .entrySet() + .stream() + .map(entry -> new JsonDomElement<>(this, entry.getKey(), entry.getValue())) + .collect(toList()))); + + /** + * Map of attribute names to attribute node + * + * @return map of attribute names to attribute node + */ + Supplier>> attributes = Finals.lazy(() -> new JsonDomNamedNodeMap<>(Arrays + .asList(new JsonDomAttribute<>(this, + getJsonDomValue().getType().getValue(), + getJsonDomValue()::getTextValue)) + .stream() + .collect(toLinkedHashMap(JsonDomAttribute::getNodeName, Function.identity())))); + + /** + * Constructor of {@link JsonDomElement}. + * + * @param parentNode parent node + * @param nodeName node name + * @param jsonDomValue wrapped JSON element + */ + public JsonDomElement(final JsonDomNode parentNode, final String nodeName, final JsonDomValue jsonDomValue) { + super(parentNode, nodeName); + + this.jsonDomValue = jsonDomValue; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getAttribute(@Nullable final String name) { + final JsonDomAttribute attribute = getAttributeNode(name); + return attribute == null ? "" : attribute.getValue(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomAttribute getAttributeNode(@Nullable final String name) { + return Objects.requireNonNull(getAttributes()).get(name); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomAttribute getAttributeNodeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public String getAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNamedNodeMap> getAttributes() { + return attributes.get(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getChildNodes() { + return childNodes.get(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getElementsByTagName(@Nullable final String name) { + if (name == null) { + return new JsonDomNodeList<>(Collections.emptyList()); + } + + final List> list = new ArrayList<>(); + for (final JsonDomElement child : getChildNodes()) { + if (name.equals(child.getTagName()) || name.equals("*")) { + list.add(child); + } + list.addAll(child.getElementsByTagName(name)); + } + return new JsonDomNodeList<>(list); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNodeList> getElementsByTagNameNS( + @Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public T getJsonElement() { + return getJsonDomValue().getJsonElement(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomElement getNextSibling() { + final JsonDomNode parentNode = Objects.requireNonNull(getParentNode()); + final JsonDomNodeList> children = parentNode.getChildNodes(); + final int index = children.indexOf(this); + return index + 1 < children.size() ? children.get(index + 1) : null; + } + + /** {@inheritDoc} */ + @Override + public short getNodeType() { + return Node.ELEMENT_NODE; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getNodeValue() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomDocument getOwnerDocument() { + return Objects.requireNonNull(getParentNode()).getOwnerDocument(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomElement getPreviousSibling() { + final JsonDomNode parentNode = Objects.requireNonNull(getParentNode()); + final JsonDomNodeList> children = parentNode.getChildNodes(); + final int index = children.indexOf(this); + return index > 0 ? children.get(index - 1) : null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public TypeInfo getSchemaTypeInfo() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getTagName() { + return getNodeName(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String getTextContent() { + return ""; + } + + /** {@inheritDoc} */ + @Override + public boolean hasAttribute(@Nullable final String name) { + return Objects.requireNonNull(getAttributes()).containsKey(name); + } + + /** {@inheritDoc} */ + @Override + public boolean hasAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void removeAttribute(@Nullable @SuppressWarnings("unused") final String name) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomAttribute removeAttributeNode(@Nullable @SuppressWarnings("unused") final Attr oldAttr) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void removeAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setAttribute(@Nullable @SuppressWarnings("unused") final String name, + @Nullable @SuppressWarnings("unused") final String value) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomAttribute setAttributeNode(@Nullable @SuppressWarnings("unused") final Attr newAttr) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomAttribute setAttributeNodeNS(@Nullable @SuppressWarnings("unused") final Attr newAttr) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String qualifiedName, + @Nullable @SuppressWarnings("unused") final String value) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setIdAttribute(@Nullable @SuppressWarnings("unused") final String name, + @SuppressWarnings("unused") final boolean isId) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setIdAttributeNode(@Nullable @SuppressWarnings("unused") final Attr idAttr, + @SuppressWarnings("unused") final boolean isId) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setIdAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName, + @SuppressWarnings("unused") final boolean isId) { + throw new JsonDomNotSupportedException(); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomNamedNodeMap.java b/src/main/lombok/de/larssh/json/dom/JsonDomNamedNodeMap.java new file mode 100644 index 0000000..21abb1a --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomNamedNodeMap.java @@ -0,0 +1,97 @@ +package de.larssh.json.dom; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +import org.w3c.dom.DOMException; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import de.larssh.utils.collection.ProxiedMap; +import de.larssh.utils.text.Strings; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; + +/** + * {@link Map} based {@link NamedNodeMap} implementation + * + * @param node element type + */ +public class JsonDomNamedNodeMap extends ProxiedMap implements NamedNodeMap { + /** + * Constructor to create a {@link JsonDomNamedNodeMap} based on {@code map}. + * + * @param map map of keys to nodes + */ + public JsonDomNamedNodeMap(final Map map) { + super(Collections.unmodifiableMap(map)); + } + + /** {@inheritDoc} */ + @Override + public int getLength() { + return size(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public E getNamedItem(@Nullable final String name) { + return get(name); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public E getNamedItemNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public E item(final int index) { + if (index < 0) { + throw new DOMException(DOMException.INDEX_SIZE_ERR, Strings.format("Invalid index %d.", index)); + } + if (index >= size()) { + return null; + } + final Iterator iterator = values().iterator(); + for (int i = 0; i < index; i += 1) { + iterator.next(); + } + return iterator.next(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public E removeNamedItem(@Nullable final String name) { + return remove(name); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public E removeNamedItemNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, + @Nullable @SuppressWarnings("unused") final String localName) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public E setNamedItem(@Nullable @SuppressWarnings("unused") final Node node) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public E setNamedItemNS(@Nullable @SuppressWarnings("unused") final Node node) { + throw new JsonDomNotSupportedException(); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomNode.java b/src/main/lombok/de/larssh/json/dom/JsonDomNode.java new file mode 100644 index 0000000..187cd13 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomNode.java @@ -0,0 +1,265 @@ +package de.larssh.json.dom; + +import java.util.Optional; + +import org.w3c.dom.Node; +import org.w3c.dom.UserDataHandler; + +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * JSON DOM implementation of {@link Node}. + * + * @param implementation specific JSON element type + */ +@Getter +@RequiredArgsConstructor +@EqualsAndHashCode(onParam_ = { @Nullable }) +public abstract class JsonDomNode implements Node { + + /** + * Parent node + * + * @return parent node + */ + @Nullable + JsonDomNode parentNode; + + /** + * Node node + * + * @return node node + */ + String nodeName; + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode appendChild(@Nullable @SuppressWarnings("unused") final Node newChild) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode cloneNode(@SuppressWarnings("unused") final boolean deep) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public short compareDocumentPosition(@Nullable @SuppressWarnings("unused") final Node other) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public abstract JsonDomNamedNodeMap> getAttributes(); + + /** {@inheritDoc} */ + @Nullable + @Override + public String getBaseURI() { + return null; + } + + /** {@inheritDoc} */ + @NonNull + @Override + public abstract JsonDomNodeList> getChildNodes(); + + /** {@inheritDoc} */ + @Nullable + @Override + public Object getFeature(@Nullable @SuppressWarnings("unused") final String feature, + @Nullable @SuppressWarnings("unused") final String version) { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomElement getFirstChild() { + final JsonDomNodeList> childNodes = getChildNodes(); + return childNodes.isEmpty() ? null : childNodes.get(0); + } + + /** + * Returns the implementation specific JSON element. + * + * @return implementation specific JSON element + */ + @Nullable + public abstract T getJsonElement(); + + /** {@inheritDoc} */ + @Nullable + @Override + public JsonDomElement getLastChild() { + final JsonDomNodeList> childNodes = getChildNodes(); + return childNodes.isEmpty() ? null : childNodes.get(childNodes.size() - 1); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getLocalName() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String getNamespaceURI() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public abstract JsonDomNode getNextSibling(); + + /** {@inheritDoc} */ + @NonNull + @Override + public abstract JsonDomDocument getOwnerDocument(); + + /** {@inheritDoc} */ + @Nullable + @Override + public String getPrefix() { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public abstract JsonDomNode getPreviousSibling(); + + /** {@inheritDoc} */ + @Nullable + @Override + public Object getUserData(@Nullable @SuppressWarnings("unused") final String key) { + return null; + } + + /** {@inheritDoc} */ + @Override + public boolean hasAttributes() { + final JsonDomNamedNodeMap> attributes = getAttributes(); + return attributes != null && !attributes.isEmpty(); + } + + /** {@inheritDoc} */ + @Override + public boolean hasChildNodes() { + return !getChildNodes().isEmpty(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode insertBefore(@Nullable @SuppressWarnings("unused") final Node newChild, + @Nullable @SuppressWarnings("unused") final Node refChild) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public boolean isDefaultNamespace(@Nullable @SuppressWarnings("unused") final String namespaceURI) { + return false; + } + + /** {@inheritDoc} */ + @Override + public boolean isEqualNode(@Nullable final Node node) { + return equals(node); + } + + /** {@inheritDoc} */ + @Override + public boolean isSameNode(@Nullable final Node node) { + return equals(node); + } + + /** {@inheritDoc} */ + @Override + public boolean isSupported(@Nullable @SuppressWarnings("unused") final String feature, + @Nullable @SuppressWarnings("unused") final String version) { + return false; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String lookupNamespaceURI(@Nullable @SuppressWarnings("unused") final String prefix) { + return null; + } + + /** {@inheritDoc} */ + @Nullable + @Override + public String lookupPrefix(@Nullable @SuppressWarnings("unused") final String namespaceURI) { + return null; + } + + /** {@inheritDoc} */ + @Override + public void normalize() { + // do nothing + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode removeChild(@Nullable @SuppressWarnings("unused") final Node oldChild) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public JsonDomNode replaceChild(@Nullable @SuppressWarnings("unused") final Node newChild, + @Nullable @SuppressWarnings("unused") final Node oldChild) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setNodeValue(@Nullable @SuppressWarnings("unused") final String nodeValue) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setPrefix(@Nullable @SuppressWarnings("unused") final String prefix) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Override + public void setTextContent(@Nullable @SuppressWarnings("unused") final String textContent) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @Nullable + @Override + public Object setUserData(@Nullable @SuppressWarnings("unused") final String key, + @Nullable @SuppressWarnings("unused") final Object data, + @Nullable @SuppressWarnings("unused") final UserDataHandler handler) { + throw new JsonDomNotSupportedException(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public String toString() { + return Optional.ofNullable(getJsonElement()).map(Object::toString).orElse("undefined"); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomNodeList.java b/src/main/lombok/de/larssh/json/dom/JsonDomNodeList.java new file mode 100644 index 0000000..0310859 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomNodeList.java @@ -0,0 +1,41 @@ +package de.larssh.json.dom; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import de.larssh.utils.collection.ProxiedList; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * {@link List} based {@link NodeList} implementation + * + * @param node element type + */ +public class JsonDomNodeList extends ProxiedList implements NodeList { + + /** + * Constructor to create a {@link JsonDomNodeList} based on {@code list}. + * + * @param list list of nodes + */ + public JsonDomNodeList(final List list) { + super(Collections.unmodifiableList(list)); + } + + /** {@inheritDoc} */ + @Override + public int getLength() { + return size(); + } + + /** {@inheritDoc} */ + @NonNull + @Override + public E item(final int index) { + return Objects.requireNonNull(get(index)); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomNotSupportedException.java b/src/main/lombok/de/larssh/json/dom/JsonDomNotSupportedException.java new file mode 100644 index 0000000..d88dfc2 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomNotSupportedException.java @@ -0,0 +1,22 @@ +package de.larssh.json.dom; + +import org.w3c.dom.DOMException; + +import lombok.ToString; + +/** + * Thrown to indicate that JSON DOM does not support a particular DOM feature. + */ +@ToString +public class JsonDomNotSupportedException extends DOMException { + // @EqualsAndHashCode(callSuper = true, onParam_ = { @Nullable }) + private static final long serialVersionUID = 6374630768171198592L; + + /** + * Constructs a new {@link JsonDomNotSupportedException} with the default detail + * message. + */ + public JsonDomNotSupportedException() { + super(DOMException.NOT_SUPPORTED_ERR, "Not supported."); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomType.java b/src/main/lombok/de/larssh/json/dom/JsonDomType.java new file mode 100644 index 0000000..505f9af --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomType.java @@ -0,0 +1,48 @@ +package de.larssh.json.dom; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * JSON data types + */ +@Getter +@RequiredArgsConstructor +public enum JsonDomType { + /** + * JSON array + */ + ARRAY("array"), + + /** + * JSON boolean + */ + BOOLEAN("boolean"), + + /** + * JSON null + */ + NULL("null"), + + /** + * JSON number + */ + NUMBER("number"), + + /** + * JSON object + */ + OBJECT("object"), + + /** + * JSON string + */ + STRING("string"); + + /** + * String representation of the JSON type + * + * @return string representation + */ + String value; +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomValue.java b/src/main/lombok/de/larssh/json/dom/JsonDomValue.java new file mode 100644 index 0000000..e37cb06 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomValue.java @@ -0,0 +1,47 @@ +package de.larssh.json.dom; + +import java.util.Map; + +/** + * Generic wrapper to keep JSON implementation specifics off of JSON DOM. + * + * @param implementation specific JSON element type + */ +public interface JsonDomValue { + /** + * Returns a map of child node names and their values. + * + *

+ * For JSON objects and arrays a map of keys to wrapped values should be + * returned. Keys of JSON arrays follow the scheme {@code "item" + index}. For + * other JSON elements an empty map should be returned. + * + * @return map of child node names and their value. + */ + Map> getChildren(); + + /** + * Returns the implementation specific JSON element. + * + * @return implementation specific JSON element + */ + T getJsonElement(); + + /** + * Returns the text value. + * + *

+ * For JSON strings this is the string value without quotes. For other JSON + * elements it is their JSON representation. + * + * @return text value + */ + String getTextValue(); + + /** + * Returns the JSON elements value type. + * + * @return JSON data type + */ + JsonDomType getType(); +} diff --git a/src/main/lombok/de/larssh/json/dom/JsonDomXPathExpressions.java b/src/main/lombok/de/larssh/json/dom/JsonDomXPathExpressions.java new file mode 100644 index 0000000..6c13e42 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/JsonDomXPathExpressions.java @@ -0,0 +1,61 @@ +package de.larssh.json.dom; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Optional; + +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; + +import de.larssh.utils.dom.XPathExpressions; +import lombok.experimental.UtilityClass; + +/** + * This class contains JSON DOM related helper methods for + * {@link XPathExpression}. + */ +@UtilityClass +public final class JsonDomXPathExpressions { + /** + * Evaluates the compiled XPath expression in the specified context and + * optionally returns a JSON element. + * + *

+ * See package {@link de.larssh.utils.dom.json} for a description of the JSON + * DOM hierarchy. + * + * @param implementation specific JSON element type + * @param jsonDomValue the starting context + * @param expression the XPath expression + * @return evaluation result as optional {@code T} + * @throws XPathExpressionException If the expression cannot be evaluated. + */ + public static Optional getJsonElement(final JsonDomValue jsonDomValue, final XPathExpression expression) + throws XPathExpressionException { + return XPathExpressions.>getNode(new JsonDomDocument<>(jsonDomValue), expression) + .map(JsonDomNode::getJsonElement); + } + + /** + * Evaluates the compiled XPath expression in the specified context and returns + * a list of JSON elements. + * + *

+ * See package {@link de.larssh.utils.dom.json} for a description of the JSON + * DOM hierarchy. + * + * @param implementation specific JSON element type + * @param jsonDomValue the starting context + * @param expression the XPath expression + * @return evaluation result as list of {@code T} + * @throws XPathExpressionException If the expression cannot be evaluated. + */ + public static List getJsonElementList(final JsonDomValue jsonDomValue, final XPathExpression expression) + throws XPathExpressionException { + return XPathExpressions.>getNodes(new JsonDomDocument<>(jsonDomValue), expression) + .stream() + .map(JsonDomNode::getJsonElement) + .collect(toList()); + } +} diff --git a/src/main/lombok/de/larssh/json/dom/package-info.java b/src/main/lombok/de/larssh/json/dom/package-info.java new file mode 100644 index 0000000..ea4f237 --- /dev/null +++ b/src/main/lombok/de/larssh/json/dom/package-info.java @@ -0,0 +1,64 @@ +/** + * DOM implementation for JSON + * + *

+ * While DOM is widely used for XML structured data, it can be useful for JSON + * data, too. These classes wrap generic JSON elements to fit the DOM + * interfaces. + * + *

+ * JSON DOM does not come with its own JSON parser or JSON element objects. + * Instead {@link JsonDomValue} is made up to integrate existing parsers. The + * JSON DOM is not modifiable! + * + *

+ * Model: Each JSON element is represented by a DOM element. Those DOM + * elements contain a DOM attribute describing its data type and value. + * + *

+ * The DOM elements node name depend on the elements parent. + *

    + *
  • The document elements node name is {@code root}. + *
  • For JSON objects the node name matches the corresponding object key. + *
  • For JSON arrays the node name equals {@code "item" + index}. + *
+ * + *

+ * Example: While JSON DOM is not equivalent to XML, but a DOM + * implementation, developers might be familiar with the XML DOM. Therefore the + * following snippet demonstrates a simple JSON document represented as XML as + * it is implemented in JSON DOM. + * + * + * + * + * + * + * + * + * + *
JSONXML example
{
+ * 	"name": "Monitor",
+ * 	"size": 24,
+ * 	"speaker": false,
+ * 	"volume": null,
+ * 	"colors": ["red", "green", "blue"]
+ * }
<root object="...">
+ * 	<name string="Monitor" />
+ * 	<size number="24" />
+ * 	<speaker boolean="false" />
+ * 	<volume null="null" />
+ * 	<colors array="...">
+ * 		<item0 string="red" />
+ * 		<item1 string="green" />
+ * 		<item2 string="blue" />
+ * 	</colors>
+ * </root>
+ * + *

+ * Note: In this example the values of {@code array} and {@code object} + * attributes have been replaced with {@code ...} to simplify the output. The + * value of such attributes is their JSON representation. + */ +@de.larssh.utils.annotations.NonNullByDefault +package de.larssh.json.dom;