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