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

Last change on this file since 820 was 820, checked in by sherbold, 12 years ago
  • modified GUIModel to not automatically merge nodes during the creation of the model, but rather after the creation is completed through a call of condenseModel
  • added command condenseGuiModel
File size: 15.7 KB
Line 
1
2package de.ugoe.cs.quest.eventcore.guimodel;
3
4import java.io.OutputStream;
5import java.io.PrintStream;
6import java.io.UnsupportedEncodingException;
7import java.util.ArrayList;
8import java.util.LinkedList;
9import java.util.List;
10
11/**
12 * <p>
13 * A GUI model is a tree of {@link IGUIElements} and represents a complete GUI of a software. It is
14 * platform independent. It may have several root nodes, as some GUIs are made up of several Frames
15 * being independent from each other. The GUI model is filled using the
16 * {@link #integratePath(List, IGUIElementFactory)} method.
17 * </p>
18 *
19 * TODO: getChildren and getParent should be rewritten to at least throw warnings, in case there are
20 * multiple nodes in allNodes that match the current GUI element.
21 *
22 * @version $Revision: $ $Date: 14.08.2012$
23 * @author Patrick Harms, Steffen Herbold
24 */
25public class GUIModel {
26
27    /**
28     * <p>
29     * the root node of the tree not provided externally.
30     * </p>
31     */
32    private TreeNode root = new TreeNode();
33
34    /**
35     * <p>
36     * a list with all nodes currently known
37     * </p>
38     */
39    private List<TreeNode> allNodes = new ArrayList<TreeNode>();
40
41    /**
42     * <p>
43     * Integrates a path of GUI elements into the GUI model. The GUI model itself is a tree and
44     * therefore a set of different paths through the tree that start with a root node and end with
45     * a leaf node. Such a path can be added to the tree. The method checks, if any of the GUI
46     * elements denoted by the path already exists. If so, it reuses it. It may therefore also
47     * return an existing GUI element being the leaf node of the provided path. If a GUI element of
48     * the path does not exist yet, it creates a new one using the provided GUI element factory.
49     * </p>
50     * <p>
51     * If a GUI element specification describes an existing GUI element or not is determined through
52     * comparing the GUI element specifications of the existing GUI elements with the ones provided
53     * in the path. The comparison is done using the
54     * {@link IGUIElementSpec#getSimilarity(IGUIElementSpec)} method. The comparison is only done on
55     * the correct levels. I.e. the currently known root elements of the tree are only compared to
56     * the first element in the path. If the correct one is found or created, its children are
57     * compared only to the second specification in the path, and so on.
58     * </p>
59     * <p>
60     * The returned GUI elements are singletons. I.e. it is tried to return always the identical
61     * object for the same denoted element. However, while creating the GUI model, the similarity of
62     * GUI elements may change. Therefore, the method might determine, that two formerly different
63     * nodes are now similar. (This may happen, e.g. if GUI elements do not have initial names which
64     * are set afterwards. Therefore, first they are handled differently and later they can be
65     * identified as being the same.) In such a case, there are already several GUI element objects
66     * instantiated for the same GUI element. The singleton paradigm gets broken. Therefore, such
67     * GUI element objects are registered with each other, so that their equal method can determine
68     * equality again correctly, although the objects are no singletons anymore.
69     * </p>
70     *
71     * @param guiElementPath
72     *            the path to integrate into the model
73     * @param guiElementFactory
74     *            the GUI element factory to be used for instantiating GUI element objects
75     *
76     * @return The GUI element object representing the GUI element denoted by the provided path
77     *
78     * @throws GUIModelException
79     *             thrown in cases such as the GUI element object could not be instantiated
80     * @throws IllegalArgumentException
81     *             if the provided path is invalid.
82     */
83    public IGUIElement integratePath(List<? extends IGUIElementSpec> guiElementPath,
84                                     IGUIElementFactory guiElementFactory)
85        throws GUIModelException, IllegalArgumentException
86    {
87        if ((guiElementPath == null) || (guiElementPath.size() <= 0)) {
88            throw new IllegalArgumentException("GUI element path must contain at least one element");
89        }
90
91        List<IGUIElementSpec> remainingPath = new LinkedList<IGUIElementSpec>();
92
93        for (IGUIElementSpec spec : guiElementPath) {
94            remainingPath.add(spec);
95        }
96
97        return integratePath(root, remainingPath, guiElementFactory);
98    }
99
100    /**
101     * <p>
102     * Returns all children of the provided GUI element or null, if it does not have any or the node
103     * is unknown.
104     * </p>
105     *
106     * @param guiElement
107     *            the GUI element of which the children shall be returned
108     *
109     * @return As described
110     */
111    public List<IGUIElement> getChildren(IGUIElement guiElement) {
112        for (TreeNode node : allNodes) {
113            if (node.guiElement.equals(guiElement)) {
114                List<IGUIElement> result = new ArrayList<IGUIElement>();
115
116                if (node.children != null) {
117                    for (TreeNode child : node.children) {
118                        result.add(child.guiElement);
119                    }
120                }
121
122                return result;
123            }
124        }
125
126        return null;
127    }
128
129    /**
130     * <p>
131     * Returns the parent GUI element of the provided GUI element or null, if it does not have a
132     * parent (i.e. if it is a root node) or if the node is unknown.
133     * </p>
134     *
135     * @param guiElement
136     *            the GUI element of which the parent shall be returned
137     *
138     * @return As described
139     */
140    public IGUIElement getParent(IGUIElement guiElement) {
141        for (TreeNode node : allNodes) {
142            for (TreeNode child : node.children) {
143                if (child.guiElement.equals(guiElement)) {
144                    return node.guiElement;
145                }
146            }
147        }
148
149        return null;
150    }
151
152    /**
153     * <p>
154     * Returns all root GUI elements of the model or an empty list, if the model is empty
155     * </p>
156     *
157     * @return As described
158     */
159    public List<IGUIElement> getRootElements() {
160        List<IGUIElement> roots = new ArrayList<IGUIElement>();
161
162        if (root.children != null) {
163            for (TreeNode rootChild : root.children) {
164                roots.add(rootChild.guiElement);
165            }
166        }
167
168        return roots;
169    }
170
171    /**
172     * <p>
173     * dumps the GUI model to the provided stream. Each node is represented through its toString()
174     * method. If a node has children, those are dumped indented and surrounded by braces.
175     * </p>
176     *
177     * @param out
178     *            The stream to dump the textual representation of the model to
179     * @param encoding
180     *            The encoding to be used while dumping
181     */
182    public void dump(OutputStream out, String encoding) {
183        PrintStream stream;
184
185        if (out instanceof PrintStream) {
186            stream = (PrintStream) out;
187        }
188        else {
189            String enc = encoding == null ? "UTF-8" : encoding;
190            try {
191                stream = new PrintStream(out, true, enc);
192            }
193            catch (UnsupportedEncodingException e) {
194                throw new IllegalArgumentException("encodind " + enc + " not supported");
195            }
196        }
197
198        for (IGUIElement root : getRootElements()) {
199            dumpGUIElement(stream, root, "");
200        }
201    }
202
203    /**
204     * <p>
205     * TODO: comment
206     * </p>
207     *
208     */
209    public void condenseModel() {
210        mergeSimilarChildren(root);
211    }
212
213    /**
214     * <p>
215     * internally integrates a path as the children of the provided parent node. This method is
216     * recursive and calls itself, for the child of the parent node, that matches the first element
217     * in the remaining path.
218     * </p>
219     *
220     * @param parentNode
221     *            the parent node to add children for
222     * @param guiElementPath
223     *            the path of children to be created starting with the parent node
224     * @param guiElementFactory
225     *            the GUI element factory to be used for instantiating GUI element objects
226     *
227     * @return The GUI element object representing the GUI element denoted by the provided path
228     *
229     * @throws GUIModelException
230     *             thrown in cases such as the GUI element object could not be instantiated
231     */
232    private IGUIElement integratePath(TreeNode parentNode,
233                                      List<? extends IGUIElementSpec> remainingPath,
234                                      IGUIElementFactory guiElementFactory)
235        throws GUIModelException
236    {
237        IGUIElementSpec specToIntegrateElementFor = remainingPath.remove(0);
238
239        TreeNode child = findEqualChild(parentNode, specToIntegrateElementFor);
240        if (child != null) {
241            // TODO this call should be deprecated and change nothing
242            child.guiElement.updateSpecification(specToIntegrateElementFor);
243        }
244        else {
245            IGUIElement newElement =
246                guiElementFactory.instantiateGUIElement(specToIntegrateElementFor,
247                                                        parentNode.guiElement);
248
249            child = parentNode.addChild(newElement);
250        }
251
252        if (remainingPath.size() > 0) {
253            return integratePath(child, remainingPath, guiElementFactory);
254        }
255        else {
256            return child.guiElement;
257        }
258    }
259
260    /**
261     * <p>
262     * TODO: comment
263     * </p>
264     *
265     * @param parentNode
266     * @param specToMatch
267     * @return
268     */
269    private TreeNode findEqualChild(TreeNode parentNode, IGUIElementSpec specToMatch) {
270        if (parentNode.children != null) {
271            for (TreeNode child : parentNode.children) {
272                if (specToMatch.equals(child.guiElement.getSpecification())) {
273                    return child;
274                }
275            }
276        }
277        return null;
278    }
279
280    /**
281     * <p>
282     * merges similar children of a parent node. The method compares all children of the parent node
283     * with each other. If two of them are similar, it merges them, registers them with each other
284     * for equality checks, and removes one of them from the list of children.
285     * </p>
286     */
287    private void mergeSimilarChildren(TreeNode parentNode) {
288        if (parentNode.children == null || parentNode.children.isEmpty()) {
289            return;
290        }
291
292        // lets first merge the grandchildren
293        for (TreeNode child : parentNode.children) {
294            mergeSimilarChildren(child);
295        }
296
297        boolean performedMerge;
298
299        do {
300            performedMerge = false;
301            for (int i = 0; !performedMerge && i < parentNode.children.size(); i++) {
302                IGUIElementSpec elemSpec1 =
303                    parentNode.children.get(i).guiElement.getSpecification();
304                for (int j = i + 1; !performedMerge && j < parentNode.children.size(); j++) {
305                    IGUIElementSpec elemSpec2 =
306                        parentNode.children.get(j).guiElement.getSpecification();
307                    if (elemSpec1.getSimilarity(elemSpec2)) {
308                        TreeNode replacement =
309                            mergeTreeNodes(parentNode.children.get(i), parentNode.children.get(j));
310
311                        parentNode.children.set(i, replacement);
312                        parentNode.children.remove(j);
313                        performedMerge = true;
314                        i--;
315                        break;
316                    }
317                }
318            }
319        }
320        while (performedMerge);
321    }
322
323    /**
324     * <p>
325     * merges two nodes with each other. Merging means registering the GUI element objects with each
326     * other for equality checks. Further it add all children of both nodes to a new replacing node.
327     * Afterwards, all similar nodes of the replacement node are merged as well.
328     * </p>
329     *
330     * @return a tree node being the merge of the two provided nodes.
331     */
332    private TreeNode mergeTreeNodes(TreeNode treeNode1, TreeNode treeNode2) {
333        // the following two lines are needed to preserve the references to the existing GUI
334        // elements. If two elements are the same, one should be deleted to make the elements
335        // singletons again. However, there may exist references to both objects. To preserve
336        // these, we simply register the equal GUI elements with each other so that an equals
337        // check can return true.
338        treeNode1.guiElement.addEqualGUIElement(treeNode2.guiElement);
339        treeNode2.guiElement.addEqualGUIElement(treeNode1.guiElement);
340
341        // and now a replacement node that is the merge of treeNode1 and treeNode2 is created
342        TreeNode replacement = new TreeNode();
343        replacement.guiElement = treeNode1.guiElement;
344        if (treeNode1.children != null) {
345            for (TreeNode child : treeNode1.children) {
346                // replacement.addChild(child.guiElement);
347                replacement.addChildNode(child);
348            }
349        }
350        if (treeNode2.children != null) {
351            for (TreeNode child : treeNode2.children) {
352                // replacement.addChild(child.guiElement);
353                replacement.addChildNode(child);
354            }
355        }
356
357        mergeSimilarChildren(replacement);
358
359        replacement.guiElement.updateSpecification(treeNode2.guiElement.getSpecification());
360
361        // finally, update the known nodes list
362        // if you don't do this getChildren will return wrong things and very bad things happen!
363        allNodes.remove(treeNode1);
364        allNodes.remove(treeNode2);
365        allNodes.add(replacement);
366
367        return replacement;
368    }
369
370    /**
371     * <p>
372     * dumps a GUI element to the stream. A dump contains the toString() representation of the GUI
373     * element as well as a indented list of its children surrounded by braces.
374     * </p>
375     */
376    private void dumpGUIElement(PrintStream out, IGUIElement guiElement, String indent) {
377        out.print(indent);
378        out.print(guiElement);
379
380        List<IGUIElement> children = getChildren(guiElement);
381
382        if ((children != null) && (children.size() > 0)) {
383            out.println(" {");
384
385            for (IGUIElement child : children) {
386                dumpGUIElement(out, child, indent + "  ");
387            }
388
389            out.print(indent);
390            out.print("}");
391        }
392
393        out.println();
394    }
395
396    /**
397     * <p>
398     * used internally for building up the tree of GUI elements.
399     * </p>
400     */
401    private class TreeNode {
402        /** */
403        private IGUIElement guiElement;
404
405        /** */
406        private List<TreeNode> children;
407
408        /** */
409        // private TreeNode parent;
410
411        /**
412         * <p>
413         * adds a child to the current node while keeping all lists of nodes up to date
414         * </p>
415         */
416        private TreeNode addChild(IGUIElement guiElement) {
417            if (children == null) {
418                children = new ArrayList<TreeNode>();
419            }
420
421            TreeNode child = new TreeNode();
422            child.guiElement = guiElement;
423            // child.parent = this;
424            children.add(child);
425
426            allNodes.add(child);
427
428            return child;
429        }
430
431        /**
432         *
433         * <p>
434         * TODO: comment
435         * </p>
436         *
437         * @param node
438         * @return
439         */
440        private TreeNode addChildNode(TreeNode node) {
441            if (children == null) {
442                children = new ArrayList<TreeNode>();
443            }
444            children.add(node);
445            return node;
446        }
447
448        /*
449         * (non-Javadoc)
450         *
451         * @see java.lang.Object#toString()
452         */
453        @Override
454        public String toString() {
455            return guiElement.toString();
456        }
457    }
458}
Note: See TracBrowser for help on using the repository browser.