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

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