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