View Javadoc
1   // Generated by delombok at Fri Nov 29 09:48:08 UTC 2024
2   package de.larssh.json.dom;
3   
4   import static de.larssh.utils.Finals.constant;
5   import static java.util.Arrays.asList;
6   import static java.util.Collections.emptyList;
7   import static java.util.Collections.singletonList;
8   import static java.util.stream.Collectors.toList;
9   import java.util.ArrayList;
10  import java.util.Iterator;
11  import java.util.List;
12  import java.util.function.Supplier;
13  import java.util.regex.Pattern;
14  import org.w3c.dom.Attr;
15  import org.w3c.dom.Element;
16  import org.w3c.dom.TypeInfo;
17  import de.larssh.json.dom.values.JsonDomValue;
18  import de.larssh.utils.Finals;
19  import de.larssh.utils.Nullables;
20  import de.larssh.utils.text.Patterns;
21  import de.larssh.utils.text.Strings;
22  import edu.umd.cs.findbugs.annotations.NonNull;
23  import edu.umd.cs.findbugs.annotations.Nullable;
24  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
25  
26  /**
27   * JSON DOM implementation of {@link Element}.
28   *
29   * @param <T> implementation specific JSON element type
30   */
31  @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.GodClass"})
32  @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_CONSTRUCTOR", justification = "fields attributes and childNodes are not used from within the constuctor")
33  public class JsonDomElement<T> extends JsonDomNode<T> implements Element {
34  	/**
35  	 * Name of the attribute {@code name}
36  	 */
37  	public static final String ATTRIBUTE_NAME = constant("name");
38  	/**
39  	 * Name of the attribute {@code type}
40  	 */
41  	public static final String ATTRIBUTE_TYPE = constant("type");
42  	/**
43  	 * The special value "*" matches all tags.
44  	 */
45  	private static final String GET_ELEMENTS_BY_TAG_NAME_WILDCARD = "*";
46  	/**
47  	 * Regex character class expression describing the first character of an XML
48  	 * name following
49  	 * <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">the
50  	 * XML 1.1 standard</a>.
51  	 */
52  	@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
53  	private static final String XML_NAME_START_CHARACTERS = ":A-Z_a-zÀ-ÖØ-öø-˿Ͱ-ͽͿ-῿‌-‍⁰-↏Ⰰ-⿯、-퟿豈-﷏ﷰ-�";
54  	/**
55  	 * Regex character class expression describing the second and following
56  	 * characters of an XML name following
57  	 * <a href="https://www.w3.org/TR/2006/REC-xml11-20060816/#sec-common-syn">the
58  	 * XML 1.1 standard</a>.
59  	 */
60  	@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
61  	private static final String XML_NAME_CHARACTERS = XML_NAME_START_CHARACTERS + "-.0-9·̀-ͯ‿-⁀";
62  	/**
63  	 * Pattern finding one or more illegal XML characters
64  	 */
65  	@SuppressWarnings("checkstyle:MultipleStringLiterals")
66  	private static final Pattern PATTERN_ILLEGAL_XML_CHARACTERS = Pattern.compile("[^" + XML_NAME_CHARACTERS + "]+");
67  	/**
68  	 * Pattern finding one or more illegal XML characters at the start
69  	 */
70  	@SuppressWarnings("checkstyle:MultipleStringLiterals")
71  	private static final Pattern PATTERN_ILLEGAL_XML_CHARACTERS_AT_START = Pattern.compile("^[^" + XML_NAME_START_CHARACTERS + "]+");
72  	/**
73  	 * Pattern matching any number.
74  	 */
75  	private static final Pattern PATTERN_IS_NUMBER = Pattern.compile("^(0|[1-9]\\d*)$");
76  
77  	/**
78  	 * Returns an XML compatible tag name based on the original JSON key.
79  	 *
80  	 * <p>
81  	 * The node names inside a JSON DOM are compatible with the XML standard.
82  	 * Therefore, keys that are invalid XML tag names are replaced inside JSON DOM.
83  	 * The attribute {@code name} still contains the original JSON object key.
84  	 *
85  	 * @param parentNode parent node
86  	 * @param jsonKey    the elements JSON key
87  	 * @return the XML compatible tag name
88  	 */
89  	private static String createTagName(final JsonDomNode<?> parentNode, final String jsonKey) {
90  		// Array Element
91  		if (parentNode instanceof JsonDomElement && ((JsonDomElement<?>) parentNode).getJsonDomValue().getType() == JsonDomType.ARRAY) {
92  			return "element";
93  		}
94  		// Empty
95  		if (jsonKey.isEmpty()) {
96  			return "empty";
97  		}
98  		// Spaces
99  		final String trimmedName = jsonKey.trim();
100 		if (trimmedName.isEmpty()) {
101 			return jsonKey.length() == 1 ? "whitespace" : "whitespaces";
102 		}
103 		// Numeric
104 		if (Patterns.matches(PATTERN_IS_NUMBER, trimmedName).isPresent()) {
105 			return 'n' + trimmedName;
106 		}
107 		// Simplified
108 		final String simplifiedName = Strings.replaceFirst(trimmedName, PATTERN_ILLEGAL_XML_CHARACTERS_AT_START, "");
109 		if (!simplifiedName.isEmpty()) {
110 			return Strings.replaceAll(simplifiedName, PATTERN_ILLEGAL_XML_CHARACTERS, "-");
111 		}
112 		// Non-Empty
113 		return "non-empty";
114 	}
115 
116 	/**
117 	 * Map of attribute names to attribute node
118 	 *
119 	 * @return map of attribute names to attribute node
120 	 */
121 	@SuppressWarnings("PMD.LooseCoupling")
122 	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()))));
123 	/**
124 	 * List of child element nodes
125 	 */
126 	@SuppressWarnings("PMD.LooseCoupling")
127 	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()))));
128 	/**
129 	 * Wrapped JSON element
130 	 */
131 	private final JsonDomValue<T> jsonDomValue;
132 	/**
133 	 * The elements JSON key
134 	 */
135 	private final String jsonKey;
136 
137 	/**
138 	 * Constructor of {@link JsonDomElement}.
139 	 *
140 	 * @param parentNode   parent node
141 	 * @param jsonKey      the elements JSON key
142 	 * @param jsonDomValue wrapped JSON element
143 	 */
144 	public JsonDomElement(final JsonDomNode<T> parentNode, final String jsonKey, final JsonDomValue<T> jsonDomValue) {
145 		super(parentNode, createTagName(parentNode, jsonKey));
146 		this.jsonDomValue = jsonDomValue;
147 		this.jsonKey = jsonKey;
148 	}
149 
150 	/**
151 	 * {@inheritDoc}
152 	 */
153 	@NonNull
154 	@Override
155 	public String getAttribute(@Nullable final String name) {
156 		final JsonDomAttribute<T> attribute = getAttributeNode(name);
157 		return attribute == null ? "" : attribute.getValue();
158 	}
159 
160 	/**
161 	 * {@inheritDoc}
162 	 */
163 	@Nullable
164 	@Override
165 	public JsonDomAttribute<T> getAttributeNode(@Nullable final String name) {
166 		return Nullables.orElseThrow(getAttributes()).get(name);
167 	}
168 
169 	/**
170 	 * {@inheritDoc}
171 	 */
172 	@Nullable
173 	@Override
174 	public JsonDomAttribute<T> getAttributeNodeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName) {
175 		throw new JsonDomNotSupportedException();
176 	}
177 
178 	/**
179 	 * {@inheritDoc}
180 	 */
181 	@Override
182 	public String getAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName) {
183 		throw new JsonDomNotSupportedException();
184 	}
185 
186 	/**
187 	 * {@inheritDoc}
188 	 */
189 	@NonNull
190 	@Override
191 	public JsonDomNamedNodeMap<JsonDomAttribute<T>> getAttributes() {
192 		return attributes.get();
193 	}
194 
195 	/**
196 	 * {@inheritDoc}
197 	 */
198 	@NonNull
199 	@Override
200 	public JsonDomNodeList<JsonDomNode<T>> getChildNodes() {
201 		return childNodes.get();
202 	}
203 
204 	/**
205 	 * {@inheritDoc}
206 	 */
207 	@NonNull
208 	@Override
209 	public JsonDomNodeList<JsonDomElement<T>> getElementsByTagName(@Nullable final String name) {
210 		if (name == null) {
211 			return new JsonDomNodeList<>(emptyList());
212 		}
213 		final List<JsonDomElement<T>> list = new ArrayList<>();
214 		for (final JsonDomNode<T> child : getChildNodes()) {
215 			if (child instanceof JsonDomElement) {
216 				final JsonDomElement<T> childElement = (JsonDomElement<T>) child;
217 				if (name.equals(childElement.getTagName()) || GET_ELEMENTS_BY_TAG_NAME_WILDCARD.equals(name)) {
218 					list.add(childElement);
219 				}
220 				list.addAll(childElement.getElementsByTagName(name));
221 			}
222 		}
223 		return new JsonDomNodeList<>(list);
224 	}
225 
226 	/**
227 	 * {@inheritDoc}
228 	 */
229 	@NonNull
230 	@Override
231 	public JsonDomNodeList<JsonDomElement<T>> getElementsByTagNameNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName) {
232 		throw new JsonDomNotSupportedException();
233 	}
234 
235 	/**
236 	 * {@inheritDoc}
237 	 */
238 	@Override
239 	public T getJsonElement() {
240 		return getJsonDomValue().getJsonElement();
241 	}
242 
243 	/**
244 	 * {@inheritDoc}
245 	 */
246 	@Nullable
247 	@Override
248 	@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.LooseCoupling"})
249 	public final JsonDomNode<T> getNextSibling() {
250 		final JsonDomNode<T> parentNode = Nullables.orElseThrow(getParentNode());
251 		final JsonDomNodeList<JsonDomNode<T>> childNodes = Nullables.orElseThrow(parentNode.getChildNodes());
252 		final Iterator<JsonDomNode<T>> iterator = childNodes.iterator();
253 		while (iterator.hasNext()) {
254 			if (iterator.next() == this) {
255 				return iterator.hasNext() ? iterator.next() : null;
256 			}
257 		}
258 		return null;
259 	}
260 
261 	/**
262 	 * {@inheritDoc}
263 	 */
264 	@Override
265 	public short getNodeType() {
266 		return ELEMENT_NODE;
267 	}
268 
269 	/**
270 	 * {@inheritDoc}
271 	 */
272 	@Nullable
273 	@Override
274 	public String getNodeValue() {
275 		return null;
276 	}
277 
278 	/**
279 	 * {@inheritDoc}
280 	 */
281 	@NonNull
282 	@Override
283 	public JsonDomDocument<T> getOwnerDocument() {
284 		return Nullables.orElseThrow(getParentNode()).getOwnerDocument();
285 	}
286 
287 	/**
288 	 * {@inheritDoc}
289 	 */
290 	@Nullable
291 	@Override
292 	@SuppressWarnings({"PMD.CompareObjectsWithEquals", "PMD.LooseCoupling"})
293 	public final JsonDomNode<T> getPreviousSibling() {
294 		JsonDomNode<T> previousSibling = null;
295 		JsonDomNode<T> currentSibling = null;
296 		final JsonDomNode<T> parentNode = Nullables.orElseThrow(getParentNode());
297 		final JsonDomNodeList<JsonDomNode<T>> childNodes = Nullables.orElseThrow(parentNode.getChildNodes());
298 		final Iterator<JsonDomNode<T>> iterator = childNodes.iterator();
299 		while (currentSibling != this) {
300 			previousSibling = currentSibling;
301 			currentSibling = iterator.next();
302 		}
303 		return previousSibling;
304 	}
305 
306 	/**
307 	 * {@inheritDoc}
308 	 */
309 	@Nullable
310 	@Override
311 	public TypeInfo getSchemaTypeInfo() {
312 		return null;
313 	}
314 
315 	/**
316 	 * {@inheritDoc}
317 	 */
318 	@NonNull
319 	@Override
320 	public String getTagName() {
321 		return getNodeName();
322 	}
323 
324 	/**
325 	 * {@inheritDoc}
326 	 */
327 	@NonNull
328 	@Override
329 	public String getTextContent() {
330 		return "";
331 	}
332 
333 	/**
334 	 * {@inheritDoc}
335 	 */
336 	@Override
337 	public boolean hasAttribute(@Nullable final String name) {
338 		return Nullables.orElseThrow(getAttributes()).containsKey(name);
339 	}
340 
341 	/**
342 	 * {@inheritDoc}
343 	 */
344 	@Override
345 	public boolean hasAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName) {
346 		throw new JsonDomNotSupportedException();
347 	}
348 
349 	/**
350 	 * {@inheritDoc}
351 	 */
352 	@Override
353 	public void removeAttribute(@Nullable @SuppressWarnings("unused") final String name) {
354 		throw new JsonDomNotSupportedException();
355 	}
356 
357 	/**
358 	 * {@inheritDoc}
359 	 */
360 	@NonNull
361 	@Override
362 	public JsonDomAttribute<T> removeAttributeNode(@Nullable @SuppressWarnings("unused") final Attr oldAttr) {
363 		throw new JsonDomNotSupportedException();
364 	}
365 
366 	/**
367 	 * {@inheritDoc}
368 	 */
369 	@Override
370 	public void removeAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName) {
371 		throw new JsonDomNotSupportedException();
372 	}
373 
374 	/**
375 	 * {@inheritDoc}
376 	 */
377 	@Override
378 	public void setAttribute(@Nullable @SuppressWarnings("unused") final String name, @Nullable @SuppressWarnings("unused") final String value) {
379 		throw new JsonDomNotSupportedException();
380 	}
381 
382 	/**
383 	 * {@inheritDoc}
384 	 */
385 	@NonNull
386 	@Override
387 	public JsonDomAttribute<T> setAttributeNode(@Nullable @SuppressWarnings("unused") final Attr newAttr) {
388 		throw new JsonDomNotSupportedException();
389 	}
390 
391 	/**
392 	 * {@inheritDoc}
393 	 */
394 	@Nullable
395 	@Override
396 	public JsonDomAttribute<T> setAttributeNodeNS(@Nullable @SuppressWarnings("unused") final Attr newAttr) {
397 		throw new JsonDomNotSupportedException();
398 	}
399 
400 	/**
401 	 * {@inheritDoc}
402 	 */
403 	@Override
404 	public void setAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String qualifiedName, @Nullable @SuppressWarnings("unused") final String value) {
405 		throw new JsonDomNotSupportedException();
406 	}
407 
408 	/**
409 	 * {@inheritDoc}
410 	 */
411 	@Override
412 	public void setIdAttribute(@Nullable @SuppressWarnings("unused") final String name, @SuppressWarnings("unused") final boolean isId) {
413 		throw new JsonDomNotSupportedException();
414 	}
415 
416 	/**
417 	 * {@inheritDoc}
418 	 */
419 	@Override
420 	public void setIdAttributeNode(@Nullable @SuppressWarnings("unused") final Attr idAttr, @SuppressWarnings("unused") final boolean isId) {
421 		throw new JsonDomNotSupportedException();
422 	}
423 
424 	/**
425 	 * {@inheritDoc}
426 	 */
427 	@Override
428 	public void setIdAttributeNS(@Nullable @SuppressWarnings("unused") final String namespaceURI, @Nullable @SuppressWarnings("unused") final String localName, @SuppressWarnings("unused") final boolean isId) {
429 		throw new JsonDomNotSupportedException();
430 	}
431 
432 	/**
433 	 * Wrapped JSON element
434 	 *
435 	 * @return wrapped JSON element
436 	 */
437 	@java.lang.SuppressWarnings("all")
438 	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
439 	@lombok.Generated
440 	public JsonDomValue<T> getJsonDomValue() {
441 		return this.jsonDomValue;
442 	}
443 
444 	/**
445 	 * The elements JSON key
446 	 *
447 	 * @return the elements JSON key
448 	 */
449 	@java.lang.SuppressWarnings("all")
450 	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
451 	@lombok.Generated
452 	public String getJsonKey() {
453 		return this.jsonKey;
454 	}
455 
456 	@java.lang.Override
457 	@java.lang.SuppressWarnings("all")
458 	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
459 	@lombok.Generated
460 	public boolean equals(@edu.umd.cs.findbugs.annotations.Nullable final java.lang.Object o) {
461 		if (o == this) return true;
462 		if (!(o instanceof JsonDomElement)) return false;
463 		final JsonDomElement<?> other = (JsonDomElement<?>) o;
464 		if (!other.canEqual((java.lang.Object) this)) return false;
465 		if (!super.equals(o)) return false;
466 		final java.lang.Object this$attributes = this.getAttributes();
467 		final java.lang.Object other$attributes = other.getAttributes();
468 		if (this$attributes == null ? other$attributes != null : !this$attributes.equals(other$attributes)) return false;
469 		final java.lang.Object this$childNodes = this.getChildNodes();
470 		final java.lang.Object other$childNodes = other.getChildNodes();
471 		if (this$childNodes == null ? other$childNodes != null : !this$childNodes.equals(other$childNodes)) return false;
472 		final java.lang.Object this$jsonDomValue = this.getJsonDomValue();
473 		final java.lang.Object other$jsonDomValue = other.getJsonDomValue();
474 		if (this$jsonDomValue == null ? other$jsonDomValue != null : !this$jsonDomValue.equals(other$jsonDomValue)) return false;
475 		final java.lang.Object this$jsonKey = this.getJsonKey();
476 		final java.lang.Object other$jsonKey = other.getJsonKey();
477 		if (this$jsonKey == null ? other$jsonKey != null : !this$jsonKey.equals(other$jsonKey)) return false;
478 		return true;
479 	}
480 
481 	@java.lang.SuppressWarnings("all")
482 	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
483 	@lombok.Generated
484 	protected boolean canEqual(@edu.umd.cs.findbugs.annotations.Nullable final java.lang.Object other) {
485 		return other instanceof JsonDomElement;
486 	}
487 
488 	@java.lang.Override
489 	@java.lang.SuppressWarnings("all")
490 	@edu.umd.cs.findbugs.annotations.SuppressFBWarnings(justification = "generated code")
491 	@lombok.Generated
492 	public int hashCode() {
493 		final int PRIME = 59;
494 		int result = super.hashCode();
495 		final java.lang.Object $attributes = this.getAttributes();
496 		result = result * PRIME + ($attributes == null ? 43 : $attributes.hashCode());
497 		final java.lang.Object $childNodes = this.getChildNodes();
498 		result = result * PRIME + ($childNodes == null ? 43 : $childNodes.hashCode());
499 		final java.lang.Object $jsonDomValue = this.getJsonDomValue();
500 		result = result * PRIME + ($jsonDomValue == null ? 43 : $jsonDomValue.hashCode());
501 		final java.lang.Object $jsonKey = this.getJsonKey();
502 		result = result * PRIME + ($jsonKey == null ? 43 : $jsonKey.hashCode());
503 		return result;
504 	}
505 }