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