1 package org.opentrafficsim.editor;
2
3 import java.rmi.RemoteException;
4 import java.util.ArrayList;
5 import java.util.LinkedHashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Optional;
9
10 import org.djutils.event.Event;
11 import org.djutils.event.EventListener;
12 import org.djutils.immutablecollections.Immutable;
13 import org.djutils.immutablecollections.ImmutableArrayList;
14 import org.djutils.immutablecollections.ImmutableList;
15 import org.opentrafficsim.base.OtsRuntimeException;
16 import org.opentrafficsim.base.logger.Logger;
17 import org.opentrafficsim.editor.decoration.validation.XsdAllValidator;
18 import org.w3c.dom.Node;
19
20
21
22
23
24
25
26
27
28 public final class XsdTreeNodeUtil
29 {
30
31
32 private static final Map<XsdTreeNodeRoot, Map<String, XsdAllValidator>> XSD_ALL_VALIDATORS = new LinkedHashMap<>();
33
34
35
36
37 private XsdTreeNodeUtil()
38 {
39
40 }
41
42
43
44
45
46
47 static void addXsdAllValidator(final XsdTreeNode shared, final XsdTreeNode node)
48 {
49 String path = shared.getPathString();
50 XsdAllValidator validator = XSD_ALL_VALIDATORS.computeIfAbsent(shared.getRoot(), (r) -> new LinkedHashMap<>())
51 .computeIfAbsent(path, (p) -> new XsdAllValidator(node.getRoot()));
52 node.addNodeValidator(validator);
53 validator.addNode(node);
54 }
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 static void addChildren(final Node node, final XsdTreeNode parentNode, final List<XsdTreeNode> children,
70 final ImmutableList<Node> hiddenNodes, final Schema schema, final boolean flattenSequence, final int skip)
71 {
72 int skipIndex = skip;
73 XsdTreeNode root = parentNode.getRoot();
74 for (int childIndex = 0; childIndex < node.getChildNodes().getLength(); childIndex++)
75 {
76 Node child = node.getChildNodes().item(childIndex);
77 switch (child.getNodeName())
78 {
79 case "xsd:element":
80 if (children.size() == skipIndex)
81 {
82 skipIndex = -1;
83 break;
84 }
85 XsdTreeNode element;
86 Optional<String> ref = DocumentReader.getAttribute(child, "ref");
87 Optional<String> type = DocumentReader.getAttribute(child, "type");
88 if (ref.isPresent())
89 {
90 element = new XsdTreeNode(parentNode, ref(child, ref.get(), schema), append(hiddenNodes, node), child);
91 }
92 else if (type.isPresent())
93 {
94 Node typedNode = type(child, type.get(), schema);
95 if (typedNode == null)
96 {
97
98 element = new XsdTreeNode(parentNode, child, append(hiddenNodes, node));
99 }
100 else
101 {
102 element = new XsdTreeNode(parentNode, typedNode, append(hiddenNodes, node), child);
103 }
104 }
105 else
106 {
107 element = new XsdTreeNode(parentNode, child, append(hiddenNodes, node));
108 }
109 children.add(element);
110 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
111 new Object[] {element, parentNode, parentNode.children.indexOf(element)});
112 break;
113 case "xsd:sequence":
114 if (children.size() == skipIndex)
115 {
116 skipIndex = -1;
117 break;
118 }
119 if (flattenSequence)
120 {
121 addChildren(child, parentNode, children, XsdTreeNodeUtil.append(hiddenNodes, node), schema, false, -1);
122 }
123 else
124 {
125 XsdTreeNode sequence = new XsdTreeNode(parentNode, child, append(hiddenNodes, node));
126 children.add(sequence);
127 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
128 new Object[] {sequence, parentNode, parentNode.children.indexOf(sequence)});
129 }
130 break;
131 case "xsd:choice":
132 case "xsd:all":
133 if (children.size() == skipIndex)
134 {
135 skipIndex = -1;
136 break;
137 }
138 XsdTreeNode choice = new XsdTreeNode(parentNode, child, append(hiddenNodes, node));
139 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
140 new Object[] {choice, parentNode, parentNode.children.indexOf(choice)});
141 choice.createOptions();
142 children.add(choice);
143 choice.setOption(choice.options.get(0));
144 break;
145 case "xsd:extension":
146 if (children.size() == skipIndex)
147 {
148 skipIndex = -1;
149 break;
150 }
151 XsdTreeNode extension = new XsdTreeNode(parentNode, child, append(hiddenNodes, node));
152 root.fireEvent(XsdTreeNodeRoot.NODE_CREATED,
153 new Object[] {extension, parentNode, parentNode.children.indexOf(extension)});
154 children.add(extension);
155 break;
156 case "xsd:attribute":
157 case "xsd:annotation":
158 case "xsd:simpleType":
159 case "xsd:restriction":
160 case "xsd:simpleContent":
161 case "xsd:union":
162 case "#text":
163
164 break;
165 default:
166 Logger.ots().trace("Ignoring a {}", child.getNodeName());
167 }
168 }
169 }
170
171
172
173
174
175
176
177
178
179 private static Node ref(final Node node, final String ref, final Schema schema)
180 {
181 if (ref.equals("xi:include"))
182 {
183 return XiIncludeNode.XI_INCLUDE;
184 }
185 return schema.getElement(ref)
186 .orElseThrow(() -> new OtsRuntimeException("Unable to load ref for " + ref + " from XSD schema."));
187 }
188
189
190
191
192
193
194
195 private static ImmutableList<Node> append(final ImmutableList<Node> hiddenNodes, final Node node)
196 {
197 List<Node> list = new ArrayList<>(hiddenNodes.size() + 1);
198 list.addAll(hiddenNodes.toCollection());
199 list.add(node);
200 return new ImmutableArrayList<>(list, Immutable.WRAP);
201 }
202
203
204
205
206
207
208
209
210
211 private static Node type(final Node node, final String type, final Schema schema)
212 {
213 if (type.startsWith("xsd:"))
214 {
215 return null;
216 }
217 return schema.getType(type)
218 .orElseThrow(() -> new OtsRuntimeException("Unable to load type for " + type + " from XSD schema."));
219 }
220
221
222
223
224
225
226 static List<String> getOptionsFromRestrictions(final List<Node> restrictions)
227 {
228 List<String> options = new ArrayList<>();
229 for (Node restriction : restrictions)
230 {
231 List<Node> enumerations = DocumentReader.getChildren(restriction, "xsd:enumeration");
232 for (Node enumeration : enumerations)
233 {
234 options.add(DocumentReader.getAttribute(enumeration, "value").get());
235 }
236 }
237 return options;
238 }
239
240
241
242
243
244
245
246
247 protected static void fireCreatedEventOnExistingNodes(final XsdTreeNode node, final EventListener listener)
248 throws RemoteException
249 {
250 List<XsdTreeNode> subNodes = node.children == null ? new ArrayList<>() : new ArrayList<>(node.children);
251
252 if (node.choice != null && node.choice.selected.equals(node))
253 {
254 subNodes.add(node.choice);
255 subNodes.addAll(node.choice.options);
256 subNodes.remove(node);
257 }
258 for (XsdTreeNode child : subNodes)
259 {
260 fireCreatedEventOnExistingNodes(child, listener);
261 }
262 Event event = new Event(XsdTreeNodeRoot.NODE_CREATED, new Object[] {node, node.getParent(), subNodes.indexOf(node)});
263 listener.notify(event);
264 }
265
266
267
268
269
270
271
272
273
274
275
276
277 static Map<Node, ImmutableList<Node>> getRelevantNodesWithChildren(final Node node, final ImmutableList<Node> hiddenNodes,
278 final Schema schema)
279 {
280 Node complexType = node.getNodeName().equals("xsd:complexType") ? node
281 : DocumentReader.getChild(node, "xsd:complexType").orElse(null);
282 if (complexType != null)
283 {
284 Optional<Node> sequence = DocumentReader.getChild(complexType, "xsd:sequence");
285 if (sequence.isPresent())
286 {
287 return Map.of(sequence.get(), append(hiddenNodes, complexType));
288 }
289 Optional<Node> complexContent = DocumentReader.getChild(complexType, "xsd:complexContent");
290 if (complexContent.isPresent())
291 {
292 Optional<Node> extension = DocumentReader.getChild(complexContent.get(), "xsd:extension");
293 if (extension.isPresent())
294 {
295 ImmutableList<Node> hiddenExtension = append(append(hiddenNodes, complexType), complexContent.get());
296 LinkedHashMap<Node, ImmutableList<Node>> elements = new LinkedHashMap<>();
297 Optional<String> base = DocumentReader.getAttribute(extension.get(), "base");
298 if (base.isPresent())
299 {
300 Optional<Node> baseNode = schema.getType(base.get());
301 if (baseNode.isPresent())
302 {
303 elements.putAll(getRelevantNodesWithChildren(baseNode.get(),
304 append(hiddenExtension, extension.get()), schema));
305 }
306 }
307 elements.put(extension.get(), hiddenExtension);
308 return elements;
309 }
310 }
311 return Map.of(complexType, hiddenNodes);
312 }
313 return Map.of(node, hiddenNodes);
314 }
315
316
317
318
319
320
321
322
323 static boolean haveSameType(final XsdTreeNode node1, final XsdTreeNode node2)
324 {
325 return (node1.referringXsdNode != null && node1.referringXsdNode.equals(node2.referringXsdNode))
326 || (node1.referringXsdNode == null && node1.xsdNode.equals(node2.xsdNode));
327 }
328
329
330
331
332
333
334
335 public static boolean valuesAreEqual(final String value1, final String value2)
336 {
337 boolean value1Empty = value1 == null || value1.isEmpty();
338 boolean value2Empty = value2 == null || value2.isEmpty();
339 return (value1Empty && value2Empty) || (value1 != null && value1.equals(value2));
340 }
341
342
343
344
345
346
347
348 static boolean isEditable(final Node xsdNode, final Schema schema)
349 {
350 return getValueDefiningNode(xsdNode, schema) != null;
351 }
352
353
354
355
356
357
358
359 static Node getValueDefiningNode(final Node xsdNode, final Schema schema)
360 {
361 if (xsdNode.equals(XiIncludeNode.XI_INCLUDE))
362 {
363 return null;
364 }
365 Node simpleType = xsdNode.getNodeName().equals("xsd:simpleType") ? xsdNode
366 : DocumentReader.getChild(xsdNode, "xsd:simpleType").orElse(null);
367 if (simpleType != null)
368 {
369 return simpleType;
370 }
371 Node complexType = xsdNode.getNodeName().equals("xsd:complexType") ? xsdNode
372 : DocumentReader.getChild(xsdNode, "xsd:complexType").orElse(null);
373 while (complexType != null)
374 {
375 Optional<Node> simpleContent = DocumentReader.getChild(complexType, "xsd:simpleContent");
376 if (simpleContent.isPresent())
377 {
378 return complexType;
379 }
380 Optional<Node> complexContent = DocumentReader.getChild(complexType, "xsd:complexContent");
381 complexType = null;
382 if (complexContent.isPresent())
383 {
384 Node derivation = DocumentReader.getChild(complexContent.get(), "xsd:extension")
385 .orElse(DocumentReader.getChild(complexContent.get(), "xsd:restriction").orElse(null));
386 if (derivation != null)
387 {
388 String base = DocumentReader.getAttribute(derivation, "base").orElse(null);
389 complexType = schema.getType(base).orElse(null);
390 }
391 }
392 }
393 Optional<String> type = DocumentReader.getAttribute(xsdNode, "type");
394 if ((xsdNode.getNodeName().equals("xsd:element") || xsdNode.getNodeName().equals("xsd:attribute")) && type.isPresent()
395 && type.get().startsWith("xsd:"))
396 {
397
398 return xsdNode;
399 }
400 if (type.isPresent())
401 {
402 Node typeNode = schema.getType(type.get()).orElse(null);
403 if (typeNode != null)
404 {
405 return getValueDefiningNode(typeNode, schema);
406 }
407 }
408 return null;
409 }
410
411
412
413
414
415 static final class LoadingIndices
416 {
417
418 private int xmlNode;
419
420
421 private int xsdTreeNode;
422
423
424
425
426
427
428 LoadingIndices(final int xmlNode, final int xsdTreeNode)
429 {
430 this.xmlNode = xmlNode;
431 this.xsdTreeNode = xsdTreeNode;
432 }
433
434
435
436
437
438 public int getXmlNode()
439 {
440 return this.xmlNode;
441 }
442
443
444
445
446
447 public void setXmlNode(final int xmlNode)
448 {
449 this.xmlNode = xmlNode;
450 }
451
452
453
454
455
456 public int getXsdTreeNode()
457 {
458 return this.xsdTreeNode;
459 }
460
461
462
463
464
465 public void setXsdTreeNode(final int xsdTreeNode)
466 {
467 this.xsdTreeNode = xsdTreeNode;
468 }
469 }
470
471
472
473
474 enum Occurs
475 {
476
477 MIN("minOccurs"),
478
479
480 MAX("maxOccurs");
481
482
483 private final String attribute;
484
485
486
487
488
489 Occurs(final String attribute)
490 {
491 this.attribute = attribute;
492 }
493
494
495
496
497
498
499 public int get(final Node node)
500 {
501 Optional<String> occursValue = DocumentReader.getAttribute(node, this.attribute);
502 if (occursValue.isEmpty())
503 {
504 return 1;
505 }
506 if ("unbounded".equals(occursValue.orElse(null)))
507 {
508 return -1;
509 }
510 return Integer.valueOf(occursValue.get());
511 }
512 }
513
514 }