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