source: trunk/autoquest-core-events/src/main/java/de/ugoe/cs/autoquest/eventcore/HierarchicalEventTargetModel.java @ 2252

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