source: trunk/autoquest-core-events/src/main/java/de/ugoe/cs/autoquest/eventcore/guimodel/GUIModel.java @ 1433

Last change on this file since 1433 was 1433, checked in by pharms, 10 years ago
  • added support to retrieve the GUI model to which a GUI element belongs from the GUI element itself
File size: 34.2 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.eventcore.guimodel;
16
17import java.io.OutputStream;
18import java.io.PrintStream;
19import java.io.Serializable;
20import java.io.UnsupportedEncodingException;
21import java.util.ArrayList;
22import java.util.HashMap;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.Stack;
27import java.util.logging.Level;
28
29import de.ugoe.cs.util.console.Console;
30
31/**
32 * <p>
33 * A GUI model is a tree of {@link IGUIElements} and represents a complete GUI of a software. It is
34 * platform independent. It may have several root nodes, as some GUIs are made up of several Frames
35 * being independent from each other. The GUI model is filled using the
36 * {@link #integratePath(List, IGUIElementFactory)} method.
37 * </p>
38 *
39 * @version 1.0
40 * @author Patrick Harms, Steffen Herbold
41 */
42public class GUIModel implements Serializable {
43
44    /**  */
45    private static final long serialVersionUID = 1L;
46
47    /**
48     * <p>
49     * The root node of the tree not provided externally.
50     * </p>
51     */
52    private TreeNode root = new TreeNode();
53
54    /**
55     * <p>
56     * A map with all nodes currently known
57     * </p>
58     */
59    private Map<IGUIElement, TreeNode> allNodes = new HashMap<IGUIElement, TreeNode>();
60   
61    /**
62     * <p>
63     * true, if internal validation is switched on, false else
64     * </p>
65     */
66    private boolean validate = false;
67
68    /**
69     * <p>
70     * Default constructor to create a GUI model without internal validation
71     * </p>
72     *
73     */
74    public GUIModel() {
75        this(false);
76    }
77
78    /**
79     * <p>
80     * creates a GUI model, that internally validates itself by checking on access to nodes,
81     * if several GUI elements pretend to be equal or if several distinct GUI elements have the
82     * same child.
83     * </p>
84     *
85     * @param validate
86     *            true if internal validation shall be switched on (bad performance), false else
87     *
88     */
89    public GUIModel(boolean validate) {
90        this.validate = validate;
91    }
92
93    /**
94     * <p>
95     * Integrates a path of GUI elements into the GUI model. The GUI model itself is a tree and
96     * therefore a set of different paths through the tree that start with a root node and end with
97     * a leaf node. Such a path can be added to the tree. The method checks, if any of the GUI
98     * elements denoted by the path already exists. If so, it reuses it. It may therefore also
99     * return an existing GUI element being the leaf node of the provided path. If a GUI element of
100     * the path does not exist yet, it creates a new one using the provided GUI element factory.
101     * </p>
102     * <p>
103     * If a GUI element specification describes an existing GUI element or not is determined through
104     * comparing the GUI element specifications of the existing GUI elements with the ones provided
105     * in the path. The comparison is done using the
106     * {@link IGUIElementSpec#getSimilarity(IGUIElementSpec)} method. The comparison is only done on
107     * the correct levels. I.e. the currently known root elements of the tree are only compared to
108     * the first element in the path. If the correct one is found or created, its children are
109     * compared only to the second specification in the path, and so on.
110     * </p>
111     * <p>
112     * The returned GUI elements are singletons. I.e. it is tried to return always the identical
113     * object for the same denoted element. However, while creating the GUI model, the similarity of
114     * GUI elements may change. Therefore, the method might determine, that two formerly different
115     * nodes are now similar. (This may happen, e.g. if GUI elements do not have initial names which
116     * are set afterwards. Therefore, first they are handled differently and later they can be
117     * identified as being the same.) In such a case, there are already several GUI element objects
118     * instantiated for the same GUI element. The singleton paradigm gets broken. Therefore, such
119     * GUI element objects are registered with each other, so that their equal method can determine
120     * equality again correctly, although the objects are no singletons anymore.
121     * </p>
122     *
123     * @param guiElementPath
124     *            the path to integrate into the model
125     * @param guiElementFactory
126     *            the GUI element factory to be used for instantiating GUI element objects
127     *
128     * @return The GUI element object representing the GUI element denoted by the provided path
129     *
130     * @throws GUIModelException
131     *             thrown in cases such as the GUI element object could not be instantiated
132     * @throws IllegalArgumentException
133     *             if the provided path is invalid.
134     */
135    public IGUIElement integratePath(List<? extends IGUIElementSpec> guiElementPath,
136                                     IGUIElementFactory guiElementFactory)
137        throws GUIModelException, IllegalArgumentException
138    {
139        if ((guiElementPath == null) || (guiElementPath.size() <= 0)) {
140            throw new IllegalArgumentException("GUI element path must contain at least one element");
141        }
142
143        List<IGUIElementSpec> remainingPath = new LinkedList<IGUIElementSpec>(guiElementPath);
144
145        return integratePath(root, remainingPath, guiElementFactory);
146    }
147
148    /**
149     * <p>
150     * Returns all children of the provided GUI element or null, if it does not have any or the node
151     * is unknown.
152     * </p>
153     *
154     * @param guiElement
155     *            the GUI element of which the children shall be returned
156     *
157     * @return As described
158     */
159    public List<IGUIElement> getChildren(IGUIElement guiElement) {
160        TreeNode node = findNode(guiElement);
161       
162        List<IGUIElement> result = null;
163        if (node != null) {
164            result = new LinkedList<IGUIElement>();
165            if (node.children != null) {
166                for (TreeNode child : node.children) {
167                    result.add(child.guiElement);
168                }
169            }
170        }
171 
172        return result;
173    }
174
175    /**
176     * <p>
177     * Returns the parent GUI element of the provided GUI element or null, if it does not have a
178     * parent (i.e. if it is a root node) or if the node is unknown.
179     * </p>
180     *
181     * @param guiElement
182     *            the GUI element of which the parent shall be returned
183     *
184     * @return As described
185     */
186    public IGUIElement getParent(IGUIElement guiElement) {
187        IGUIElement parent = null;
188
189        for (Map.Entry<IGUIElement, TreeNode> entry : allNodes.entrySet()) {
190            if (entry.getValue().children != null) {
191                for (TreeNode child : entry.getValue().children) {
192                    if (child.guiElement.equals(guiElement)) {
193                        if (parent == null) {
194                            parent = entry.getKey();
195                            if (!validate) {
196                                break;
197                            }
198                        }
199                        else {
200                            Console
201                            .traceln(Level.SEVERE,
202                                     "Multiple nodes in the internal GUI model match the same GUI element. "
203                                             + "This should not be the case and the GUI model is probably invalid.");
204                        }
205                    }
206                }
207            }
208        }
209
210        return parent;
211    }
212
213    /**
214     * <p>
215     * Returns all root GUI elements of the model or an empty list, if the model is empty
216     * </p>
217     *
218     * @return As described
219     */
220    public List<IGUIElement> getRootElements() {
221        List<IGUIElement> roots = new ArrayList<IGUIElement>();
222
223        if (root.children != null) {
224            for (TreeNode rootChild : root.children) {
225                roots.add(rootChild.guiElement);
226            }
227        }
228
229        return roots;
230    }
231   
232    /**
233     * returns a traverser for the GUI model to have efficient access to the tree of GUI elements
234     * without having direct access.
235     *
236     * @return a traverser
237     */
238    public Traverser getTraverser() {
239        return new Traverser();
240    }
241
242    /**
243     * returns a traverser for the GUI model starting at the given GUI element. Returns null, if
244     * the GUI element is not part of the model.
245     *
246     * @return a traverser
247     */
248    public Traverser getTraverser(IGUIElement startingAt) {
249        TreeNode node = findNode(startingAt);
250       
251        if (node != null) {
252            Traverser traverser = new Traverser();
253            traverser.navigateTo(node);
254            return traverser;
255        }
256        else {
257            return null;
258        }
259    }
260
261    /**
262     * <p>
263     * dumps the GUI model to the provided stream. Each node is represented through its toString()
264     * method. If a node has children, those are dumped indented and surrounded by braces.
265     * </p>
266     *
267     * @param out
268     *            The stream to dump the textual representation of the model to
269     * @param encoding
270     *            The encoding to be used while dumping
271     */
272    public void dump(OutputStream out, String encoding) {
273        PrintStream stream;
274
275        if (out instanceof PrintStream) {
276            stream = (PrintStream) out;
277        }
278        else {
279            String enc = encoding == null ? "UTF-8" : encoding;
280            try {
281                stream = new PrintStream(out, true, enc);
282            }
283            catch (UnsupportedEncodingException e) {
284                throw new IllegalArgumentException("encodind " + enc + " not supported");
285            }
286        }
287
288        for (TreeNode node : root.children) {
289            dumpGUIElement(stream, node, "");
290        }
291    }
292
293    /**
294     * <p>
295     * This method groups the provided GUI elements under a common parent GUI element. The current
296     * parent GUI element of the GUI elements to group must be the same. If the GUI elements to
297     * be grouped are the whole list of children of the same parent, nothing is changed.
298     * </p>
299     *
300     * @param guiElements the list of GUI elements to be grouped
301     * @param groupName   the name of the GUI element group to be created
302     *
303     * @return the GUI element representing the group, or null, if the provided list of GUI elements
304     *         is empty
305     *
306     * @throws IllegalArgumentException
307     *             if not all GUI elements to be merged share the same parent, if one of the
308     *             parameters is null, or if one of the provided GUI elements does not belong to
309     *             the model
310     */
311    public IGUIElement groupGUIElements(List<IGUIElement> guiElements, String groupName)
312        throws IllegalArgumentException
313    {
314        if ((guiElements == null) || (groupName == null)) {
315            throw new IllegalArgumentException("parameters must not be null");
316        }
317       
318        if (guiElements.size() <= 0) {
319            // do nothing
320            return null;
321        }
322       
323        TreeNode parent = findNode(guiElements.get(0).getParent());
324       
325        List<TreeNode> nodesToGroup = new LinkedList<TreeNode>();
326       
327        for (IGUIElement element : guiElements) {
328            if (!(element instanceof AbstractDefaultGUIElement)) {
329                throw new IllegalArgumentException
330                    ("can only group nodes of type AbstractDefaultGUIElement");
331            }
332           
333            TreeNode node = findNode(element);
334            if (node == null) {
335                throw new IllegalArgumentException
336                    ("GUI element " + element + " is not part of the model");
337            }
338           
339            if (!nodesToGroup.contains(node)) {
340                nodesToGroup.add(node);
341            }
342           
343            TreeNode parentNode = findNode(element.getParent());
344           
345            if (!parent.equals(parentNode)) {
346                throw new IllegalArgumentException("GUI elements do not share the same parent: " +
347                                                   parent + " (parent of " + guiElements.get(0) +
348                                                   ") <> " + parentNode + " (parent of " +
349                                                   element + ")");
350            }
351        }
352       
353        TreeNode replacement = new TreeNode();
354        replacement.guiElement = new GUIElementGroup(groupName, parent.guiElement, this);
355       
356        for (TreeNode child : nodesToGroup) {
357            ((GUIElementGroup) replacement.guiElement).addToGroup(child.guiElement);
358            replacement.addChildNode(child);
359            ((AbstractDefaultGUIElement) child.guiElement).setParent(replacement.guiElement);
360            parent.children.remove(child);
361        }
362
363        parent.children.add(replacement);
364
365        // finally, update the known nodes list
366        // if you don't do this getChildren will return wrong things and very bad things happen!
367        allNodes.put(replacement.guiElement, replacement);
368       
369        return replacement.guiElement;
370    }
371   
372    /**
373     * <p>
374     * By calling this method, the GUIModel is traversed and similar nodes are merged.
375     * </p>
376     *
377     */
378    public void condenseModel() {
379        mergeSubTree(root);
380    }
381   
382    /**
383     * <p>
384     * Merges the tree nodes of two GUI elements. The GUI elements need to have the same parent.
385     * They are merged recursively, i.e. also their children are merged.
386     * </p>
387     *
388     * @param guiElement1
389     *            the first merge GUI element
390     * @param guiElement2
391     *            the second merge GUI element
392     *           
393     * @return the result of the merge
394     *           
395     * @throws IllegalArgumentException
396     *             thrown if the two GUI elements do not have the same parent
397     */
398    public IGUIElement mergeGUIElements(IGUIElement guiElement1, IGUIElement guiElement2)
399        throws IllegalArgumentException
400    {
401        return mergeGUIElements(guiElement1, guiElement2, true);
402    }
403   
404    /**
405     * <p>
406     * Merges the tree nodes of two GUI elements. The GUI elements need to have the same parent.
407     * If the <code>recursively</code> parameter is set to true, the children of the GUI elements
408     * are merged, as well, as long as they are similar. If the parameter is false, the children
409     * are not merged. In this case the resulting GUI element has all children of both merged GUI
410     * elements.
411     * </p>
412     *
413     * @param guiElement1
414     *            the first merge GUI element
415     * @param guiElement2
416     *            the second merge GUI element
417     * @param recursively
418     *            if true, the merge is done also for similar children, if false, not.
419     *           
420     * @return the result of the merge
421     *           
422     * @throws IllegalArgumentException
423     *             thrown if the two GUI elements do not have the same parent
424     */
425    public IGUIElement mergeGUIElements(IGUIElement guiElement1,
426                                        IGUIElement guiElement2,
427                                        boolean     recursively)
428        throws IllegalArgumentException
429    {
430        // check if both nodes have the same parent
431        IGUIElement parentElement = guiElement1.getParent();
432        boolean sameParent = (parentElement != null) ?
433            parentElement.equals(guiElement2.getParent()) : (guiElement2.getParent() == null);
434           
435        if (!sameParent) {
436            throw new IllegalArgumentException("can only merge nodes with the same parent");
437        }
438
439        // get the TreeNode of the parent of the GUI elements
440        TreeNode parent = findNode(parentElement);
441       
442        if ((parent == null) && (parentElement == null)) {
443            // merging root nodes. The parent is the root node of the GUI element tree
444            parent = root;
445        }
446
447        // get the TreeNodes for both GUI elements
448        TreeNode node1 = findNode(guiElement1);
449        TreeNode node2 = findNode(guiElement2);
450
451        if (node1 == null || node2 == null) {
452            throw new IllegalArgumentException(
453                                               "Error while merging nodes: one element is not part of the GUI model!");
454        }
455
456        TreeNode replacement = mergeTreeNodes(node1, node2, recursively);
457
458        if (parent != null) {
459            // remove node1 and node2 from the parent's children and add the replacement instead
460            // assumes that there are no duplicates of node1 and node2
461            if (parent.children != null) {
462                parent.children.set(parent.children.indexOf(node1), replacement);
463                parent.children.remove(node2);
464            }
465        }
466
467        return replacement.guiElement;
468    }
469
470    /**
471     * <p>
472     * internally integrates a path as the children of the provided parent node. This method is
473     * recursive and calls itself, for the child of the parent node, that matches the first element
474     * in the remaining path.
475     * </p>
476     *
477     * @param parentNode
478     *            the parent node to add children for
479     * @param guiElementPath
480     *            the path of children to be created starting with the parent node
481     * @param guiElementFactory
482     *            the GUI element factory to be used for instantiating GUI element objects
483     *
484     * @return The GUI element object representing the GUI element denoted by the provided path
485     *
486     * @throws GUIModelException
487     *             thrown in cases such as the GUI element object could not be instantiated
488     */
489    private IGUIElement integratePath(TreeNode                        parentNode,
490                                      List<? extends IGUIElementSpec> remainingPath,
491                                      IGUIElementFactory              guiElementFactory)
492        throws GUIModelException
493    {
494        IGUIElementSpec specToIntegrateElementFor = remainingPath.remove(0);
495
496        TreeNode child = findEqualChild(parentNode, specToIntegrateElementFor);
497        if (child == null) {
498            IGUIElement newElement =
499                guiElementFactory.instantiateGUIElement(specToIntegrateElementFor,
500                                                        parentNode.guiElement);
501
502            if (newElement instanceof AbstractDefaultGUIElement) {
503                ((AbstractDefaultGUIElement) newElement).setGUIModel(this);
504            }
505           
506            child = parentNode.addChild(newElement);
507            allNodes.put(child.guiElement, child);
508        }
509
510        if (remainingPath.size() > 0) {
511            return integratePath(child, remainingPath, guiElementFactory);
512        }
513        else {
514            return child.guiElement;
515        }
516    }
517
518    /**
519     * <p>
520     * Searches the children of a tree node to see if the {@link IGUIElementSpec} of equals the
521     * specification of the {@link TreeNode#guiElement} of the child. If a match is found, the child
522     * is returned.
523     * </p>
524     *
525     * @param parentNode
526     *            parent node whose children are searched
527     * @param specToMatch
528     *            specification that is searched for
529     * @return matching child node or null if no child matches
530     */
531    private TreeNode findEqualChild(TreeNode parentNode, IGUIElementSpec specToMatch) {
532        if (parentNode.children != null) {
533            for (TreeNode child : parentNode.children) {
534                if (specToMatch.equals(child.guiElement.getSpecification())) {
535                    return child;
536                }
537            }
538        }
539        return null;
540    }
541
542    /**
543     * <p>
544     * Merges all similar nodes in the sub-tree of the GUI model defined by the subTreeRoot.
545     * </p>
546     * <p>
547     * The merging order is a bottom-up. This means, that we first call mergeSubTree recursively for
548     * the grand children of the subTreeRoot, before we merge subTreeRoot.
549     * </p>
550     * <p>
551     * The merging strategy is top-down. This means, that every time we merge two child nodes, we
552     * call mergeSubTree recursively for all children of the merged nodes in order to check if we
553     * can merge the children, too.
554     * </p>
555     *
556     * @param subTreeRoot
557     *            root node of the sub-tree that is merged
558     */
559    private void mergeSubTree(TreeNode subTreeRoot) {
560        if (subTreeRoot.children == null || subTreeRoot.children.isEmpty()) {
561            return;
562        }
563
564        // lets first merge the grand children of the parentNode
565        for (TreeNode child : subTreeRoot.children) {
566            mergeSubTree(child);
567        }
568
569        boolean performedMerge;
570
571        do {
572            performedMerge = false;
573            for (int i = 0; !performedMerge && i < subTreeRoot.children.size(); i++) {
574                IGUIElementSpec elemSpec1 =
575                    subTreeRoot.children.get(i).guiElement.getSpecification();
576                for (int j = i + 1; !performedMerge && j < subTreeRoot.children.size(); j++) {
577                    IGUIElementSpec elemSpec2 =
578                        subTreeRoot.children.get(j).guiElement.getSpecification();
579                    if (elemSpec1.getSimilarity(elemSpec2)) {
580                        TreeNode replacement = mergeTreeNodes
581                            (subTreeRoot.children.get(i), subTreeRoot.children.get(j), true);
582
583                        subTreeRoot.children.set(i, replacement);
584                        subTreeRoot.children.remove(j);
585                        performedMerge = true;
586                        i--;
587                        break;
588                    }
589                }
590            }
591        }
592        while (performedMerge);
593    }
594
595    /**
596     * <p>
597     * merges two nodes with each other. Merging means registering the GUI element objects with each
598     * other for equality checks. Further it adds all children of both nodes to a new replacing
599     * node. Afterwards, all similar nodes of the replacement node are merged as well as long
600     * the recursive parameter is set to true.
601     * </p>
602     *
603     * @param treeNode1
604     *            the first of the two nodes to be merged
605     * @param treeNode2
606     *            the second of the two nodes to be merged
607     * @param recursively
608     *            if true, the merging also merges child nodes
609     *           
610     * @return a tree node being the merge of the two provided nodes.
611     */
612    private TreeNode mergeTreeNodes(TreeNode treeNode1, TreeNode treeNode2, boolean recursively) {
613        // and now a replacement node that is the merge of treeNode1 and treeNode2 is created
614        TreeNode replacement = new TreeNode();
615        replacement.guiElement = treeNode1.guiElement;
616        if (treeNode1.children != null) {
617            for (TreeNode child : treeNode1.children) {
618                replacement.addChildNode(child);
619            }
620        }
621        if (treeNode2.children != null) {
622            for (TreeNode child : treeNode2.children) {
623                replacement.addChildNode(child);
624            }
625        }
626
627        if (recursively) {
628            mergeSubTree(replacement);
629        }
630
631        replacement.guiElement.updateSpecification(treeNode2.guiElement.getSpecification());
632
633        // finally, update the known nodes list
634        // if you don't do this getChildren will return wrong things and very bad things happen!
635        allNodes.remove(treeNode1.guiElement);
636        allNodes.remove(treeNode2.guiElement);
637
638        // the following two lines are needed to preserve the references to the existing GUI
639        // elements. If two elements are the same, one should be deleted to make the elements
640        // singletons again. However, there may exist references to both objects. To preserve
641        // these, we simply register the equal GUI elements with each other so that an equals
642        // check can return true.
643        treeNode1.guiElement.addEqualGUIElement(treeNode2.guiElement);
644        treeNode2.guiElement.addEqualGUIElement(treeNode1.guiElement);
645       
646        allNodes.put(replacement.guiElement, replacement);
647
648        return replacement;
649    }
650
651    /**
652     * <p>
653     * dumps a GUI element to the stream. A dump contains the toString() representation of the GUI
654     * element as well as a indented list of its children surrounded by braces. Therefore, not the
655     * GUI element itself but its tree node is provided to have an efficient access to its children
656     * </p>
657     *
658     * @param out
659     *            {@link PrintStream} where the guiElement is dumped to
660     * @param node
661     *            the guiElement's tree node of which the string representation is dumped
662     * @param indent
663     *            indent string of the dumping
664     */
665    private void dumpGUIElement(PrintStream out, TreeNode node, String indent) {
666        out.print(indent);
667        out.print(node.guiElement);
668
669        if ((node.children != null) && (node.children.size() > 0)) {
670            out.println(" {");
671
672            for (TreeNode child : node.children) {
673                dumpGUIElement(out, child, indent + "  ");
674            }
675
676            out.print(indent);
677            out.print("}");
678        }
679
680        out.println();
681    }
682   
683    /**
684     * <p>
685     * Retrieves the TreeNode associated with a GUI element. Returns null if no such TreeNode is
686     * found.
687     * </p>
688     *
689     * @param element
690     *            the GUI element
691     * @return associated TreeNode; null if no such node exists
692     */
693    private TreeNode findNode(IGUIElement element) {
694        if (element == null) {
695            return null;
696        }
697
698        TreeNode result = null;
699       
700        if (!validate) {
701            result = allNodes.get(element);
702        }
703        else {
704            for (Map.Entry<IGUIElement, TreeNode> entry : allNodes.entrySet()) {
705                if (entry.getKey().equals(element)) {
706                    if (result == null) {
707                        result = entry.getValue();
708                    }
709                    else {
710                        Console.traceln(Level.SEVERE, "Multiple nodes in the internal GUI model " +
711                                        "match the same GUI element. This should not be the case " +
712                                        "and the GUI model is probably invalid.");
713                    }
714                }
715            }
716        }
717        return result;
718    }
719
720    /**
721     * <p>
722     * Used externally for tree traversal without providing direct access to the tree nodes
723     * </p>
724     *
725     * @version 1.0
726     * @author Patrick Harms, Steffen Herbold
727     */
728    public class Traverser {
729       
730        /**
731         * <p>
732         * the stack of nodes on which the traverser currently works
733         * </p>
734         */
735        private Stack<StackEntry> nodeStack = new Stack<StackEntry>();
736       
737        /**
738         * <p>
739         * initializes the traverser by adding the root node of the GUI model to the stack
740         * </p>
741         */
742        private Traverser() {
743            nodeStack.push(new StackEntry(root, 0));
744        }
745       
746        /**
747         * <p>
748         * returns the first child of the current GUI element. On the first call of this method on
749         * the traverser the first of the root GUI elements of the GUI model is returned. If the
750         * current GUI element does not have children, the method returns null. If the GUI model
751         * is empty, then a call to this method will return null. The returned GUI element is the
752         * next one the traverser points to.
753         * </p>
754         *
755         * @return as described.
756         */
757        public IGUIElement firstChild() {
758            return pushChild(0);
759        }
760       
761        /**
762         * <p>
763         * returns true, if the current GUI element has a first child, i.e. if the next call to the
764         * method {@link #firstChild()} would return a GUI element or null.
765         * </p>
766         *
767         * @return as described
768         */
769        public boolean hasFirstChild() {
770            return
771                (nodeStack.peek().treeNode.children != null) &&
772                (nodeStack.peek().treeNode.children.size() > 0);
773        }
774       
775        /**
776         * <p>
777         * returns the next sibling of the current GUI element. If there is no further sibling,
778         * null is returned. If the current GUI element is one of the root nodes, the next root
779         * node of the GUI model is returned. The returned GUI element is the next one the
780         * traverser points to.
781         * </p>
782         *
783         * @return as described
784         */
785        public IGUIElement nextSibling() {
786            int lastIndex = nodeStack.pop().index;
787           
788            IGUIElement retval = pushChild(lastIndex + 1);
789            if (retval == null) {
790                pushChild(lastIndex);
791            }
792           
793            return retval;
794        }
795       
796        /**
797         * <p>
798         * returns true, if the current GUI element has a further sibling, i.e. if a call to the
799         * method {@link #nextSibling()} will return a GUI element;
800         * </p>
801         *
802         * @return as described
803         */
804        public boolean hasNextSibling() {
805            boolean result = false;
806            if (nodeStack.size() > 1) {
807                StackEntry entry = nodeStack.pop();
808                result = nodeStack.peek().treeNode.children.size() > (entry.index + 1);
809                pushChild(entry.index);
810            }
811           
812            return result;
813        }
814       
815        /**
816         * <p>
817         * returns the parent GUI element of the current GUI element. If the current GUI element
818         * is a root node, null is returned. If there is no current GUI element yet as the method
819         * {@link #firstChild()} was not called yet, null is returned.
820         * </p>
821         *
822         * @return as described
823         */
824        public IGUIElement parent() {
825            IGUIElement retval = null;
826           
827            if (nodeStack.size() > 1) {
828                nodeStack.pop();
829                retval = nodeStack.peek().treeNode.guiElement;
830            }
831           
832            return retval;
833        }
834       
835        /**
836         * <p>
837         * internal method used for changing the state of the traverser. I.e. to switch to a
838         * specific child GUI element of the current one.
839         * </p>
840         */
841        private IGUIElement pushChild(int index) {
842            IGUIElement retVal = null;
843           
844            if ((nodeStack.peek().treeNode.children != null) &&
845                (nodeStack.peek().treeNode.children.size() > index))
846            {
847                nodeStack.push
848                    (new StackEntry(nodeStack.peek().treeNode.children.get(index), index));
849                retVal = nodeStack.peek().treeNode.guiElement;
850            }
851           
852            return retVal;
853        }
854       
855        /**
856         * <p>
857         * navigates the traverser to the given node in the GUI model
858         * </p>
859         */
860        private boolean navigateTo(TreeNode node) {
861            if (hasFirstChild()) {
862                IGUIElement childElement = firstChild();
863           
864                while (childElement != null) {
865                    if (childElement.equals(node.guiElement)) {
866                        return true;
867                    }
868                    else if (navigateTo(node)) {
869                        return true;
870                    }
871                    else {
872                        childElement = nextSibling();
873                    }
874                }
875           
876                parent();
877            }
878           
879            return false;
880        }
881
882        /**
883         * <p>
884         * internal class needed to fill the stack with nodes of the GUI models and their
885         * respective index in the children of the parent node.
886         * </p>
887         */
888        private class StackEntry {
889           
890            /** */
891            private TreeNode treeNode;
892           
893            /** */
894            private int index;
895           
896            /**
897             * <p>
898             * creates a new stack entry.
899             * </p>
900             */
901            private StackEntry(TreeNode treeNode, int index) {
902                this.treeNode = treeNode;
903                this.index = index;
904            }
905        }
906    }
907
908    /**
909     * <p>
910     * Used internally for building up the tree of GUI elements.
911     * </p>
912     *
913     * @version 1.0
914     * @author Patrick Harms, Steffen Herbold
915     */
916    private static class TreeNode implements Serializable {
917
918        /**  */
919        private static final long serialVersionUID = 1L;
920
921        /**
922         * <p>
923         * GUI element associated with the TreeNode.
924         * </p>
925         */
926        private IGUIElement guiElement;
927
928        /**
929         * <p>
930         * Children of the TreeNode.
931         * </p>
932         */
933        private List<TreeNode> children;
934
935        /**
936         * <p>
937         * Adds a child to the current node while keeping all lists of nodes up to date
938         * </p>
939         *
940         * @param guiElement
941         *            GUI element that will be associated with the new child
942         * @return the added child
943         */
944        private TreeNode addChild(IGUIElement guiElement) {
945            if (children == null) {
946                children = new ArrayList<TreeNode>();
947            }
948
949            TreeNode child = new TreeNode();
950            child.guiElement = guiElement;
951            children.add(child);
952
953            return child;
954        }
955
956        /**
957         *
958         * <p>
959         * Adds a TreeNode as child to the current node. This way, the whole sub-tree is added.
960         * </p>
961         *
962         * @param node
963         *            child node that is added
964         * @return node that has been added
965         */
966        private TreeNode addChildNode(TreeNode node) {
967            if (children == null) {
968                children = new ArrayList<TreeNode>();
969            }
970            children.add(node);
971            return node;
972        }
973
974        /*
975         * (non-Javadoc)
976         *
977         * @see java.lang.Object#toString()
978         */
979        @Override
980        public String toString() {
981            return guiElement.toString();
982        }
983
984    }
985}
Note: See TracBrowser for help on using the repository browser.