1
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
28
29
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
36
37 public static final String ATTRIBUTE_NAME = constant("name");
38
39
40
41 public static final String ATTRIBUTE_TYPE = constant("type");
42
43
44
45 private static final String GET_ELEMENTS_BY_TAG_NAME_WILDCARD = "*";
46
47
48
49
50
51
52 @SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
53 private static final String XML_NAME_START_CHARACTERS = ":A-Z_a-zÀ-ÖØ-öø-˿Ͱ-ͽͿ--⁰-Ⰰ-、-豈-﷏ﷰ-�";
54
55
56
57
58
59
60 @SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
61 private static final String XML_NAME_CHARACTERS = XML_NAME_START_CHARACTERS + "-.0-9·̀-ͯ‿-⁀";
62
63
64
65 @SuppressWarnings("checkstyle:MultipleStringLiterals")
66 private static final Pattern PATTERN_ILLEGAL_XML_CHARACTERS = Pattern.compile("[^" + XML_NAME_CHARACTERS + "]+");
67
68
69
70 @SuppressWarnings("checkstyle:MultipleStringLiterals")
71 private static final Pattern PATTERN_ILLEGAL_XML_CHARACTERS_AT_START = Pattern.compile("^[^" + XML_NAME_START_CHARACTERS + "]+");
72
73
74
75 private static final Pattern PATTERN_IS_NUMBER = Pattern.compile("^(0|[1-9]\\d*)$");
76
77
78
79
80
81
82
83
84
85
86
87
88
89 private static String createTagName(final JsonDomNode<?> parentNode, final String jsonKey) {
90
91 if (parentNode instanceof JsonDomElement && ((JsonDomElement<?>) parentNode).getJsonDomValue().getType() == JsonDomType.ARRAY) {
92 return "element";
93 }
94
95 if (jsonKey.isEmpty()) {
96 return "empty";
97 }
98
99 final String trimmedName = jsonKey.trim();
100 if (trimmedName.isEmpty()) {
101 return jsonKey.length() == 1 ? "whitespace" : "whitespaces";
102 }
103
104 if (Patterns.matches(PATTERN_IS_NUMBER, trimmedName).isPresent()) {
105 return 'n' + trimmedName;
106 }
107
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
113 return "non-empty";
114 }
115
116
117
118
119
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
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
130
131 private final JsonDomValue<T> jsonDomValue;
132
133
134
135 private final String jsonKey;
136
137
138
139
140
141
142
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
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
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
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
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
188
189 @NonNull
190 @Override
191 public JsonDomNamedNodeMap<JsonDomAttribute<T>> getAttributes() {
192 return attributes.get();
193 }
194
195
196
197
198 @NonNull
199 @Override
200 public JsonDomNodeList<JsonDomNode<T>> getChildNodes() {
201 return childNodes.get();
202 }
203
204
205
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
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
237
238 @Override
239 public T getJsonElement() {
240 return getJsonDomValue().getJsonElement();
241 }
242
243
244
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
263
264 @Override
265 public short getNodeType() {
266 return ELEMENT_NODE;
267 }
268
269
270
271
272 @Nullable
273 @Override
274 public String getNodeValue() {
275 return null;
276 }
277
278
279
280
281 @NonNull
282 @Override
283 public JsonDomDocument<T> getOwnerDocument() {
284 return Nullables.orElseThrow(getParentNode()).getOwnerDocument();
285 }
286
287
288
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
308
309 @Nullable
310 @Override
311 public TypeInfo getSchemaTypeInfo() {
312 return null;
313 }
314
315
316
317
318 @NonNull
319 @Override
320 public String getTagName() {
321 return getNodeName();
322 }
323
324
325
326
327 @NonNull
328 @Override
329 public String getTextContent() {
330 return "";
331 }
332
333
334
335
336 @Override
337 public boolean hasAttribute(@Nullable final String name) {
338 return Nullables.orElseThrow(getAttributes()).containsKey(name);
339 }
340
341
342
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
351
352 @Override
353 public void removeAttribute(@Nullable @SuppressWarnings("unused") final String name) {
354 throw new JsonDomNotSupportedException();
355 }
356
357
358
359
360 @NonNull
361 @Override
362 public JsonDomAttribute<T> removeAttributeNode(@Nullable @SuppressWarnings("unused") final Attr oldAttr) {
363 throw new JsonDomNotSupportedException();
364 }
365
366
367
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
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
384
385 @NonNull
386 @Override
387 public JsonDomAttribute<T> setAttributeNode(@Nullable @SuppressWarnings("unused") final Attr newAttr) {
388 throw new JsonDomNotSupportedException();
389 }
390
391
392
393
394 @Nullable
395 @Override
396 public JsonDomAttribute<T> setAttributeNodeNS(@Nullable @SuppressWarnings("unused") final Attr newAttr) {
397 throw new JsonDomNotSupportedException();
398 }
399
400
401
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
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
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
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
434
435
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
446
447
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 }