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