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

Last change on this file since 1260 was 1260, checked in by pharms, 11 years ago
  • added support for grouping GUI elements
  • performance improvements
  • extended merge support for including non-recursive merges
  • added switch for validation
File size: 33.6 KB
RevLine 
[927]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.
[820]14
[922]15package de.ugoe.cs.autoquest.eventcore.guimodel;
[545]16
[643]17import java.io.OutputStream;
18import java.io.PrintStream;
[1116]19import java.io.Serializable;
[746]20import java.io.UnsupportedEncodingException;
[545]21import java.util.ArrayList;
[1260]22import java.util.HashMap;
[603]23import java.util.LinkedList;
[545]24import java.util.List;
[1260]25import java.util.Map;
[1086]26import java.util.Stack;
[821]27import java.util.logging.Level;
[545]28
[821]29import de.ugoe.cs.util.console.Console;
30
[545]31/**
[576]32 * <p>
[746]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
[820]36 * {@link #integratePath(List, IGUIElementFactory)} method.
[576]37 * </p>
[545]38 *
[821]39 * @version 1.0
[820]40 * @author Patrick Harms, Steffen Herbold
[545]41 */
[1116]42public class GUIModel implements Serializable {
[820]43
[1116]44    /**  */
45    private static final long serialVersionUID = 1L;
46
[545]47    /**
[746]48     * <p>
[831]49     * The root node of the tree not provided externally.
[746]50     * </p>
[545]51     */
[576]52    private TreeNode root = new TreeNode();
[820]53
[545]54    /**
[746]55     * <p>
[1260]56     * A map with all nodes currently known
[746]57     * </p>
[545]58     */
[1260]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;
[545]67
68    /**
[746]69     * <p>
[1260]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>
[746]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
[820]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.
[746]101     * </p>
102     * <p>
[820]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
[746]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
[820]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.
[746]121     * </p>
[820]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     *
[746]128     * @return The GUI element object representing the GUI element denoted by the provided path
129     *
[820]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.
[545]134     */
[576]135    public IGUIElement integratePath(List<? extends IGUIElementSpec> guiElementPath,
[820]136                                     IGUIElementFactory guiElementFactory)
[746]137        throws GUIModelException, IllegalArgumentException
[576]138    {
[611]139        if ((guiElementPath == null) || (guiElementPath.size() <= 0)) {
[820]140            throw new IllegalArgumentException("GUI element path must contain at least one element");
[611]141        }
[820]142
[1260]143        List<IGUIElementSpec> remainingPath = new LinkedList<IGUIElementSpec>(guiElementPath);
[820]144
[576]145        return integratePath(root, remainingPath, guiElementFactory);
[545]146    }
147
148    /**
[746]149     * <p>
[820]150     * Returns all children of the provided GUI element or null, if it does not have any or the node
151     * is unknown.
[746]152     * </p>
153     *
[820]154     * @param guiElement
155     *            the GUI element of which the children shall be returned
156     *
[746]157     * @return As described
[545]158     */
[576]159    public List<IGUIElement> getChildren(IGUIElement guiElement) {
[1260]160        TreeNode node = findNode(guiElement);
161       
[821]162        List<IGUIElement> result = null;
[1260]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);
[576]168                }
169            }
170        }
[1260]171 
[821]172        return result;
[545]173    }
174
175    /**
[593]176     * <p>
[746]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.
[593]179     * </p>
[746]180     *
[820]181     * @param guiElement
182     *            the GUI element of which the parent shall be returned
183     *
[746]184     * @return As described
[545]185     */
[593]186    public IGUIElement getParent(IGUIElement guiElement) {
[821]187        IGUIElement parent = null;
188
[1260]189        for (Map.Entry<IGUIElement, TreeNode> entry : allNodes.entrySet()) {
190            if (entry.getValue().children != null) {
191                for (TreeNode child : entry.getValue().children) {
[1088]192                    if (child.guiElement.equals(guiElement)) {
[1260]193                        if (parent == null) {
194                            parent = entry.getKey();
195                            if (!validate) {
196                                break;
197                            }
[1088]198                        }
199                        else {
200                            Console
[821]201                            .traceln(Level.SEVERE,
202                                     "Multiple nodes in the internal GUI model match the same GUI element. "
[1088]203                                             + "This should not be the case and the GUI model is probably invalid.");
204                        }
[821]205                    }
[593]206                }
207            }
208        }
[820]209
[821]210        return parent;
[593]211    }
212
213    /**
[746]214     * <p>
215     * Returns all root GUI elements of the model or an empty list, if the model is empty
216     * </p>
[820]217     *
[746]218     * @return As described
[593]219     */
[576]220    public List<IGUIElement> getRootElements() {
221        List<IGUIElement> roots = new ArrayList<IGUIElement>();
[820]222
[611]223        if (root.children != null) {
224            for (TreeNode rootChild : root.children) {
225                roots.add(rootChild.guiElement);
226            }
[545]227        }
[820]228
[545]229        return roots;
230    }
[1086]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    }
[545]241
242    /**
[1088]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    /**
[746]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     *
[820]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
[643]271     */
[746]272    public void dump(OutputStream out, String encoding) {
[643]273        PrintStream stream;
[820]274
[643]275        if (out instanceof PrintStream) {
276            stream = (PrintStream) out;
277        }
278        else {
[746]279            String enc = encoding == null ? "UTF-8" : encoding;
280            try {
281                stream = new PrintStream(out, true, enc);
282            }
283            catch (UnsupportedEncodingException e) {
[747]284                throw new IllegalArgumentException("encodind " + enc + " not supported");
[746]285            }
[643]286        }
[820]287
[1086]288        for (TreeNode node : root.children) {
289            dumpGUIElement(stream, node, "");
[643]290        }
291    }
292
293    /**
[576]294     * <p>
[1260]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 + " <> " + parentNode);
348            }
349        }
350       
351        TreeNode replacement = new TreeNode();
352        replacement.guiElement = new GUIElementGroup(groupName, parent.guiElement);
353       
354        for (TreeNode child : nodesToGroup) {
355            ((GUIElementGroup) replacement.guiElement).addToGroup(child.guiElement);
356            replacement.addChildNode(child);
357            ((AbstractDefaultGUIElement) child.guiElement).setParent(replacement.guiElement);
358            parent.children.remove(child);
359        }
360
361        parent.children.add(replacement);
362
363        // finally, update the known nodes list
364        // if you don't do this getChildren will return wrong things and very bad things happen!
365        allNodes.put(replacement.guiElement, replacement);
366       
367        return replacement.guiElement;
368    }
369   
370    /**
371     * <p>
[821]372     * By calling this method, the GUIModel is traversed and similar nodes are merged.
[576]373     * </p>
[820]374     *
375     */
376    public void condenseModel() {
[821]377        mergeSubTree(root);
[820]378    }
[846]379   
380    /**
381     * <p>
382     * Merges the tree nodes of two GUI elements. The GUI elements need to have the same parent.
[1260]383     * They are merged recursively, i.e. also their children are merged.
[846]384     * </p>
385     *
386     * @param guiElement1
387     *            the first merge GUI element
388     * @param guiElement2
389     *            the second merge GUI element
[1260]390     *           
391     * @return the result of the merge
392     *           
[846]393     * @throws IllegalArgumentException
394     *             thrown if the two GUI elements do not have the same parent
395     */
[1260]396    public IGUIElement mergeGUIElements(IGUIElement guiElement1, IGUIElement guiElement2)
[846]397        throws IllegalArgumentException
398    {
[1260]399        return mergeGUIElements(guiElement1, guiElement2, true);
400    }
401   
402    /**
403     * <p>
404     * Merges the tree nodes of two GUI elements. The GUI elements need to have the same parent.
405     * If the <code>recursively</code> parameter is set to true, the children of the GUI elements
406     * are merged, as well, as long as they are similar. If the parameter is false, the children
407     * are not merged. In this case the resulting GUI element has all children of both merged GUI
408     * elements.
409     * </p>
410     *
411     * @param guiElement1
412     *            the first merge GUI element
413     * @param guiElement2
414     *            the second merge GUI element
415     * @param recursively
416     *            if true, the merge is done also for similar children, if false, not.
417     *           
418     * @return the result of the merge
419     *           
420     * @throws IllegalArgumentException
421     *             thrown if the two GUI elements do not have the same parent
422     */
423    public IGUIElement mergeGUIElements(IGUIElement guiElement1,
424                                        IGUIElement guiElement2,
425                                        boolean     recursively)
426        throws IllegalArgumentException
427    {
[846]428        // check if both nodes have the same parent
429        IGUIElement parentElement = guiElement1.getParent();
430        if (parentElement != null && !parentElement.equals(guiElement2.getParent())) {
431            throw new IllegalArgumentException("can only merge nodes with the same parent");
432        }
[820]433
[846]434        // get the TreeNode of the parent of the GUI elements
435        TreeNode parent = findNode(parentElement);
436
437        // get the TreeNodes for both GUI elements
438        TreeNode node1 = findNode(guiElement1);
439        TreeNode node2 = findNode(guiElement2);
440
441        if (node1 == null || node2 == null) {
442            throw new IllegalArgumentException(
443                                               "Error while merging nodes: one element is not part of the GUI model!");
444        }
445
[1260]446        TreeNode replacement = mergeTreeNodes(node1, node2, recursively);
[846]447
448        if (parent != null) {
449            // remove node1 and node2 from the parent's children and add the replacement instead
450            // assumes that there are no duplicates of node1 and node2
451            if (parent.children != null) {
452                parent.children.set(parent.children.indexOf(node1), replacement);
453                parent.children.remove(node2);
454            }
455        }
456
[1260]457        return replacement.guiElement;
[846]458    }
459
[820]460    /**
461     * <p>
462     * internally integrates a path as the children of the provided parent node. This method is
463     * recursive and calls itself, for the child of the parent node, that matches the first element
464     * in the remaining path.
465     * </p>
466     *
467     * @param parentNode
468     *            the parent node to add children for
469     * @param guiElementPath
470     *            the path of children to be created starting with the parent node
471     * @param guiElementFactory
472     *            the GUI element factory to be used for instantiating GUI element objects
473     *
[746]474     * @return The GUI element object representing the GUI element denoted by the provided path
475     *
[820]476     * @throws GUIModelException
477     *             thrown in cases such as the GUI element object could not be instantiated
[545]478     */
[820]479    private IGUIElement integratePath(TreeNode parentNode,
[576]480                                      List<? extends IGUIElementSpec> remainingPath,
[820]481                                      IGUIElementFactory guiElementFactory)
[576]482        throws GUIModelException
483    {
484        IGUIElementSpec specToIntegrateElementFor = remainingPath.remove(0);
[611]485
[820]486        TreeNode child = findEqualChild(parentNode, specToIntegrateElementFor);
[821]487        if (child == null) {
[820]488            IGUIElement newElement =
489                guiElementFactory.instantiateGUIElement(specToIntegrateElementFor,
490                                                        parentNode.guiElement);
[611]491
[820]492            child = parentNode.addChild(newElement);
[1260]493            allNodes.put(child.guiElement, child);
[545]494        }
[820]495
[576]496        if (remainingPath.size() > 0) {
[820]497            return integratePath(child, remainingPath, guiElementFactory);
[576]498        }
499        else {
[820]500            return child.guiElement;
[576]501        }
[545]502    }
503
[576]504    /**
505     * <p>
[821]506     * Searches the children of a tree node to see if the {@link IGUIElementSpec} of equals the
507     * specification of the {@link TreeNode#guiElement} of the child. If a match is found, the child
508     * is returned.
[611]509     * </p>
[831]510     *
511     * @param parentNode
512     *            parent node whose children are searched
513     * @param specToMatch
514     *            specification that is searched for
515     * @return matching child node or null if no child matches
[611]516     */
[820]517    private TreeNode findEqualChild(TreeNode parentNode, IGUIElementSpec specToMatch) {
[611]518        if (parentNode.children != null) {
519            for (TreeNode child : parentNode.children) {
[820]520                if (specToMatch.equals(child.guiElement.getSpecification())) {
521                    return child;
[611]522                }
523            }
524        }
[820]525        return null;
[611]526    }
527
528    /**
529     * <p>
[821]530     * Merges all similar nodes in the sub-tree of the GUI model defined by the subTreeRoot.
[611]531     * </p>
[821]532     * <p>
533     * The merging order is a bottom-up. This means, that we first call mergeSubTree recursively for
534     * the grand children of the subTreeRoot, before we merge subTreeRoot.
535     * </p>
536     * <p>
537     * The merging strategy is top-down. This means, that every time we merge two child nodes, we
538     * call mergeSubTree recursively for all children of the merged nodes in order to check if we
539     * can merge the children, too.
540     * </p>
[831]541     *
542     * @param subTreeRoot
543     *            root node of the sub-tree that is merged
[611]544     */
[821]545    private void mergeSubTree(TreeNode subTreeRoot) {
546        if (subTreeRoot.children == null || subTreeRoot.children.isEmpty()) {
[820]547            return;
[611]548        }
[820]549
[821]550        // lets first merge the grand children of the parentNode
551        for (TreeNode child : subTreeRoot.children) {
552            mergeSubTree(child);
[611]553        }
554
555        boolean performedMerge;
[820]556
[611]557        do {
558            performedMerge = false;
[821]559            for (int i = 0; !performedMerge && i < subTreeRoot.children.size(); i++) {
[820]560                IGUIElementSpec elemSpec1 =
[821]561                    subTreeRoot.children.get(i).guiElement.getSpecification();
562                for (int j = i + 1; !performedMerge && j < subTreeRoot.children.size(); j++) {
[820]563                    IGUIElementSpec elemSpec2 =
[821]564                        subTreeRoot.children.get(j).guiElement.getSpecification();
[820]565                    if (elemSpec1.getSimilarity(elemSpec2)) {
[1260]566                        TreeNode replacement = mergeTreeNodes
567                            (subTreeRoot.children.get(i), subTreeRoot.children.get(j), true);
[820]568
[821]569                        subTreeRoot.children.set(i, replacement);
570                        subTreeRoot.children.remove(j);
[820]571                        performedMerge = true;
572                        i--;
573                        break;
[611]574                    }
575                }
576            }
577        }
578        while (performedMerge);
579    }
580
581    /**
582     * <p>
[820]583     * merges two nodes with each other. Merging means registering the GUI element objects with each
[1260]584     * other for equality checks. Further it adds all children of both nodes to a new replacing
585     * node. Afterwards, all similar nodes of the replacement node are merged as well as long
586     * the recursive parameter is set to true.
[611]587     * </p>
[820]588     *
[831]589     * @param treeNode1
590     *            the first of the two nodes to be merged
591     * @param treeNode2
592     *            the second of the two nodes to be merged
[1260]593     * @param recursively
594     *            if true, the merging also merges child nodes
595     *           
[746]596     * @return a tree node being the merge of the two provided nodes.
[611]597     */
[1260]598    private TreeNode mergeTreeNodes(TreeNode treeNode1, TreeNode treeNode2, boolean recursively) {
[820]599        // and now a replacement node that is the merge of treeNode1 and treeNode2 is created
600        TreeNode replacement = new TreeNode();
601        replacement.guiElement = treeNode1.guiElement;
[611]602        if (treeNode1.children != null) {
603            for (TreeNode child : treeNode1.children) {
[820]604                replacement.addChildNode(child);
[611]605            }
606        }
607        if (treeNode2.children != null) {
608            for (TreeNode child : treeNode2.children) {
[820]609                replacement.addChildNode(child);
[611]610            }
611        }
[820]612
[1260]613        if (recursively) {
614            mergeSubTree(replacement);
615        }
[820]616
[611]617        replacement.guiElement.updateSpecification(treeNode2.guiElement.getSpecification());
[820]618
619        // finally, update the known nodes list
620        // if you don't do this getChildren will return wrong things and very bad things happen!
[1260]621        allNodes.remove(treeNode1.guiElement);
622        allNodes.remove(treeNode2.guiElement);
[820]623
[1260]624        // the following two lines are needed to preserve the references to the existing GUI
625        // elements. If two elements are the same, one should be deleted to make the elements
626        // singletons again. However, there may exist references to both objects. To preserve
627        // these, we simply register the equal GUI elements with each other so that an equals
628        // check can return true.
629        treeNode1.guiElement.addEqualGUIElement(treeNode2.guiElement);
630        treeNode2.guiElement.addEqualGUIElement(treeNode1.guiElement);
631       
632        allNodes.put(replacement.guiElement, replacement);
633
[611]634        return replacement;
635    }
636
637    /**
[746]638     * <p>
[820]639     * dumps a GUI element to the stream. A dump contains the toString() representation of the GUI
[1086]640     * element as well as a indented list of its children surrounded by braces. Therefore, not the
641     * GUI element itself but its tree node is provided to have an efficient access to its children
[746]642     * </p>
[831]643     *
644     * @param out
645     *            {@link PrintStream} where the guiElement is dumped to
[1086]646     * @param node
647     *            the guiElement's tree node of which the string representation is dumped
[831]648     * @param indent
649     *            indent string of the dumping
[643]650     */
[1086]651    private void dumpGUIElement(PrintStream out, TreeNode node, String indent) {
[643]652        out.print(indent);
[1086]653        out.print(node.guiElement);
[643]654
[1086]655        if ((node.children != null) && (node.children.size() > 0)) {
[643]656            out.println(" {");
657
[1086]658            for (TreeNode child : node.children) {
[643]659                dumpGUIElement(out, child, indent + "  ");
660            }
661
662            out.print(indent);
663            out.print("}");
664        }
665
666        out.println();
667    }
[846]668   
669    /**
670     * <p>
671     * Retrieves the TreeNode associated with a GUI element. Returns null if no such TreeNode is
672     * found.
673     * </p>
674     *
675     * @param element
676     *            the GUI element
677     * @return associated TreeNode; null if no such node exists
678     */
679    private TreeNode findNode(IGUIElement element) {
680        if (element == null) {
681            return null;
682        }
[643]683
[846]684        TreeNode result = null;
[1260]685       
686        if (!validate) {
687            result = allNodes.get(element);
688        }
689        else {
690            for (Map.Entry<IGUIElement, TreeNode> entry : allNodes.entrySet()) {
691                if (entry.getKey().equals(element)) {
692                    if (result == null) {
693                        result = entry.getValue();
694                    }
695                    else {
696                        Console.traceln(Level.SEVERE, "Multiple nodes in the internal GUI model " +
697                                        "match the same GUI element. This should not be the case " +
698                                        "and the GUI model is probably invalid.");
699                    }
[846]700                }
701            }
702        }
703        return result;
704    }
705
[643]706    /**
[611]707     * <p>
[1086]708     * Used externally for tree traversal without providing direct access to the tree nodes
709     * </p>
710     *
711     * @version 1.0
712     * @author Patrick Harms, Steffen Herbold
713     */
714    public class Traverser {
715       
716        /**
717         * <p>
718         * the stack of nodes on which the traverser currently works
719         * </p>
720         */
721        private Stack<StackEntry> nodeStack = new Stack<StackEntry>();
722       
723        /**
724         * <p>
725         * initializes the traverser by adding the root node of the GUI model to the stack
726         * </p>
727         */
728        private Traverser() {
729            nodeStack.push(new StackEntry(root, 0));
730        }
731       
732        /**
733         * <p>
734         * returns the first child of the current GUI element. On the first call of this method on
735         * the traverser the first of the root GUI elements of the GUI model is returned. If the
736         * current GUI element does not have children, the method returns null. If the GUI model
737         * is empty, then a call to this method will return null. The returned GUI element is the
738         * next one the traverser points to.
739         * </p>
740         *
741         * @return as described.
742         */
743        public IGUIElement firstChild() {
744            return pushChild(0);
745        }
746       
747        /**
748         * <p>
749         * returns true, if the current GUI element has a first child, i.e. if the next call to the
750         * method {@link #firstChild()} would return a GUI element or null.
751         * </p>
752         *
753         * @return as described
754         */
755        public boolean hasFirstChild() {
756            return
757                (nodeStack.peek().treeNode.children != null) &&
758                (nodeStack.peek().treeNode.children.size() > 0);
759        }
760       
761        /**
762         * <p>
763         * returns the next sibling of the current GUI element. If there is no further sibling,
764         * null is returned. If the current GUI element is one of the root nodes, the next root
765         * node of the GUI model is returned. The returned GUI element is the next one the
766         * traverser points to.
767         * </p>
768         *
769         * @return as described
770         */
771        public IGUIElement nextSibling() {
772            int lastIndex = nodeStack.pop().index;
773           
774            IGUIElement retval = pushChild(lastIndex + 1);
775            if (retval == null) {
776                pushChild(lastIndex);
777            }
778           
779            return retval;
780        }
781       
782        /**
783         * <p>
784         * returns true, if the current GUI element has a further sibling, i.e. if a call to the
785         * method {@link #nextSibling()} will return a GUI element;
786         * </p>
787         *
788         * @return as described
789         */
790        public boolean hasNextSibling() {
[1088]791            boolean result = false;
792            if (nodeStack.size() > 1) {
793                StackEntry entry = nodeStack.pop();
794                result = nodeStack.peek().treeNode.children.size() > (entry.index + 1);
795                pushChild(entry.index);
796            }
797           
[1086]798            return result;
799        }
800       
801        /**
802         * <p>
803         * returns the parent GUI element of the current GUI element. If the current GUI element
804         * is a root node, null is returned. If there is no current GUI element yet as the method
805         * {@link #firstChild()} was not called yet, null is returned.
806         * </p>
807         *
808         * @return as described
809         */
810        public IGUIElement parent() {
811            IGUIElement retval = null;
812           
813            if (nodeStack.size() > 1) {
814                nodeStack.pop();
815                retval = nodeStack.peek().treeNode.guiElement;
816            }
817           
818            return retval;
819        }
820       
821        /**
822         * <p>
823         * internal method used for changing the state of the traverser. I.e. to switch to a
824         * specific child GUI element of the current one.
825         * </p>
826         */
827        private IGUIElement pushChild(int index) {
828            IGUIElement retVal = null;
829           
830            if ((nodeStack.peek().treeNode.children != null) &&
831                (nodeStack.peek().treeNode.children.size() > index))
832            {
833                nodeStack.push
834                    (new StackEntry(nodeStack.peek().treeNode.children.get(index), index));
835                retVal = nodeStack.peek().treeNode.guiElement;
836            }
837           
838            return retVal;
839        }
840       
841        /**
842         * <p>
[1088]843         * navigates the traverser to the given node in the GUI model
844         * </p>
845         */
846        private boolean navigateTo(TreeNode node) {
847            if (hasFirstChild()) {
848                IGUIElement childElement = firstChild();
849           
850                while (childElement != null) {
851                    if (childElement.equals(node.guiElement)) {
852                        return true;
853                    }
854                    else if (navigateTo(node)) {
855                        return true;
856                    }
857                    else {
858                        childElement = nextSibling();
859                    }
860                }
861           
862                parent();
863            }
864           
865            return false;
866        }
867
868        /**
869         * <p>
[1086]870         * internal class needed to fill the stack with nodes of the GUI models and their
871         * respective index in the children of the parent node.
872         * </p>
873         */
874        private class StackEntry {
875           
876            /** */
877            private TreeNode treeNode;
878           
879            /** */
880            private int index;
881           
882            /**
883             * <p>
884             * creates a new stack entry.
885             * </p>
886             */
887            private StackEntry(TreeNode treeNode, int index) {
888                this.treeNode = treeNode;
889                this.index = index;
890            }
891        }
892    }
893
894    /**
895     * <p>
[831]896     * Used internally for building up the tree of GUI elements.
[576]897     * </p>
[831]898     *
899     * @version 1.0
900     * @author Patrick Harms, Steffen Herbold
[576]901     */
[1181]902    private static class TreeNode implements Serializable {
[831]903
[1116]904        /**  */
905        private static final long serialVersionUID = 1L;
906
[831]907        /**
908         * <p>
909         * GUI element associated with the TreeNode.
910         * </p>
911         */
[576]912        private IGUIElement guiElement;
[820]913
[831]914        /**
915         * <p>
916         * Children of the TreeNode.
917         * </p>
918         */
[576]919        private List<TreeNode> children;
[820]920
[576]921        /**
922         * <p>
[831]923         * Adds a child to the current node while keeping all lists of nodes up to date
[576]924         * </p>
[831]925         *
926         * @param guiElement
927         *            GUI element that will be associated with the new child
928         * @return the added child
[576]929         */
[820]930        private TreeNode addChild(IGUIElement guiElement) {
931            if (children == null) {
[576]932                children = new ArrayList<TreeNode>();
933            }
[820]934
[576]935            TreeNode child = new TreeNode();
936            child.guiElement = guiElement;
937            children.add(child);
[820]938
[576]939            return child;
940        }
[709]941
[820]942        /**
943         *
944         * <p>
[821]945         * Adds a TreeNode as child to the current node. This way, the whole sub-tree is added.
[820]946         * </p>
[831]947         *
948         * @param node
949         *            child node that is added
950         * @return node that has been added
[820]951         */
952        private TreeNode addChildNode(TreeNode node) {
953            if (children == null) {
954                children = new ArrayList<TreeNode>();
955            }
956            children.add(node);
957            return node;
958        }
959
960        /*
961         * (non-Javadoc)
962         *
[709]963         * @see java.lang.Object#toString()
964         */
965        @Override
966        public String toString() {
967            return guiElement.toString();
968        }
[1260]969
[576]970    }
[545]971}
Note: See TracBrowser for help on using the repository browser.