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 if (xsdNode.equals(XiIncludeNode.XI_INCLUDE))
351 {
352 return false;
353 }
354 if (xsdNode.getChildNodes().getLength() == DocumentReader.getChildren(xsdNode, "#text").size()
355 && xsdNode.getChildNodes().getLength() > 0)
356 {
357
358 return true;
359 }
360 Node simpleType = xsdNode.getNodeName().equals("xsd:simpleType") ? xsdNode
361 : DocumentReader.getChild(xsdNode, "xsd:simpleType").orElse(null);
362 if (simpleType != null)
363 {
364 return true;
365 }
366 Node complexType = xsdNode.getNodeName().equals("xsd:complexType") ? xsdNode
367 : DocumentReader.getChild(xsdNode, "xsd:complexType").orElse(null);
368 boolean isComplex = complexType != null;
369 while (complexType != null)
370 {
371 Optional<Node> simpleContent = DocumentReader.getChild(complexType, "xsd:simpleContent");
372 if (simpleContent.isPresent())
373 {
374 return true;
375 }
376 Optional<Node> complexContent = DocumentReader.getChild(complexType, "xsd:complexContent");
377 complexType = null;
378 if (complexContent.isPresent())
379 {
380 Optional<Node> extension = DocumentReader.getChild(complexContent.get(), "xsd:extension");
381 if (extension.isPresent())
382 {
383 String base = DocumentReader.getAttribute(extension.get(), "base").orElse(null);
384 complexType = schema.getType(base).orElse(null);
385 }
386 }
387 }
388 if (isComplex)
389 {
390
391 return false;
392 }
393 Optional<String> type = DocumentReader.getAttribute(xsdNode, "type");
394 if (xsdNode.getNodeName().equals("xsd:element") && (type.isEmpty() || type.get().startsWith("xsd:")))
395 {
396 return true;
397 }
398 return false;
399 }
400
401
402
403
404
405 static final class LoadingIndices
406 {
407
408 private int xmlNode;
409
410
411 private int xsdTreeNode;
412
413
414
415
416
417
418 LoadingIndices(final int xmlNode, final int xsdTreeNode)
419 {
420 this.xmlNode = xmlNode;
421 this.xsdTreeNode = xsdTreeNode;
422 }
423
424
425
426
427
428 public int getXmlNode()
429 {
430 return this.xmlNode;
431 }
432
433
434
435
436
437 public void setXmlNode(final int xmlNode)
438 {
439 this.xmlNode = xmlNode;
440 }
441
442
443
444
445
446 public int getXsdTreeNode()
447 {
448 return this.xsdTreeNode;
449 }
450
451
452
453
454
455 public void setXsdTreeNode(final int xsdTreeNode)
456 {
457 this.xsdTreeNode = xsdTreeNode;
458 }
459 }
460
461
462
463
464 enum Occurs
465 {
466
467 MIN("minOccurs"),
468
469
470 MAX("maxOccurs");
471
472
473 private final String attribute;
474
475
476
477
478
479 Occurs(final String attribute)
480 {
481 this.attribute = attribute;
482 }
483
484
485
486
487
488
489 public int get(final Node node)
490 {
491 Optional<String> occursValue = DocumentReader.getAttribute(node, this.attribute);
492 if (occursValue.isEmpty())
493 {
494 return 1;
495 }
496 if ("unbounded".equals(occursValue.orElse(null)))
497 {
498 return -1;
499 }
500 return Integer.valueOf(occursValue.get());
501 }
502 }
503
504 }