JsonDomElement.java
// Generated by delombok at Fri Nov 29 09:48:08 UTC 2024
package de.larssh.json.dom;
import static de.larssh.utils.Finals.constant;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.toList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.TypeInfo;
import de.larssh.json.dom.values.JsonDomValue;
import de.larssh.utils.Finals;
import de.larssh.utils.Nullables;
import de.larssh.utils.text.Patterns;
import de.larssh.utils.text.Strings;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
/**
* JSON DOM implementation of {@link Element}.
*
* @param <T> implementation specific JSON element type
*/
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.GodClass"})
@SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "fields attributes and childNodes are not used from within the constuctor")
public class JsonDomElement<T> extends JsonDomNode<T> implements Element {
/**
* Name of the attribute {@code name}
*/
public static final String ATTRIBUTE_NAME = constant("name");
/**
* Name of the attribute {@code type}
*/
public static final String ATTRIBUTE_TYPE = constant("type");
/**
* The special value "*" matches all tags.
*/
private static final String GET_ELEMENTS_BY_TAG_NAME_WILDCARD = "*";
/**
* Regex character class expression describing the first character of an XML
* name following
* <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">the
* XML 1.1 standard</a>.
*/
@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
private static final String XML_NAME_START_CHARACTERS = ":A-Z_a-zÀ-ÖØ-öø-˿Ͱ-ͽͿ--⁰-Ⰰ-、-豈-﷏ﷰ-�";
/**
* Regex character class expression describing the second and following
* characters of an XML name following
* <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">the
* XML 1.1 standard</a>.
*/
@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
private static final String XML_NAME_CHARACTERS = XML_NAME_START_CHARACTERS + "-.0-9·̀-ͯ‿-⁀";
/**
* Pattern finding one or more illegal XML characters
*/
@SuppressWarnings("checkstyle:MultipleStringLiterals")
private static final Pattern PATTERN_ILLEGAL_XML_CHARACTERS = Pattern.compile("[^" + XML_NAME_CHARACTERS + "]+");
/**
* Pattern finding one or more illegal XML characters at the start
*/
@SuppressWarnings("checkstyle:MultipleStringLiterals")
private static final Pattern PATTERN_ILLEGAL_XML_CHARACTERS_AT_START = Pattern.compile("^[^" + XML_NAME_START_CHARACTERS + "]+");
/**
* Pattern matching any number.
*/
private static final Pattern PATTERN_IS_NUMBER = Pattern.compile("^(0|[1-9]\\d*)$");
/**
* Returns an XML compatible tag name based on the original JSON key.
*
* <p>
* The node names inside a JSON DOM are compatible with the XML standard.
* Therefore, keys that are invalid XML tag names are replaced inside JSON DOM.
* The attribute {@code name} still contains the original JSON object key.
*
* @param parentNode parent node
* @param jsonKey the elements JSON key
* @return the XML compatible tag name
*/
private static String createTagName(final JsonDomNode<?> parentNode, final String jsonKey) {
// Array Element
if (parentNode instanceof JsonDomElement && ((JsonDomElement<?>) parentNode).getJsonDomValue().getType() == JsonDomType.ARRAY) {
return "element";
}
// Empty
if (jsonKey.isEmpty()) {
return "empty";
}
// Spaces
final String trimmedName = jsonKey.trim();
if (trimmedName.isEmpty()) {
return jsonKey.length() == 1 ? "whitespace" : "whitespaces";
}
// Numeric
if (Patterns.matches(PATTERN_IS_NUMBER, trimmedName).isPresent()) {
return 'n' + trimmedName;
}
// Simplified
final String simplifiedName = Strings.replaceFirst(trimmedName, PATTERN_ILLEGAL_XML_CHARACTERS_AT_START, "");
if (!simplifiedName.isEmpty()) {
return Strings.replaceAll(simplifiedName, PATTERN_ILLEGAL_XML_CHARACTERS, "-");
}
// Non-Empty
return "non-empty";
}
/**
* Map of attribute names to attribute node
*
* @return map of attribute names to attribute node
*/
@SuppressWarnings("PMD.LooseCoupling")
private final Supplier<JsonDomNamedNodeMap<JsonDomAttribute<T>>> attributes = Finals.lazy(() -> new JsonDomNamedNodeMap<>(asList(new JsonDomAttribute<>(this, ATTRIBUTE_NAME, this::getJsonKey), new JsonDomAttribute<>(this, ATTRIBUTE_TYPE, () -> getJsonDomValue().getType().getValue()))));
/**
* List of child element nodes
*/
@SuppressWarnings("PMD.LooseCoupling")
private final Supplier<JsonDomNodeList<JsonDomNode<T>>> childNodes = Finals.lazy(() -> new JsonDomNodeList<>(getJsonDomValue().getType().isComplex() ? getJsonDomValue().getChildren().entrySet().stream().map(entry -> new JsonDomElement<>(this, entry.getKey(), entry.getValue())).collect(toList()) : singletonList(new JsonDomText<>(this, getJsonDomValue().getTextValue()))));
/**
* Wrapped JSON element
*/
private final JsonDomValue<T> jsonDomValue;
/**
* The elements JSON key
*/
private final String jsonKey;
/**
* Constructor of {@link JsonDomElement}.
*
* @param parentNode parent node
* @param jsonKey the elements JSON key
* @param jsonDomValue wrapped JSON element
*/
public JsonDomElement(final JsonDomNode<T> parentNode, final String jsonKey, final JsonDomValue<T> jsonDomValue) {
super(parentNode, createTagName(parentNode, jsonKey));
this.jsonDomValue = jsonDomValue;
this.jsonKey = jsonKey;
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public String getAttribute(@Nullable final String name) {
final JsonDomAttribute<T> attribute = getAttributeNode(name);
return attribute == null ? "" : attribute.getValue();
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public JsonDomAttribute<T> getAttributeNode(@Nullable final String name) {
return Nullables.orElseThrow(getAttributes()).get(name);
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public JsonDomAttribute<T> 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<JsonDomAttribute<T>> getAttributes() {
return attributes.get();
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public JsonDomNodeList<JsonDomNode<T>> getChildNodes() {
return childNodes.get();
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public JsonDomNodeList<JsonDomElement<T>> getElementsByTagName(@Nullable final String name) {
if (name == null) {
return new JsonDomNodeList<>(emptyList());
}
final List<JsonDomElement<T>> list = new ArrayList<>();
for (final JsonDomNode<T> child : getChildNodes()) {
if (child instanceof JsonDomElement) {
final JsonDomElement<T> childElement = (JsonDomElement<T>) child;
if (name.equals(childElement.getTagName()) || GET_ELEMENTS_BY_TAG_NAME_WILDCARD.equals(name)) {
list.add(childElement);
}
list.addAll(childElement.getElementsByTagName(name));
}
}
return new JsonDomNodeList<>(list);
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public JsonDomNodeList<JsonDomElement<T>> getElementsByTagNameNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName) {
throw new JsonDomNotSupportedException();
}
/**
* {@inheritDoc}
*/
@Override
public T getJsonElement() {
return getJsonDomValue().getJsonElement();
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.LooseCoupling"})
public final JsonDomNode<T> getNextSibling() {
final JsonDomNode<T> parentNode = Nullables.orElseThrow(getParentNode());
final JsonDomNodeList<JsonDomNode<T>> childNodes = Nullables.orElseThrow(parentNode.getChildNodes());
final Iterator<JsonDomNode<T>> iterator = childNodes.iterator();
while (iterator.hasNext()) {
if (iterator.next() == this) {
return iterator.hasNext() ? iterator.next() : null;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public short getNodeType() {
return ELEMENT_NODE;
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public String getNodeValue() {
return null;
}
/**
* {@inheritDoc}
*/
@NonNull
@Override
public JsonDomDocument<T> getOwnerDocument() {
return Nullables.orElseThrow(getParentNode()).getOwnerDocument();
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.LooseCoupling"})
public final JsonDomNode<T> getPreviousSibling() {
JsonDomNode<T> previousSibling = null;
JsonDomNode<T> currentSibling = null;
final JsonDomNode<T> parentNode = Nullables.orElseThrow(getParentNode());
final JsonDomNodeList<JsonDomNode<T>> childNodes = Nullables.orElseThrow(parentNode.getChildNodes());
final Iterator<JsonDomNode<T>> iterator = childNodes.iterator();
while (currentSibling != this) {
previousSibling = currentSibling;
currentSibling = iterator.next();
}
return previousSibling;
}
/**
* {@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 Nullables.orElseThrow(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<T> 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<T> setAttributeNode(@Nullable @SuppressWarnings("unused") final Attr newAttr) {
throw new JsonDomNotSupportedException();
}
/**
* {@inheritDoc}
*/
@Nullable
@Override
public JsonDomAttribute<T> 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();
}
/**
* Wrapped JSON element
*
* @return wrapped JSON element
*/
@java.lang.SuppressWarnings("all")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
@lombok.Generated
public JsonDomValue<T> getJsonDomValue() {
return this.jsonDomValue;
}
/**
* The elements JSON key
*
* @return the elements JSON key
*/
@java.lang.SuppressWarnings("all")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
@lombok.Generated
public String getJsonKey() {
return this.jsonKey;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
@lombok.Generated
public boolean equals(@edu.umd.cs.findbugs.annotations.Nullable final java.lang.Object o) {
if (o == this) return true;
if (!(o instanceof JsonDomElement)) return false;
final JsonDomElement<?> other = (JsonDomElement<?>) o;
if (!other.canEqual((java.lang.Object) this)) return false;
if (!super.equals(o)) return false;
final java.lang.Object this$attributes = this.getAttributes();
final java.lang.Object other$attributes = other.getAttributes();
if (this$attributes == null ? other$attributes != null : !this$attributes.equals(other$attributes)) return false;
final java.lang.Object this$childNodes = this.getChildNodes();
final java.lang.Object other$childNodes = other.getChildNodes();
if (this$childNodes == null ? other$childNodes != null : !this$childNodes.equals(other$childNodes)) return false;
final java.lang.Object this$jsonDomValue = this.getJsonDomValue();
final java.lang.Object other$jsonDomValue = other.getJsonDomValue();
if (this$jsonDomValue == null ? other$jsonDomValue != null : !this$jsonDomValue.equals(other$jsonDomValue)) return false;
final java.lang.Object this$jsonKey = this.getJsonKey();
final java.lang.Object other$jsonKey = other.getJsonKey();
if (this$jsonKey == null ? other$jsonKey != null : !this$jsonKey.equals(other$jsonKey)) return false;
return true;
}
@java.lang.SuppressWarnings("all")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
@lombok.Generated
protected boolean canEqual(@edu.umd.cs.findbugs.annotations.Nullable final java.lang.Object other) {
return other instanceof JsonDomElement;
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
@lombok.Generated
public int hashCode() {
final int PRIME = 59;
int result = super.hashCode();
final java.lang.Object $attributes = this.getAttributes();
result = result * PRIME + ($attributes == null ? 43 : $attributes.hashCode());
final java.lang.Object $childNodes = this.getChildNodes();
result = result * PRIME + ($childNodes == null ? 43 : $childNodes.hashCode());
final java.lang.Object $jsonDomValue = this.getJsonDomValue();
result = result * PRIME + ($jsonDomValue == null ? 43 : $jsonDomValue.hashCode());
final java.lang.Object $jsonKey = this.getJsonKey();
result = result * PRIME + ($jsonKey == null ? 43 : $jsonKey.hashCode());
return result;
}
}