1 package org.opentrafficsim.editor;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.util.ArrayList;
6 import java.util.Collections;
7 import java.util.LinkedHashMap;
8 import java.util.LinkedHashSet;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Map.Entry;
12 import java.util.NoSuchElementException;
13 import java.util.Objects;
14 import java.util.Optional;
15 import java.util.Set;
16 import java.util.SortedMap;
17 import java.util.SortedSet;
18 import java.util.TreeMap;
19 import java.util.TreeSet;
20 import java.util.function.Consumer;
21 import java.util.function.Function;
22 import java.util.stream.Collectors;
23
24 import javax.xml.parsers.ParserConfigurationException;
25
26 import org.djutils.event.Event;
27 import org.djutils.event.EventListener;
28 import org.djutils.event.EventListenerMap;
29 import org.djutils.event.EventType;
30 import org.djutils.event.LocalEventProducer;
31 import org.djutils.event.reference.Reference;
32 import org.djutils.event.reference.ReferenceType;
33 import org.djutils.exceptions.Throw;
34 import org.djutils.immutablecollections.ImmutableArrayList;
35 import org.djutils.immutablecollections.ImmutableList;
36 import org.djutils.metadata.MetaData;
37 import org.djutils.metadata.ObjectDescriptor;
38 import org.opentrafficsim.base.logger.Logger;
39 import org.opentrafficsim.editor.DocumentReader.NodeAnnotation;
40 import org.opentrafficsim.editor.XsdTreeNodeUtil.LoadingIndices;
41 import org.opentrafficsim.editor.XsdTreeNodeUtil.Occurs;
42 import org.opentrafficsim.editor.decoration.validation.CoupledValidator;
43 import org.opentrafficsim.editor.decoration.validation.KeyValidator;
44 import org.opentrafficsim.editor.decoration.validation.KeyrefValidator;
45 import org.opentrafficsim.editor.decoration.validation.ValueValidator;
46 import org.opentrafficsim.swing.gui.OtsAnimationPanel;
47 import org.w3c.dom.Document;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.Node;
50 import org.w3c.dom.NodeList;
51 import org.xml.sax.SAXException;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class XsdTreeNode extends LocalEventProducer
71 {
72
73
74 public static final EventType VALUE_CHANGED = new EventType("VALUECHANGED",
75 new MetaData("Value changed", "Value changed on node",
76 new ObjectDescriptor("Node", "Node with changed value", XsdTreeNode.class),
77 new ObjectDescriptor("Previous", "Previous node value", String.class)));
78
79
80 public static final EventType ATTRIBUTE_CHANGED = new EventType("ATTRIBUTECHANGED",
81 new MetaData("Attribute changed", "Attribute changed on node",
82 new ObjectDescriptor("Node", "Node with changed attribute value", XsdTreeNode.class),
83 new ObjectDescriptor("Attribute", "Name of the attribute", String.class),
84 new ObjectDescriptor("Previous", "Previous attribute value", String.class)));
85
86
87 public static final EventType OPTION_CHANGED = new EventType("OPTIONCHANGED",
88 new MetaData("Option changed", "Option changed on node",
89 new ObjectDescriptor("Node", "Node on which the event is called", XsdTreeNode.class),
90 new ObjectDescriptor("Selected", "Newly selected option node", XsdTreeNode.class),
91 new ObjectDescriptor("Previous", "Previously selected option node", XsdTreeNode.class)));
92
93
94 public static final EventType ACTIVATION_CHANGED = new EventType("ACTIVATIONCHANGED",
95 new MetaData("Activation changed", "Activation changed on node",
96 new ObjectDescriptor("Node", "Node with changed activation.", XsdTreeNode.class),
97 new ObjectDescriptor("Activation", "New activation state.", Boolean.class)));
98
99
100 public static final EventType MOVED = new EventType("MOVED",
101 new MetaData("Node moved", "Node moved", new ObjectDescriptor("Node", "Node that was moved.", XsdTreeNode.class),
102 new ObjectDescriptor("OldIndex", "Old index.", Integer.class),
103 new ObjectDescriptor("NewIndex", "New index.", Integer.class)));
104
105
106 private static final int MAX_OPTIONNAME_LENGTH = 64;
107
108
109 @SuppressWarnings("checkstyle:visibilitymodifier")
110 XsdTreeNode parent;
111
112
113 @SuppressWarnings("checkstyle:visibilitymodifier")
114 Node xsdNode;
115
116
117 private final ImmutableList<Node> hiddenNodes;
118
119
120
121
122
123 @SuppressWarnings("checkstyle:visibilitymodifier")
124 Node referringXsdNode;
125
126
127 private final Schema schema;
128
129
130 private int minOccurs = 0;
131
132
133 private int maxOccurs = -1;
134
135
136
137
138 private final String pathString;
139
140
141
142
143 @SuppressWarnings("checkstyle:visibilitymodifier")
144 XsdTreeNode choice;
145
146
147 @SuppressWarnings("checkstyle:visibilitymodifier")
148 List<XsdTreeNode> options;
149
150
151 @SuppressWarnings("checkstyle:visibilitymodifier")
152 XsdTreeNode selected;
153
154
155
156
157 @SuppressWarnings("checkstyle:visibilitymodifier")
158 List<XsdTreeNode> children;
159
160
161
162
163 private List<Node> attributeNodes;
164
165
166 private List<String> attributeValues;
167
168
169
170
171 @SuppressWarnings("checkstyle:visibilitymodifier")
172 boolean active;
173
174
175
176
177
178
179 private boolean deactivated;
180
181
182 private Boolean isIdentifiable;
183
184
185 private int idIndex;
186
187
188 private Boolean isEditable;
189
190
191 private String value;
192
193
194
195
196
197 private boolean isIncluded;
198
199
200
201
202 private Function<XsdTreeNode, String> stringFunction;
203
204
205 private Map<String, Consumer<XsdTreeNode>> consumers = new LinkedHashMap<>();
206
207
208 private String description;
209
210
211 private Set<Function<XsdTreeNode, String>> nodeValidators = new LinkedHashSet<>();
212
213
214 private SortedMap<ValueValidator, Object> valueValidators = new TreeMap<>();
215
216
217 private Map<String, SortedSet<ValueValidator>> attributeValidators = new LinkedHashMap<>();
218
219
220
221
222
223
224 private Map<String, Map<ValueValidator, Object>> attributeValidatorFields = new LinkedHashMap<>();
225
226
227 private Boolean isSelfValid = null;
228
229
230 private Boolean isValid = null;
231
232
233 private Boolean valueValid = null;
234
235
236 private String valueInvalidMessage = null;
237
238
239 private Boolean nodeValid = null;
240
241
242 private String nodeInvalidMessage = null;
243
244
245 private List<Boolean> attributeValid;
246
247
248 private List<String> attributeInvalidMessage;
249
250
251
252
253
254
255 protected XsdTreeNode(final Schema schema)
256 {
257 Throw.whenNull(schema, "XsdSchema may not be null.");
258 this.parent = null;
259 this.hiddenNodes = new ImmutableArrayList<>(Collections.emptyList());
260 this.schema = schema;
261 this.xsdNode = this.schema.getRoot();
262 this.referringXsdNode = null;
263 setOccurs();
264 this.pathString = buildPathLocation();
265 this.active = true;
266 this.isIncluded = false;
267 }
268
269
270
271
272
273
274
275 XsdTreeNode(final XsdTreeNode parent, final Node xsdNode, final ImmutableList<Node> hiddenNodes)
276 {
277 this(parent, xsdNode, hiddenNodes, null);
278 }
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317 XsdTreeNode(final XsdTreeNode parent, final Node xsdNode, final ImmutableList<Node> hiddenNodes,
318 final Node referringXsdNode)
319 {
320 Throw.whenNull(xsdNode, "Node may not be null.");
321 this.parent = parent;
322 this.xsdNode = xsdNode;
323 this.hiddenNodes = hiddenNodes;
324 this.referringXsdNode = referringXsdNode;
325 this.schema = parent.schema;
326 setOccurs();
327 this.active = this.minOccurs > 0;
328 this.pathString = buildPathLocation();
329 this.isIncluded = parent.isIncluded;
330 this.value = referringXsdNode == null ? DocumentReader.getAttribute(xsdNode, "default").orElse(null)
331 : DocumentReader.getAttribute(referringXsdNode, "default").orElse(null);
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349 private void setOccurs()
350 {
351 Node node = this.choice != null ? this.choice.xsdNode : getRelevantNode();
352 this.minOccurs = Occurs.MIN.get(node);
353 this.maxOccurs = Occurs.MAX.get(node);
354
355
356
357 if (getNodeName().equals("xsd:all"))
358 {
359 int childCount = 0;
360 for (int i = 0; i < this.xsdNode.getChildNodes().getLength(); i++)
361 {
362 Node child = this.xsdNode.getChildNodes().item(i);
363 if (!child.getNodeName().equals("#text"))
364 {
365 childCount++;
366 }
367 }
368 this.maxOccurs *= childCount;
369 }
370 }
371
372
373
374
375
376 private String buildPathLocation()
377 {
378 List<XsdTreeNode> path = getPath();
379 StringBuilder pathStr = new StringBuilder(((XsdTreeNode) path.get(0)).getNodeName());
380 for (int i = 1; i < path.size(); i++)
381 {
382 String nodeString = ((XsdTreeNode) path.get(i)).getNodeName();
383
384 if ((!nodeString.equals("xsd:choice") && !nodeString.equals("xsd:all") && !nodeString.equals("xsd:sequence")
385 && !nodeString.equals("xi:include")) || i == path.size() - 1)
386 {
387 pathStr.append(".").append(nodeString);
388 }
389 }
390 return pathStr.toString();
391 }
392
393
394
395
396
397 public List<XsdTreeNode> getPath()
398 {
399 List<XsdTreeNode> path = this.parent != null ? this.parent.getPath() : new ArrayList<>();
400 path.add(this);
401 return path;
402 }
403
404
405
406
407
408 public XsdTreeNodeRoot getRoot()
409 {
410 return this.parent.getRoot();
411 }
412
413
414
415
416
417
418 private Node getRelevantNode()
419 {
420 return this.referringXsdNode == null ? this.xsdNode : this.referringXsdNode;
421 }
422
423
424
425
426
427 public boolean isSequence()
428 {
429 return this.xsdNode.getNodeName().equals("xsd:sequence");
430 }
431
432
433
434
435
436
437
438 public boolean isChoice()
439 {
440 return this.choice != null;
441 }
442
443
444
445
446
447 public List<XsdOption> getOptions()
448 {
449 List<XsdOption> out = new ArrayList<>();
450 if (this.choice != null)
451 {
452 for (XsdTreeNode node : this.choice.options)
453 {
454 out.add(new XsdOption(node, this.choice, node.equals(this.choice.selected)));
455 }
456 }
457 return out;
458 }
459
460
461
462
463
464 public void setOption(final XsdTreeNode node)
465 {
466 Throw.when(!isChoice(), IllegalStateException.class, "Setting option on node that is not (part of) a choice.");
467 Throw.when(!this.choice.options.contains(node) && !this.choice.equals(node), IllegalStateException.class,
468 "Setting option on node that does not have this option.");
469 XsdTreeNode previous = this.choice.selected == null ? this.choice : this.choice.selected;
470 if (node.equals(previous))
471 {
472 return;
473 }
474 this.choice.selected = node;
475 int index = removeOptionFromParent();
476 this.parent.children.add(index, node);
477 node.invalidate();
478 this.choice.options.forEach((n) -> n.fireEvent(XsdTreeNodeRoot.OPTION_CHANGED, new Object[] {n, node, previous}));
479 }
480
481
482
483
484
485 private int removeOptionFromParent()
486 {
487 int removeIndex = this.parent.children.indexOf(this);
488 if (removeIndex >= 0)
489 {
490 this.parent.children.remove(removeIndex);
491 return removeIndex;
492 }
493 for (XsdTreeNode node : this.choice.options)
494 {
495 removeIndex = this.parent.children.indexOf(node);
496 if (removeIndex >= 0)
497 {
498 this.parent.children.remove(removeIndex);
499 return removeIndex;
500 }
501 }
502 return this.parent.children.size();
503 }
504
505
506
507
508
509 public XsdTreeNode getOption()
510 {
511 return this.choice.selected;
512 }
513
514
515
516
517 void createOptions()
518 {
519 Throw.when(!this.xsdNode.getNodeName().equals("xsd:choice") && !this.xsdNode.getNodeName().equals("xsd:all"),
520 IllegalStateException.class, "Can only add options for a node of type xsd:choice or xsd:all.");
521 this.options = new ArrayList<>();
522 XsdTreeNodeUtil.addChildren(this.xsdNode, this.parent, this.options, this.hiddenNodes, this.schema, false, -1);
523 this.choice = this;
524 for (XsdTreeNode option : this.options)
525 {
526 option.minOccurs = this.minOccurs;
527 option.maxOccurs = this.maxOccurs;
528 if (this.minOccurs == 0)
529 {
530 option.active = false;
531 }
532 option.choice = this;
533 }
534 if (this.choice.xsdNode.getNodeName().equals("xsd:all"))
535 {
536 for (XsdTreeNode option : this.options)
537 {
538 XsdTreeNodeUtil.addXsdAllValidator(this.choice, option);
539 }
540 }
541 }
542
543
544
545
546
547
548 public boolean isSingleChoiceType()
549 {
550 boolean choiceFound = false;
551 for (int i = 0; i < this.xsdNode.getChildNodes().getLength(); i++)
552 {
553 Node child = this.xsdNode.getChildNodes().item(i);
554 String name = child.getNodeName();
555 if (name.equals("xsd:choice") && Occurs.MAX.get(child) == 1)
556 {
557 if (choiceFound)
558 {
559 return false;
560 }
561 choiceFound = true;
562 }
563 else if (!name.equals("#text"))
564 {
565 return false;
566 }
567 }
568 return choiceFound;
569 }
570
571
572
573
574
575
576
577 public int getChildCount()
578 {
579 if (!this.active)
580 {
581 return 0;
582 }
583 assureChildren();
584 return this.children.size();
585 }
586
587
588
589
590
591
592 public void setChild(final int index, final XsdTreeNode child)
593 {
594 if (index >= this.children.size())
595 {
596 this.children.add(child);
597 }
598 else
599 {
600 this.children.add(index, child);
601 }
602 child.parent = this;
603 child.invalidate();
604 }
605
606
607
608
609
610
611 public XsdTreeNode getChild(final int index)
612 {
613 assureChildren();
614 return this.children.get(index);
615 }
616
617
618
619
620
621
622 public boolean hasChild(final String name)
623 {
624 assureChildren();
625 for (XsdTreeNode child : this.children)
626 {
627 if (child.getNodeName().equals("xsd:sequence") || child.getNodeName().equals("xsd:choice")
628 || child.getNodeName().equals("xsd:all"))
629 {
630 return child.hasChild(name);
631 }
632 if (child.getNodeName().equals(name))
633 {
634 return true;
635 }
636 }
637 return false;
638 }
639
640
641
642
643
644
645
646
647 public XsdTreeNode getFirstChild(final String name)
648 {
649 assureChildren();
650 for (XsdTreeNode child : this.children)
651 {
652 if (child.getNodeName().equals("xsd:sequence") || child.getNodeName().equals("xsd:choice")
653 || child.getNodeName().equals("xsd:all"))
654 {
655 try
656 {
657 return child.getFirstChild(name);
658 }
659 catch (NoSuchElementException ex)
660 {
661
662 }
663 }
664 if (child.getNodeName().equals(name))
665 {
666 return child;
667 }
668 }
669 throw new NoSuchElementException("Node does not have a child named " + name);
670 }
671
672
673
674
675
676 public List<XsdTreeNode> getChildren()
677 {
678 assureChildren();
679 return new ArrayList<>(this.children);
680 }
681
682
683
684
685 protected void assureChildren()
686 {
687 if (this.children != null)
688 {
689 return;
690 }
691 if (!this.active)
692 {
693 return;
694 }
695 this.children = new ArrayList<>();
696 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
697 {
698 if (this.attributeValues == null || this.attributeValues.get(0) == null)
699 {
700 return;
701 }
702 File file = new File(this.attributeValues.get(0));
703 if (!file.isAbsolute())
704 {
705 file = new File(getRoot().getDirectory() + this.attributeValues.get(0));
706 }
707 if (!file.exists() && this.attributeValues.get(1) != null)
708 {
709 file = new File(this.attributeValues.get(1));
710 if (!file.isAbsolute())
711 {
712 file = new File(getRoot().getDirectory() + this.attributeValues.get(1));
713 }
714 }
715 if (file.exists())
716 {
717 Document document;
718 try
719 {
720 document = DocumentReader.open(file.toURI());
721 }
722 catch (SAXException | IOException | ParserConfigurationException exception)
723 {
724 return;
725 }
726 Node xsdIncludeNode = document.getFirstChild();
727 String nameXml = xsdIncludeNode.getNodeName().replace("ots:", "");
728
729
730 for (XsdTreeNode sibling : this.parent.children)
731 {
732 if (sibling.isRelevantNode(nameXml))
733 {
734 XsdTreeNode child =
735 new XsdTreeNode(this, sibling.xsdNode, sibling.hiddenNodes, sibling.referringXsdNode);
736 child.isIncluded = true;
737 this.children.add(child);
738 getRoot().fireEvent(XsdTreeNodeRoot.NODE_CREATED,
739 new Object[] {child, child.parent, child.parent.children.indexOf(child)});
740 child.loadXmlNodes(xsdIncludeNode);
741 return;
742 }
743 }
744 }
745 }
746 else if (!this.xsdNode.hasChildNodes())
747 {
748 return;
749 }
750 Map<Node, ImmutableList<Node>> relevantNodes = XsdTreeNodeUtil.getRelevantNodesWithChildren(this.xsdNode,
751 new ImmutableArrayList<>(Collections.emptyList()), this.schema);
752 for (Entry<Node, ImmutableList<Node>> entry : relevantNodes.entrySet())
753 {
754 XsdTreeNodeUtil.addChildren(entry.getKey(), this, this.children, entry.getValue(), this.schema, true, -1);
755 }
756 for (int index = 0; index < this.children.size(); index++)
757 {
758 XsdTreeNode child = this.children.get(index);
759 for (int occurs = 1; occurs < child.minOccurs; occurs++)
760 {
761 if (!child.isActive())
762 {
763 child.setActive();
764 }
765 child.add();
766 index++;
767 }
768 }
769 }
770
771
772
773
774
775 public XsdTreeNode getParent()
776 {
777 return this.parent;
778 }
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798 private synchronized void assureAttributesAndDescription()
799 {
800 if (this.attributeNodes != null)
801 {
802 return;
803 }
804 this.attributeNodes = new ArrayList<>();
805 this.attributeValues = new ArrayList<>();
806 this.attributeValid = new ArrayList<>();
807 this.attributeInvalidMessage = new ArrayList<>();
808 this.description = NodeAnnotation.DESCRIPTION.get(getRelevantNode()).orElse(null);
809 int descriptionSpecificity = this.description != null ? 0 : Integer.MIN_VALUE;
810 Node complexType =
811 (this.xsdNode.getNodeName().equals("xsd:complexType") || this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
812 ? this.xsdNode : DocumentReader.getChild(this.xsdNode, "xsd:complexType").orElse(null);
813 if (complexType != null && this.xsdNode.hasChildNodes())
814 {
815 findAttributesAndDescription(complexType, -1, descriptionSpecificity);
816 }
817 }
818
819
820
821
822
823
824
825
826
827 private void findAttributesAndDescription(final Node node, final int nodeSpecificity, final int descriptionSpecificity)
828 {
829 Optional<String> descript = NodeAnnotation.DESCRIPTION.get(node);
830 int updatedDescriptionSpecificity = descriptionSpecificity;
831 if (descript.isPresent() && descriptionSpecificity < nodeSpecificity)
832 {
833 updatedDescriptionSpecificity = nodeSpecificity;
834 this.description = descript.get();
835 }
836 for (int childIndex = 0; childIndex < node.getChildNodes().getLength(); childIndex++)
837 {
838 Node child = node.getChildNodes().item(childIndex);
839 if (child.getNodeName().equals("xsd:attribute") && DocumentReader.getAttribute(child, "name").isPresent())
840 {
841 this.attributeNodes.add(child);
842 this.attributeValues.add(null);
843 this.attributeValid.add(null);
844 this.attributeInvalidMessage.add(null);
845 }
846 if (child.getNodeName().equals("xsd:complexContent") || child.getNodeName().equals("xsd:simpleContent"))
847 {
848 Optional<Node> extension = DocumentReader.getChild(child, "xsd:extension");
849 if (extension.isPresent())
850 {
851 findAttributesAndDescription(extension.get(), nodeSpecificity - 1, updatedDescriptionSpecificity);
852 String base = DocumentReader.getAttribute(extension.get(), "base").get();
853 Optional<Node> baseNode = this.schema.getType(base);
854 if (baseNode.isPresent())
855 {
856 findAttributesAndDescription(baseNode.get(), nodeSpecificity - 2, updatedDescriptionSpecificity);
857 }
858 }
859 Optional<Node> restriction = DocumentReader.getChild(child, "xsd:restriction");
860 if (restriction.isPresent())
861 {
862 String base = DocumentReader.getAttribute(restriction.get(), "base").get();
863 Optional<Node> baseNode = this.schema.getType(base);
864 if (baseNode.isPresent())
865 {
866 findAttributesAndDescription(baseNode.get(), nodeSpecificity - 2, descriptionSpecificity);
867 }
868 }
869 }
870 }
871 }
872
873
874
875
876
877 public int attributeCount()
878 {
879 if (!this.active)
880 {
881 return 0;
882 }
883 assureAttributesAndDescription();
884 return this.attributeNodes.size();
885 }
886
887
888
889
890
891
892
893 public Node getAttributeNode(final int index)
894 {
895 assureAttributesAndDescription();
896 Objects.checkIndex(index, attributeCount());
897 return this.attributeNodes.get(index);
898 }
899
900
901
902
903
904
905
906 @SuppressWarnings("checkstyle:hiddenfield")
907 public void setAttributeValue(final int index, final String value)
908 {
909 Objects.checkIndex(index, attributeCount());
910 String previous = this.attributeValues.get(index);
911 if (!XsdTreeNodeUtil.valuesAreEqual(previous, value))
912 {
913 boolean isDefaultBoolean = false;
914 if ("xsd:boolean".equals(DocumentReader.getAttribute(this.attributeNodes.get(index), "type").orElse(null)))
915 {
916 isDefaultBoolean = getDefaultAttributeValue(index).equals(value);
917 }
918 this.attributeValues.set(index, (value == null || value.isEmpty() || isDefaultBoolean) ? null : value);
919 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
920 {
921 removeChildren();
922 this.children = null;
923 assureChildren();
924 }
925 invalidate();
926 fireEvent(ATTRIBUTE_CHANGED, new Object[] {this, getAttributeNameByIndex(index), previous});
927 }
928 }
929
930
931
932
933
934
935
936 public Optional<String> getDefaultAttributeValue(final int index)
937 {
938 assureAttributesAndDescription();
939 Objects.checkIndex(index, attributeCount());
940 return DocumentReader.getAttribute(this.attributeNodes.get(index), "default");
941 }
942
943
944
945
946
947
948
949 @SuppressWarnings("checkstyle:hiddenfield")
950 public void setAttributeValue(final String name, final String value)
951 {
952 setAttributeValue(getAttributeIndexByName(name), value);
953 }
954
955
956
957
958
959
960
961 public String getAttributeValue(final int index)
962 {
963 assureAttributesAndDescription();
964 Objects.checkIndex(index, attributeCount());
965 return this.attributeValues.get(index);
966 }
967
968
969
970
971
972
973
974 public String getAttributeValue(final String attribute)
975 {
976 assureAttributesAndDescription();
977 return this.attributeValues.get(getAttributeIndexByName(attribute));
978 }
979
980
981
982
983
984
985
986 public int getAttributeIndexByName(final String attribute)
987 {
988 assureAttributesAndDescription();
989 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
990 {
991 switch (attribute)
992 {
993 case "File":
994 return 0;
995 case "Fallback":
996 return 1;
997 default:
998 throw new NoSuchElementException("Attribute " + attribute + " is not in node " + getNodeName() + ".");
999 }
1000 }
1001 for (int index = 0; index < this.attributeNodes.size(); index++)
1002 {
1003 Node attr = this.attributeNodes.get(index);
1004 if (attribute.equals(DocumentReader.getAttribute(attr, "name").orElse(null)))
1005 {
1006 return index;
1007 }
1008 }
1009 throw new NoSuchElementException("Attribute " + attribute + " is not in node " + getNodeName() + ".");
1010 }
1011
1012
1013
1014
1015
1016
1017
1018 public String getAttributeNameByIndex(final int index)
1019 {
1020 Objects.checkIndex(index, attributeCount());
1021 return DocumentReader.getAttribute(this.attributeNodes.get(index), "name").get();
1022 }
1023
1024
1025
1026
1027
1028
1029 public boolean hasAttribute(final String attribute)
1030 {
1031 assureAttributesAndDescription();
1032 for (int index = 0; index < this.attributeNodes.size(); index++)
1033 {
1034 Node attr = this.attributeNodes.get(index);
1035 if (attribute.equals(DocumentReader.getAttribute(attr, "name").orElse(null)))
1036 {
1037 return true;
1038 }
1039 }
1040 return false;
1041 }
1042
1043
1044
1045
1046
1047
1048
1049 public boolean isActive()
1050 {
1051 return this.active;
1052 }
1053
1054
1055
1056
1057
1058 public void setActive()
1059 {
1060 if (!this.active)
1061 {
1062 this.active = true;
1063 if (this.deactivated)
1064 {
1065 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE) || this.isIncluded)
1066 {
1067
1068 for (XsdTreeNode child : this.children)
1069 {
1070 child.setActive();
1071 }
1072 }
1073 invalidate();
1074 fireEvent(new Event(XsdTreeNodeRoot.ACTIVATION_CHANGED, new Object[] {this, true}));
1075 return;
1076 }
1077 this.children = null;
1078 this.attributeNodes = null;
1079 this.isIdentifiable = null;
1080 this.isEditable = null;
1081 assureAttributesAndDescription();
1082 assureChildren();
1083 if (this.choice != null && this.choice.selected.equals(this))
1084 {
1085 this.choice.active = true;
1086 for (XsdTreeNode option : this.choice.options)
1087 {
1088 if (!option.equals(this))
1089 {
1090 option.setActive();
1091 }
1092 }
1093 }
1094 invalidate();
1095 fireEvent(new Event(XsdTreeNodeRoot.ACTIVATION_CHANGED, new Object[] {this, true}));
1096 }
1097 }
1098
1099
1100
1101
1102
1103 public void setInactive()
1104 {
1105 if (this.active)
1106 {
1107 this.deactivated = true;
1108 this.active = false;
1109 invalidate();
1110 fireEvent(new Event(XsdTreeNode.ACTIVATION_CHANGED, new Object[] {this, false}));
1111
1112 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE) || this.isIncluded)
1113 {
1114 for (XsdTreeNode child : this.children)
1115 {
1116 child.setInactive();
1117 }
1118 }
1119 }
1120 }
1121
1122
1123
1124
1125
1126 public boolean isIdentifiable()
1127 {
1128 if (!this.active)
1129 {
1130 return false;
1131 }
1132 if (this.isIdentifiable == null)
1133 {
1134 assureAttributesAndDescription();
1135 for (int index = 0; index < attributeCount(); index++)
1136 {
1137 Node node = this.attributeNodes.get(index);
1138 if ("Id".equals(DocumentReader.getAttribute(node, "name").orElse(null)))
1139 {
1140 this.isIdentifiable = true;
1141 this.idIndex = index;
1142 return true;
1143 }
1144 }
1145 this.isIdentifiable = false;
1146 }
1147 return this.isIdentifiable;
1148 }
1149
1150
1151
1152
1153
1154
1155 public void setId(final String id)
1156 {
1157 Throw.when(!isIdentifiable(), NoSuchElementException.class, "Node is non-identifiable.");
1158 setAttributeValue(this.idIndex, id);
1159 }
1160
1161
1162
1163
1164
1165
1166 public String getId()
1167 {
1168 Throw.when(!isIdentifiable(), NoSuchElementException.class, "Getting id from non-identifiable node.");
1169 return this.attributeValues.get(this.idIndex);
1170 }
1171
1172
1173
1174
1175
1176
1177 public boolean isEditable()
1178 {
1179 if (!this.active)
1180 {
1181 return false;
1182 }
1183 if (this.isEditable == null)
1184 {
1185 this.isEditable = XsdTreeNodeUtil.isEditable(this.xsdNode, this.schema);
1186 }
1187 return this.isEditable;
1188 }
1189
1190
1191
1192
1193
1194 public void setValue(final String value)
1195 {
1196 Throw.when(!isEditable(), IllegalStateException.class,
1197 "Node is not an xsd:simpleType or xsd:complexType with xsd:simpleContent, hence no value is allowed.");
1198 String previous = this.value;
1199 if (!XsdTreeNodeUtil.valuesAreEqual(previous, value))
1200 {
1201 this.value = (value == null || value.isEmpty()) ? null : value;
1202 invalidate();
1203 fireEvent(new Event(VALUE_CHANGED, new Object[] {this, previous}));
1204 }
1205 }
1206
1207
1208
1209
1210
1211 public String getValue()
1212 {
1213 return this.value;
1214 }
1215
1216
1217
1218
1219
1220 public boolean isIncluded()
1221 {
1222 return this.isIncluded;
1223 }
1224
1225
1226
1227
1228
1229 public boolean isAddable()
1230 {
1231 return isActive() && (this.maxOccurs == -1 || (this.parent != null && siblingPositions().size() < this.maxOccurs));
1232 }
1233
1234
1235
1236
1237
1238
1239 public XsdTreeNode add()
1240 {
1241 if (this.choice != null)
1242 {
1243 int index = this.parent.children.indexOf(this) + 1;
1244 XsdTreeNode node = new XsdTreeNode(this.choice.parent, this.choice.xsdNode, this.choice.hiddenNodes,
1245 this.choice.referringXsdNode);
1246 getRoot().fireEvent(XsdTreeNodeRoot.NODE_CREATED,
1247 new Object[] {node, node.parent, node.parent.children.indexOf(this) + 1});
1248 node.createOptions();
1249 int indexSelected = this.choice.options.indexOf(this.choice.selected);
1250 XsdTreeNode selectedOption = node.options.get(indexSelected);
1251 node.choice.setOption(selectedOption);
1252 this.parent.children.remove(selectedOption);
1253 this.parent.children.add(index, selectedOption);
1254 node.options.get(indexSelected).setActive();
1255 return selectedOption;
1256 }
1257 else
1258 {
1259 int index = this.parent.children.indexOf(this) + 1;
1260 XsdTreeNode node = new XsdTreeNode(this.parent, this.xsdNode, this.hiddenNodes, this.referringXsdNode);
1261 this.parent.children.add(index, node);
1262 node.active = true;
1263 getRoot().fireEvent(XsdTreeNodeRoot.NODE_CREATED,
1264 new Object[] {node, node.parent, node.parent.children.indexOf(node)});
1265 return node;
1266 }
1267 }
1268
1269
1270
1271
1272
1273 public XsdTreeNode duplicate()
1274 {
1275 return duplicate(this.parent);
1276 }
1277
1278
1279
1280
1281
1282
1283 public XsdTreeNode duplicate(final XsdTreeNode newParent)
1284 {
1285
1286 XsdTreeNode copyNode = emptyCopy(newParent);
1287 copyNode.active = this.active;
1288 copyInto(copyNode);
1289 copyNode.invalidate();
1290 getRoot().fireEvent(XsdTreeNodeRoot.NODE_CREATED,
1291 new Object[] {copyNode, newParent, newParent.children.indexOf(copyNode)});
1292 invalidate();
1293 return copyNode;
1294 }
1295
1296
1297
1298
1299
1300 public XsdTreeNode emptyCopy()
1301 {
1302 return emptyCopy(this.parent);
1303 }
1304
1305
1306
1307
1308
1309
1310 private XsdTreeNode emptyCopy(final XsdTreeNode newParent)
1311 {
1312 int indexOfNode = this.parent.children.indexOf(this);
1313 if (newParent.equals(this.parent))
1314 {
1315 indexOfNode++;
1316 }
1317 XsdTreeNode copyNode = new XsdTreeNode(newParent, this.xsdNode, this.hiddenNodes, this.referringXsdNode);
1318 if (newParent.children == null)
1319 {
1320 newParent.children = new ArrayList<>();
1321 }
1322 newParent.children.add(indexOfNode, copyNode);
1323 copyNode.parent = newParent;
1324 return copyNode;
1325 }
1326
1327
1328
1329
1330
1331
1332
1333 public boolean canContain(final XsdTreeNode copied)
1334 {
1335 return this.xsdNode == copied.xsdNode || (this.referringXsdNode != null && copied.referringXsdNode != null
1336 && DocumentReader.getAttribute(this.referringXsdNode, "type").isPresent()
1337 && DocumentReader.getAttribute(this.referringXsdNode, "type").get()
1338 .equals(DocumentReader.getAttribute(copied.referringXsdNode, "type").orElse(null)));
1339 }
1340
1341
1342
1343
1344
1345 public void copyInto(final XsdTreeNode copyNode)
1346 {
1347 if (this.equals(copyNode))
1348 {
1349 return;
1350 }
1351 copyNode.value = this.value;
1352 copyNode.isIncluded = this.isIncluded;
1353
1354 if (this.choice != null)
1355 {
1356 XsdTreeNode choiceNode = new XsdTreeNode(copyNode.parent, this.choice.xsdNode, this.choice.hiddenNodes,
1357 this.choice.referringXsdNode);
1358 choiceNode.choice = choiceNode;
1359
1360 int selectedIndex = this.choice.options.indexOf(this);
1361 choiceNode.options = new ArrayList<>();
1362 XsdTreeNodeUtil.addChildren(this.choice.xsdNode, copyNode.parent, choiceNode.options, this.choice.hiddenNodes,
1363 this.schema, false, selectedIndex);
1364 choiceNode.options.add(selectedIndex, copyNode);
1365 choiceNode.selected = choiceNode.options.get(selectedIndex);
1366 if (this.choice.getNodeName().equals("xsd:all"))
1367 {
1368 XsdTreeNodeUtil.addXsdAllValidator(choiceNode, choiceNode);
1369 }
1370 for (int index = 0; index < choiceNode.options.size(); index++)
1371 {
1372 XsdTreeNode option = choiceNode.options.get(index);
1373 if (this.choice.getNodeName().equals("xsd:all"))
1374 {
1375 XsdTreeNodeUtil.addXsdAllValidator(choiceNode, option);
1376 }
1377 option.minOccurs = choiceNode.minOccurs;
1378 option.maxOccurs = choiceNode.maxOccurs;
1379 if (choiceNode.minOccurs > 0)
1380 {
1381 option.setActive();
1382 }
1383 option.active = this.choice.options.get(index).active;
1384 option.choice = choiceNode;
1385 }
1386 }
1387
1388 copyNode.assureAttributesAndDescription();
1389 for (int index = 0; index < attributeCount(); index++)
1390 {
1391 copyNode.attributeValues.set(index, this.attributeValues.get(index));
1392 }
1393
1394 if (copyNode.children != null)
1395 {
1396 for (int index = 0; index < copyNode.getChildCount(); index++)
1397 {
1398 XsdTreeNode child = copyNode.getChild(index);
1399 copyNode.children.remove(index);
1400 child.parent = null;
1401 getRoot().fireEvent(XsdTreeNodeRoot.NODE_REMOVED, new Object[] {child, copyNode, index});
1402 }
1403 }
1404 if (this.children != null)
1405 {
1406 for (int index = 0; index < this.children.size(); index++)
1407 {
1408 this.children.get(index).duplicate(copyNode);
1409 }
1410 }
1411 }
1412
1413
1414
1415
1416
1417
1418 public boolean isRemovable()
1419 {
1420 return isActive() && siblingPositions().size() > this.minOccurs;
1421 }
1422
1423
1424
1425
1426
1427
1428 public final void remove()
1429 {
1430 int numberOfTypeOrChoiceInParent = siblingPositions().size();
1431 if (this.minOccurs == 0 && numberOfTypeOrChoiceInParent == 1 && !this.isIncluded)
1432 {
1433 setInactive();
1434 return;
1435 }
1436 if (this.choice != null && this.choice.selected.equals(this))
1437 {
1438 for (XsdTreeNode option : this.choice.options)
1439 {
1440 if (!this.choice.selected.equals(this))
1441 {
1442 option.remove();
1443 }
1444 }
1445 }
1446 removeChildren();
1447 XsdTreeNode parentNode = this.parent;
1448 int index = this.parent.children.indexOf(this);
1449 this.parent.children.remove(this);
1450 XsdTreeNodeRoot root = getRoot();
1451 this.parent = null;
1452 root.fireEvent(XsdTreeNodeRoot.NODE_REMOVED, new Object[] {this, parentNode, index});
1453 }
1454
1455
1456
1457
1458 private void removeChildren()
1459 {
1460 if (this.children != null)
1461 {
1462
1463 List<XsdTreeNode> childs = new ArrayList<>(this.children);
1464 for (XsdTreeNode child : childs)
1465 {
1466 child.remove();
1467 }
1468 }
1469 }
1470
1471
1472
1473
1474
1475 public boolean canMoveUp()
1476 {
1477 List<Integer> positions = siblingPositions();
1478 return !positions.isEmpty() && this.parent.children.indexOf(this) > positions.get(0);
1479 }
1480
1481
1482
1483
1484
1485 public boolean canMoveDown()
1486 {
1487 List<Integer> positions = siblingPositions();
1488
1489 return !positions.isEmpty() && this.parent.children.indexOf(this) < positions.get(positions.size() - 1);
1490 }
1491
1492
1493
1494
1495
1496
1497
1498
1499 private List<Integer> siblingPositions()
1500 {
1501 List<Integer> siblingPositions = new ArrayList<>();
1502 if (this.parent == null)
1503 {
1504 return siblingPositions;
1505 }
1506 if (this.choice != null)
1507 {
1508 for (int index = 0; index < this.parent.children.size(); index++)
1509 {
1510 for (XsdTreeNode option : this.choice.options)
1511 {
1512 if (XsdTreeNodeUtil.haveSameType(option, this.parent.children.get(index)))
1513 {
1514 siblingPositions.add(index);
1515 break;
1516 }
1517 }
1518 }
1519 }
1520 else
1521 {
1522 for (int index = 0; index < this.parent.children.size(); index++)
1523 {
1524 if (XsdTreeNodeUtil.haveSameType(this, this.parent.children.get(index)))
1525 {
1526 siblingPositions.add(index);
1527 }
1528 }
1529 }
1530 return siblingPositions;
1531 }
1532
1533
1534
1535
1536
1537
1538 public void move(final int down)
1539 {
1540 int oldIndex = this.parent.children.indexOf(this);
1541 this.parent.children.remove(this);
1542 int newIndex = oldIndex + down;
1543 this.parent.children.add(newIndex, this);
1544 fireEvent(MOVED, new Object[] {this, oldIndex, newIndex});
1545 }
1546
1547
1548
1549
1550
1551 public int minOccurs()
1552 {
1553 return this.minOccurs;
1554 }
1555
1556
1557
1558
1559
1560
1561 public int maxOccurs()
1562 {
1563 return this.maxOccurs;
1564 }
1565
1566
1567
1568
1569
1570
1571 public String getAttributeBaseType(final int index)
1572 {
1573 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
1574 {
1575 return "xsd:anyURI";
1576 }
1577 return ValueValidator.getBaseType(this.attributeNodes.get(index), this.schema);
1578 }
1579
1580
1581
1582
1583
1584
1585 public boolean isType(final String path)
1586 {
1587 boolean isType = getPathString().endsWith("." + path);
1588 if (isType)
1589 {
1590 return isType;
1591 }
1592 int dot = path.lastIndexOf(".");
1593 if (dot > -1)
1594 {
1595 if (this.parent == null)
1596 {
1597 return false;
1598 }
1599 isType = isType(path.substring(dot + 1)) && this.parent.isType(path.substring(0, dot));
1600 if (isType)
1601 {
1602 return isType;
1603 }
1604 }
1605 return this.schema.isType(this.xsdNode, path);
1606 }
1607
1608
1609
1610
1611
1612
1613
1614 public boolean hasExpression()
1615 {
1616 if (!this.active)
1617 {
1618 return false;
1619 }
1620 if (valueIsExpression())
1621 {
1622 return true;
1623 }
1624 for (int index = 0; index < attributeCount(); index++)
1625 {
1626 if (attributeIsExpression(index))
1627 {
1628 return true;
1629 }
1630 }
1631 if (this.children != null)
1632 {
1633 for (XsdTreeNode child : this.children)
1634 {
1635 if (child.hasExpression())
1636 {
1637 return true;
1638 }
1639 }
1640 }
1641 return false;
1642 }
1643
1644
1645
1646
1647
1648 public boolean valueIsExpression()
1649 {
1650 return this.value != null && this.value.startsWith("{") && this.value.endsWith("}");
1651 }
1652
1653
1654
1655
1656
1657
1658 public boolean idIsExpression()
1659 {
1660 Throw.when(!isIdentifiable(), NoSuchElementException.class, "Node is non-identifiable.");
1661 return attributeIsExpression(getAttributeIndexByName("Id"));
1662 }
1663
1664
1665
1666
1667
1668
1669
1670 public boolean attributeIsExpression(final int index)
1671 {
1672 Objects.checkIndex(index, attributeCount());
1673 String attributeValue = this.attributeValues.get(index);
1674 return attributeValue != null && attributeValue.startsWith("{") && attributeValue.endsWith("}");
1675 }
1676
1677
1678
1679
1680
1681
1682
1683
1684 public void addConsumer(final String menuItem, final Consumer<XsdTreeNode> consumer)
1685 {
1686 this.consumers.put(menuItem, consumer);
1687 }
1688
1689
1690
1691
1692
1693 public boolean hasConsumer()
1694 {
1695 return !this.consumers.isEmpty();
1696 }
1697
1698
1699
1700
1701
1702 public Set<String> getConsumerMenuItems()
1703 {
1704 return this.consumers.keySet();
1705 }
1706
1707
1708
1709
1710
1711
1712 public void consume(final String menuItem)
1713 {
1714 Throw.when(!this.consumers.containsKey(menuItem), IllegalArgumentException.class, "Unable to consume node for %s.",
1715 menuItem);
1716 this.consumers.get(menuItem).accept(this);
1717 }
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727 public String getNodeName()
1728 {
1729 Node node = getRelevantNode();
1730 Optional<String> ref = DocumentReader.getAttribute(node, "ref");
1731 if (ref.isPresent())
1732 {
1733 return ref.get().replace("ots:", "");
1734 }
1735 Optional<String> name = DocumentReader.getAttribute(node, "name");
1736 if (name.isPresent())
1737 {
1738 return name.get().replace("ots:", "");
1739 }
1740 return node.getNodeName().replace("ots:", "");
1741 }
1742
1743
1744
1745
1746
1747 public Optional<String> getDescription()
1748 {
1749 assureAttributesAndDescription();
1750 if (this.description == null && isChoice())
1751 {
1752 this.choice.assureAttributesAndDescription();
1753 return Optional.ofNullable(this.choice.description);
1754 }
1755 return Optional.ofNullable(this.description);
1756 }
1757
1758
1759
1760
1761
1762 public String getShortString()
1763 {
1764 if (this.options != null)
1765 {
1766
1767 return this.options.toString().toLowerCase();
1768 }
1769 if (this.xsdNode.getNodeName().equals("xsd:sequence"))
1770 {
1771
1772 StringBuilder stringBuilder = new StringBuilder("{");
1773 Node relevantNode = getRelevantNode();
1774 Optional<String> annotation = NodeAnnotation.APPINFO_NAME.get(relevantNode);
1775 if (annotation.isPresent())
1776 {
1777 stringBuilder.append(annotation);
1778 }
1779 else
1780 {
1781
1782 Set<String> coveredTypes = new LinkedHashSet<>();
1783 String separator = "";
1784 boolean preActive = this.active;
1785 this.active = true;
1786 assureChildren();
1787 if (getChildCount() == 1)
1788 {
1789 stringBuilder.append(getChild(0).getShortString());
1790 }
1791 else
1792 {
1793 for (XsdTreeNode child : this.children)
1794 {
1795 if (!coveredTypes.contains(child.getPathString()) || child.xsdNode.getNodeName().equals("xsd:sequence")
1796 || child.xsdNode.getNodeName().equals("xsd:choice")
1797 || child.xsdNode.getNodeName().equals("xsd:all"))
1798 {
1799 stringBuilder.append(separator).append(child.getShortString());
1800 separator = "\u2009|\u2009";
1801 coveredTypes.add(child.getPathString());
1802 }
1803 }
1804 }
1805 this.active = preActive;
1806 }
1807 stringBuilder.append("}");
1808 if (stringBuilder.length() > MAX_OPTIONNAME_LENGTH)
1809 {
1810 return stringBuilder.substring(0, MAX_OPTIONNAME_LENGTH - 3) + "..}";
1811 }
1812 return stringBuilder.toString();
1813 }
1814 if (this.xsdNode.getNodeName().equals("xi:include"))
1815 {
1816 return "Include";
1817 }
1818 return OtsAnimationPanel.separatedName(getNodeName());
1819 }
1820
1821
1822
1823
1824
1825
1826
1827
1828 @SuppressWarnings("hiddenfield")
1829 public void setStringFunction(final Function<XsdTreeNode, String> stringFunction, final boolean overwrite)
1830 {
1831 if (this.stringFunction == null || overwrite)
1832 {
1833 this.stringFunction = stringFunction;
1834 }
1835 }
1836
1837
1838
1839
1840
1841
1842 public String getPathString()
1843 {
1844 return this.pathString;
1845 }
1846
1847
1848
1849
1850
1851 @Override
1852 public String toString()
1853 {
1854 StringBuilder string = new StringBuilder(getShortString());
1855 if (!this.active)
1856 {
1857 return string.toString();
1858 }
1859 if (isIdentifiable() && getId() != null && !getId().isEmpty())
1860 {
1861 string.append("\u2009").append(getId());
1862 }
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874 if (this.stringFunction != null)
1875 {
1876 string.append("\u2009(").append(this.stringFunction.apply(this)).append(")");
1877 }
1878 return string.toString();
1879 }
1880
1881
1882
1883
1884
1885
1886
1887
1888 public List<String> getIdRestrictions()
1889 {
1890 return getAttributeRestrictions(this.idIndex);
1891 }
1892
1893
1894
1895
1896
1897 public List<String> getValueRestrictions()
1898 {
1899 if ("ots:boolean".equals(DocumentReader.getAttribute(getRelevantNode(), "type").orElse(null)))
1900 {
1901 return List.of("true", "false");
1902 }
1903 List<String> valueOptions = getOptionsFromValidators(this.valueValidators);
1904 if (!valueOptions.isEmpty())
1905 {
1906 return valueOptions;
1907 }
1908 return XsdTreeNodeUtil.getOptionsFromRestrictions(ValueValidator.getRestrictions(this.xsdNode, this.schema));
1909 }
1910
1911
1912
1913
1914
1915
1916
1917 public List<String> getAttributeRestrictions(final int index)
1918 {
1919 Objects.checkIndex(index, attributeCount());
1920 if ("ots:boolean".equals(DocumentReader.getAttribute(this.attributeNodes.get(index), "type").orElse(null)))
1921 {
1922 return List.of("true", "false");
1923 }
1924 String field = getAttributeNameByIndex(index);
1925
1926 Map<ValueValidator, Object> map = new LinkedHashMap<>();
1927 this.attributeValidators.computeIfAbsent(field, (f) -> new TreeSet<>())
1928 .forEach((v) -> map.put(v, this.attributeValidatorFields.get(field).get(v)));
1929 List<String> valueOptions = getOptionsFromValidators(map);
1930
1931
1932 if (!valueOptions.isEmpty() || this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
1933 {
1934 return valueOptions;
1935 }
1936 return XsdTreeNodeUtil
1937 .getOptionsFromRestrictions(ValueValidator.getRestrictions(this.attributeNodes.get(index), this.schema));
1938 }
1939
1940
1941
1942
1943
1944
1945 private List<String> getOptionsFromValidators(final Map<ValueValidator, Object> validators)
1946 {
1947 List<String> intersection = null;
1948 for (Entry<ValueValidator, Object> entry : validators.entrySet())
1949 {
1950 ValueValidator validator = entry.getKey();
1951 Optional<List<String>> valueOptions = validator.getOptions(this, entry.getValue());
1952 if (valueOptions.isPresent())
1953 {
1954 intersection = intersection == null ? valueOptions.get()
1955 : intersection.stream().filter(valueOptions.get()::contains).collect(Collectors.toList());
1956 }
1957 }
1958 return intersection == null ? Collections.emptyList() : intersection;
1959 }
1960
1961
1962
1963
1964
1965
1966
1967 public Optional<XsdTreeNode> getCoupledNodeValue()
1968 {
1969 return Optional.ofNullable(getCoupledNode(this.valueValidators.keySet()));
1970 }
1971
1972
1973
1974
1975
1976
1977
1978 public Optional<XsdTreeNode> getCoupledNodeAttribute(final int index)
1979 {
1980 Objects.checkIndex(index, attributeCount());
1981 return getCoupledNodeAttribute(getAttributeNameByIndex(index));
1982 }
1983
1984
1985
1986
1987
1988
1989 public Optional<XsdTreeNode> getCoupledNodeAttribute(final String attribute)
1990 {
1991 if (this.attributeValidators.containsKey(attribute))
1992 {
1993 return Optional.ofNullable(getCoupledNode(this.attributeValidators.get(attribute)));
1994 }
1995 return Optional.empty();
1996 }
1997
1998
1999
2000
2001
2002
2003 private XsdTreeNode getCoupledNode(final Set<ValueValidator> validators)
2004 {
2005 for (ValueValidator validator : validators)
2006 {
2007 if (validator instanceof CoupledValidator coupledValidator)
2008 {
2009 coupledValidator.validate(this);
2010 return coupledValidator.getCoupledNode(this);
2011 }
2012 }
2013 return null;
2014 }
2015
2016
2017
2018
2019
2020
2021
2022
2023 public boolean isSelfValid()
2024 {
2025 if (this.isSelfValid == null)
2026 {
2027 if (!this.active)
2028 {
2029 this.isSelfValid = true;
2030 }
2031 else if (reportInvalidNode().isPresent() || reportInvalidValue().isPresent())
2032 {
2033 this.isSelfValid = false;
2034 }
2035 else
2036 {
2037 boolean attributesValid = true;
2038 for (int index = 0; index < attributeCount(); index++)
2039 {
2040 if (reportInvalidAttributeValue(index).isPresent())
2041 {
2042 attributesValid = false;
2043 break;
2044 }
2045 }
2046 this.isSelfValid = attributesValid;
2047 }
2048 }
2049 return this.isSelfValid;
2050 }
2051
2052
2053
2054
2055
2056
2057 public boolean isValid()
2058 {
2059 if (this.isValid == null)
2060 {
2061 if (!isActive())
2062 {
2063 this.isValid = true;
2064 }
2065 else if (!isSelfValid())
2066 {
2067 this.isValid = false;
2068 }
2069 else
2070 {
2071 boolean childrenValid = true;
2072 if (this.children != null)
2073 {
2074 for (XsdTreeNode child : this.children)
2075 {
2076 if (!child.isValid())
2077 {
2078 childrenValid = false;
2079 }
2080 }
2081 }
2082 this.isValid = childrenValid;
2083 }
2084 }
2085 return this.isValid;
2086 }
2087
2088
2089
2090
2091 public void invalidate()
2092 {
2093 this.isSelfValid = null;
2094 this.isValid = null;
2095 if (this.parent != null)
2096 {
2097 this.parent.invalidate();
2098 }
2099 this.valueValid = null;
2100 this.valueInvalidMessage = null;
2101 this.nodeValid = null;
2102 this.nodeInvalidMessage = null;
2103 assureAttributesAndDescription();
2104 Collections.fill(this.attributeValid, null);
2105 Collections.fill(this.attributeInvalidMessage, null);
2106 }
2107
2108
2109
2110
2111 void invalidateAll()
2112 {
2113 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
2114 {
2115 removeChildren();
2116 this.children = null;
2117 assureChildren();
2118 }
2119 else if (this.children != null)
2120 {
2121 for (XsdTreeNode child : this.children)
2122 {
2123 child.invalidateAll();
2124 }
2125 }
2126 invalidate();
2127 }
2128
2129
2130
2131
2132
2133
2134 public void addNodeValidator(final Function<XsdTreeNode, String> validator)
2135 {
2136 this.nodeValidators.add(validator);
2137 }
2138
2139
2140
2141
2142
2143
2144
2145
2146 public void addValueValidator(final ValueValidator validator, final Object field)
2147 {
2148 Throw.when(validator instanceof CoupledValidator && coupledValidatorExists(this.valueValidators.keySet(), validator),
2149 IllegalStateException.class, "Adding %s to the node value of %s but a CoupledValidator already exists.",
2150 validator.getClass().getSimpleName(), getPathString());
2151 this.valueValidators.put(validator, field);
2152 }
2153
2154
2155
2156
2157
2158
2159
2160 public void addAttributeValidator(final String attribute, final ValueValidator validator)
2161 {
2162 addAttributeValidator(attribute, validator, null);
2163 }
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173 public void addAttributeValidator(final String attribute, final ValueValidator validator, final Object field)
2174 {
2175 Throw.when(
2176 validator instanceof CoupledValidator
2177 && coupledValidatorExists(this.attributeValidators.get(attribute), validator),
2178 IllegalStateException.class, "Adding %s to the attribute %s in %s but a CoupledValidator already exists.",
2179 validator.getClass().getSimpleName(), attribute, getPathString());
2180 this.attributeValidators.computeIfAbsent(attribute, (key) -> new TreeSet<>()).add(validator);
2181 this.attributeValidatorFields.computeIfAbsent(attribute, (key) -> new LinkedHashMap<>()).put(validator, field);
2182 }
2183
2184
2185
2186
2187
2188
2189
2190 private boolean coupledValidatorExists(final Set<ValueValidator> validators, final ValueValidator validator)
2191 {
2192 return validators != null
2193 && validators.stream().filter((v) -> v instanceof CoupledValidator && !v.equals(validator)).count() > 0;
2194 }
2195
2196
2197
2198
2199
2200
2201 public Optional<String> reportInvalidNode()
2202 {
2203 if (this.nodeValid == null)
2204 {
2205 for (Function<XsdTreeNode, String> validator : this.nodeValidators)
2206 {
2207 String message = validator.apply(this);
2208 if (message != null)
2209 {
2210 this.nodeInvalidMessage = message;
2211 this.nodeValid = false;
2212 return Optional.of(message);
2213 }
2214 }
2215 this.nodeValid = true;
2216 }
2217 return Optional.ofNullable(this.nodeInvalidMessage);
2218 }
2219
2220
2221
2222
2223
2224 public Optional<String> reportInvalidId()
2225 {
2226 if (!isActive())
2227 {
2228 return Optional.empty();
2229 }
2230 return isIdentifiable() ? reportInvalidAttributeValue(getAttributeIndexByName("Id")) : Optional.empty();
2231 }
2232
2233
2234
2235
2236
2237 public Optional<String> reportInvalidValue()
2238 {
2239 if (this.valueValid == null)
2240 {
2241 if (!isEditable() || !isActive())
2242 {
2243 this.valueInvalidMessage = null;
2244 this.valueValid = true;
2245 return Optional.empty();
2246 }
2247 if (this.value != null && !this.value.isEmpty())
2248 {
2249 for (ValueValidator validator : this.valueValidators.keySet())
2250 {
2251 Optional<String> message = validator.validate(this);
2252 if (message.isPresent())
2253 {
2254 this.valueInvalidMessage = message.get();
2255 this.valueValid = false;
2256 return message;
2257 }
2258 }
2259 }
2260 Optional<String> message = ValueValidator.reportInvalidValue(this.xsdNode, this.value, this.schema);
2261 this.valueInvalidMessage = message.orElse(null);
2262 this.valueValid = message.isEmpty();
2263 }
2264 return Optional.ofNullable(this.valueInvalidMessage);
2265 }
2266
2267
2268
2269
2270
2271
2272
2273 public Optional<String> reportInvalidAttributeValue(final int index)
2274 {
2275 Objects.checkIndex(index, attributeCount());
2276 if (this.attributeValid.get(index) == null)
2277 {
2278 if (!isActive())
2279 {
2280 this.attributeInvalidMessage.set(index, null);
2281 this.attributeValid.set(index, true);
2282 return Optional.empty();
2283 }
2284 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
2285 {
2286 XsdTreeNode root = getPath().get(0);
2287 if (root instanceof XsdTreeNodeRoot)
2288 {
2289 Optional<String> message = ValueValidator.reportInvalidInclude(((XsdTreeNodeRoot) root).getDirectory(),
2290 this.attributeValues.get(0), this.attributeValues.get(1));
2291 this.attributeInvalidMessage.set(index, message.orElse(null));
2292 this.attributeValid.set(index, message.isEmpty());
2293 return message;
2294 }
2295 else
2296 {
2297
2298 this.attributeInvalidMessage.set(index, null);
2299 this.attributeValid.set(index, true);
2300 return Optional.empty();
2301 }
2302 }
2303 String attribute = getAttributeNameByIndex(index);
2304 for (ValueValidator validator : this.attributeValidators.computeIfAbsent(attribute, (key) -> new TreeSet<>()))
2305 {
2306 Optional<String> message = validator.validate(this);
2307 if (message.isPresent())
2308 {
2309 this.attributeInvalidMessage.set(index, message.orElse(null));
2310 this.attributeValid.set(index, false);
2311 return message;
2312 }
2313 }
2314 Optional<String> message =
2315 ValueValidator.reportInvalidAttributeValue(getAttributeNode(index), getAttributeValue(index), this.schema);
2316 this.attributeInvalidMessage.set(index, message.orElse(null));
2317 this.attributeValid.set(index, message.isEmpty());
2318 return message;
2319 }
2320 return Optional.ofNullable(this.attributeInvalidMessage.get(index));
2321 }
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335 public void saveXmlNodes(final Document document, final Node xmlParent)
2336 {
2337 if (!this.active)
2338 {
2339 return;
2340 }
2341
2342
2343 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
2344 {
2345 Element element = document.createElement(getNodeName());
2346 xmlParent.appendChild(element);
2347 if (this.attributeValues != null && this.attributeValues.get(0) != null)
2348 {
2349 element.setAttribute("href", this.attributeValues.get(0));
2350 if (this.attributeValues.get(1) != null)
2351 {
2352 Element fallback = document.createElement("xi:fallback");
2353 element.appendChild(fallback);
2354 Element include = document.createElement("xi:include");
2355 fallback.appendChild(include);
2356 include.setAttribute("href", this.attributeValues.get(1));
2357 }
2358 }
2359 return;
2360 }
2361
2362
2363 if (this.xsdNode.getNodeName().equals("xsd:sequence"))
2364 {
2365 for (int index = 0; index < this.getChildCount(); index++)
2366 {
2367 this.children.get(index).saveXmlNodes(document, xmlParent);
2368 }
2369 return;
2370 }
2371
2372 Element element = document.createElement("ots:" + getNodeName());
2373 xmlParent.appendChild(element);
2374
2375 if (this.value != null && !this.value.isEmpty())
2376 {
2377 element.setTextContent(this.value);
2378 }
2379
2380 for (int index = 0; index < attributeCount(); index++)
2381 {
2382 String attributeValue = this.attributeValues.get(index);
2383 if (attributeValue != null && !attributeValue.isEmpty())
2384 {
2385 element.setAttribute(getAttributeNameByIndex(index), attributeValue);
2386 }
2387 }
2388
2389 for (int index = 0; index < this.getChildCount(); index++)
2390 {
2391 if (!this.children.get(index).isIncluded)
2392 {
2393 this.children.get(index).saveXmlNodes(document, element);
2394 }
2395 }
2396 }
2397
2398
2399
2400
2401
2402
2403 public void loadXmlNodes(final Node nodeXml)
2404 {
2405 setActive();
2406
2407 if (this.xsdNode.equals(XiIncludeNode.XI_INCLUDE))
2408 {
2409 assureAttributesAndDescription();
2410 setAttributeValue(0, DocumentReader.getAttribute(nodeXml, "href").orElse(null));
2411 Optional<Node> fallback = DocumentReader.getChild(nodeXml, "xi:fallback");
2412 if (fallback.isPresent())
2413 {
2414 Optional<Node> fallbackInclude = DocumentReader.getChild(fallback.get(), "xi:include");
2415 if (fallbackInclude.isPresent())
2416 {
2417 setAttributeValue(1, DocumentReader.getAttribute(fallbackInclude.get(), "href").orElse(null));
2418 }
2419 }
2420 return;
2421 }
2422
2423
2424 String candidateValue = "";
2425 if (nodeXml.getChildNodes() != null)
2426 {
2427 for (int indexXml = 0; indexXml < nodeXml.getChildNodes().getLength(); indexXml++)
2428 {
2429 if (nodeXml.getChildNodes().item(indexXml).getNodeName().equals("#text"))
2430 {
2431 String nodeValue = nodeXml.getChildNodes().item(indexXml).getNodeValue();
2432 if (!nodeValue.isBlank())
2433 {
2434 candidateValue += nodeValue;
2435 }
2436 }
2437 }
2438 }
2439 if (!candidateValue.isEmpty())
2440 {
2441 String previous = this.value;
2442 this.value = candidateValue;
2443 fireEvent(new Event(VALUE_CHANGED, new Object[] {this, previous}));
2444 }
2445
2446
2447 assureAttributesAndDescription();
2448 if (nodeXml.getAttributes() != null)
2449 {
2450 for (int index = 0; index < nodeXml.getAttributes().getLength(); index++)
2451 {
2452 Node attributeNode = nodeXml.getAttributes().item(index);
2453 switch (attributeNode.getNodeName())
2454 {
2455 case "xmlns:ots":
2456 case "xmlns:xi":
2457 case "xmlns:xsi":
2458 case "xsi:schemaLocation":
2459 continue;
2460 default:
2461 try
2462 {
2463 setAttributeValue(attributeNode.getNodeName(), attributeNode.getNodeValue());
2464 }
2465 catch (NoSuchElementException e)
2466 {
2467 Logger.ots().warn("Unable to load attribute {}=\"{}\" in {}.", attributeNode.getNodeName(),
2468 attributeNode.getNodeValue(), getShortString());
2469 }
2470 }
2471 }
2472 }
2473
2474
2475 assureChildren();
2476 if (nodeXml.getChildNodes() != null)
2477 {
2478 loadChildren(new LoadingIndices(0, 0), nodeXml.getChildNodes(), false);
2479
2480
2481 if (this.isIncluded)
2482 {
2483 int index = 0;
2484 while (index < this.children.size())
2485 {
2486 boolean relevantForAny = false;
2487 for (int indexXml = 0; indexXml < nodeXml.getChildNodes().getLength(); indexXml++)
2488 {
2489 String xmlName = nodeXml.getChildNodes().item(indexXml).getNodeName().replace("ots:", "");
2490 if (this.children.get(index).isRelevantNode(xmlName))
2491 {
2492 relevantForAny = true;
2493 break;
2494 }
2495 }
2496 if (!relevantForAny)
2497 {
2498
2499 this.children.remove(index);
2500 }
2501 else
2502 {
2503 index++;
2504 }
2505 }
2506 }
2507
2508 }
2509 invalidate();
2510 }
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549 protected void loadChildren(final LoadingIndices indices, final NodeList childrenXml, final boolean loadingSubSequence)
2550 {
2551 List<XsdTreeNode> loadedChildren = new ArrayList<>();
2552 int passes = 0;
2553 int maxPasses = 1;
2554 int loadedDuringPass = 0;
2555 Optional<Node> complexType = DocumentReader.getChild(this.xsdNode, "xsd:complexType");
2556 if (complexType.isPresent())
2557 {
2558 Optional<Node> sequence = DocumentReader.getChild(complexType.get(), "xsd:sequence");
2559 if (sequence.isPresent())
2560 {
2561 maxPasses = Occurs.MAX.get(sequence.get());
2562 }
2563 }
2564
2565 int xmlNodeIndex = indices.getXmlNode();
2566 int xsdTreeNodeIndex = indices.getXsdTreeNode();
2567 while (xmlNodeIndex < childrenXml.getLength())
2568 {
2569 Node childNodeXml = childrenXml.item(xmlNodeIndex);
2570 if (childNodeXml.getNodeName().equals("#text"))
2571 {
2572 xmlNodeIndex++;
2573 continue;
2574 }
2575
2576
2577 String nameXml = childNodeXml.getNodeName().replace("ots:", "");
2578 if (xsdTreeNodeIndex > 0 && this.children.get(xsdTreeNodeIndex - 1).isRelevantNode(nameXml))
2579 {
2580 if (xsdTreeNodeIndex >= this.children.size() || !this.children.get(xsdTreeNodeIndex).isRelevantNode(nameXml))
2581 {
2582 this.children.get(xsdTreeNodeIndex - 1).add();
2583 }
2584 }
2585 else
2586 {
2587 while (xsdTreeNodeIndex < this.children.size() && (!this.children.get(xsdTreeNodeIndex).isRelevantNode(nameXml)
2588 || loadedChildren.contains(this.children.get(xsdTreeNodeIndex))))
2589 {
2590 xsdTreeNodeIndex++;
2591 }
2592 if (xsdTreeNodeIndex >= this.children.size())
2593 {
2594 if (loadedDuringPass == 0)
2595 {
2596 Logger.ots().warn("Failing to load {}, it is not a valid node.", nameXml);
2597 xmlNodeIndex++;
2598 xsdTreeNodeIndex = 0;
2599 continue;
2600 }
2601 else
2602 {
2603 passes++;
2604 if (passes >= maxPasses)
2605 {
2606 if (!loadingSubSequence)
2607 {
2608 Logger.ots().warn("Failing to load {}, maximum number of passes reached.", nameXml);
2609 }
2610 indices.setXmlNode(xmlNodeIndex);
2611 return;
2612 }
2613 }
2614 xsdTreeNodeIndex = 0;
2615 loadedDuringPass = 0;
2616 continue;
2617 }
2618 }
2619
2620
2621 XsdTreeNode relevantChild = this.children.get(xsdTreeNodeIndex);
2622 if (relevantChild.choice == null)
2623 {
2624 if (relevantChild.getNodeName().equals("xsd:sequence"))
2625 {
2626 LoadingIndices sequenceIndices = new LoadingIndices(xmlNodeIndex, 0);
2627 relevantChild.loadChildren(sequenceIndices, childrenXml, true);
2628 loadedChildren.add(relevantChild);
2629 xmlNodeIndex = sequenceIndices.getXmlNode() - 1;
2630 }
2631 else
2632 {
2633 relevantChild.loadXmlNodes(childNodeXml);
2634 loadedChildren.add(relevantChild);
2635 }
2636 }
2637 else
2638 {
2639 boolean optionSet = false;
2640 for (XsdTreeNode option : relevantChild.choice.options)
2641 {
2642 if (option.xsdNode.getNodeName().equals("xsd:sequence"))
2643 {
2644 for (XsdTreeNode child : option.children)
2645 {
2646 if (child.isRelevantNode(nameXml))
2647 {
2648 relevantChild.choice.setOption(option);
2649 LoadingIndices optionIndices = new LoadingIndices(xmlNodeIndex, 0);
2650 option.loadChildren(optionIndices, childrenXml, true);
2651 loadedChildren.add(option);
2652 xmlNodeIndex = optionIndices.getXmlNode() - 1;
2653 optionSet = true;
2654 break;
2655 }
2656 }
2657 }
2658 if (option.getNodeName().equals(nameXml))
2659 {
2660 relevantChild.choice.setOption(option);
2661 option.loadXmlNodes(childNodeXml);
2662 optionSet = true;
2663 }
2664 if (optionSet)
2665 {
2666 for (XsdTreeNode otherOption : relevantChild.choice.options)
2667 {
2668 if (!otherOption.equals(option))
2669 {
2670 otherOption.setActive();
2671 }
2672 }
2673 break;
2674 }
2675 }
2676 }
2677 loadedDuringPass++;
2678 xsdTreeNodeIndex++;
2679 indices.setXsdTreeNode(xsdTreeNodeIndex);
2680 xmlNodeIndex++;
2681 }
2682 indices.setXmlNode(xmlNodeIndex);
2683 }
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699 private boolean isRelevantNode(final String nameXml)
2700 {
2701 if (this.getNodeName().equals(nameXml))
2702 {
2703 return true;
2704 }
2705 if (this.choice == null)
2706 {
2707 if (getNodeName().equals("xsd:sequence"))
2708 {
2709 this.active = true;
2710 assureChildren();
2711 for (XsdTreeNode child : this.children)
2712 {
2713 boolean relevant = child.isRelevantNode(nameXml);
2714 if (relevant)
2715 {
2716 return relevant;
2717 }
2718 }
2719 }
2720 return false;
2721 }
2722 if (this.choice.selected.equals(this))
2723 {
2724 for (XsdTreeNode option : this.choice.options)
2725 {
2726 if (option.xsdNode.getNodeName().equals("xsd:sequence"))
2727 {
2728 option.active = true;
2729 option.assureChildren();
2730 for (XsdTreeNode child : option.children)
2731 {
2732 boolean relevant = child.isRelevantNode(nameXml);
2733 if (relevant)
2734 {
2735 return relevant;
2736 }
2737 }
2738 }
2739 else if (!option.equals(this))
2740 {
2741 boolean relevant = option.isRelevantNode(nameXml);
2742 if (relevant)
2743 {
2744 return relevant;
2745 }
2746 }
2747 }
2748 }
2749 return false;
2750 }
2751
2752
2753
2754 @Override
2755 public boolean addListener(final EventListener listener, final EventType eventType, final ReferenceType referenceType)
2756 {
2757 boolean result = super.addListener(listener, eventType, referenceType);
2758 sortListeners(eventType);
2759 return result;
2760 }
2761
2762 @Override
2763 public boolean addListener(final EventListener listener, final EventType eventType)
2764 {
2765 boolean result = super.addListener(listener, eventType);
2766 sortListeners(eventType);
2767 return result;
2768 }
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778 private void sortListeners(final EventType eventType)
2779 {
2780 EventListenerMap map = getEventListenerMap();
2781 Reference<EventListener> undo = null;
2782 List<Reference<EventListener>> list = map.get(eventType);
2783 List<Reference<EventListener>> keys = new ArrayList<>();
2784 List<Reference<EventListener>> keyrefs = new ArrayList<>();
2785 List<Reference<EventListener>> coupled = new ArrayList<>();
2786 for (Reference<EventListener> listen : list)
2787 {
2788 if (listen.get() instanceof Undo)
2789 {
2790 undo = listen;
2791 }
2792 else if (listen.get() instanceof KeyValidator)
2793 {
2794 keys.add(listen);
2795 }
2796 else if (listen.get() instanceof KeyrefValidator)
2797 {
2798 keyrefs.add(listen);
2799 }
2800 else if (listen.get() instanceof CoupledValidator)
2801 {
2802 coupled.add(listen);
2803 }
2804 }
2805 list.removeAll(coupled);
2806 list.removeAll(keyrefs);
2807 list.removeAll(keys);
2808 list.addAll(0, coupled);
2809 list.addAll(0, keyrefs);
2810 list.addAll(0, keys);
2811 if (undo != null)
2812 {
2813 list.remove(undo);
2814 list.add(0, undo);
2815 }
2816 }
2817
2818 }