source: trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultGuiElementSequenceDetectionRule.java @ 1044

Last change on this file since 1044 was 993, checked in by pharms, 12 years ago
  • prevented a null pointer in the case, an already existing subsequence does not have a common GUI element
File size: 18.6 KB
Line 
1package de.ugoe.cs.autoquest.tasktrees.temporalrelation;
2
3import java.util.ArrayList;
4import java.util.List;
5
6import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
7import de.ugoe.cs.autoquest.tasktrees.treeifc.IEventTask;
8import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
9import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeBuilder;
10import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNode;
11import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskTreeNodeFactory;
12
13/**
14 * This rule structures the task tree based on GUI elements of the GUI model. The rule can
15 * be provided with a filter for considered GUI elements. It generates sub sequences for any
16 * GUI element in the hierarchy matching the filter so that each sequence represents all
17 * interactions in a certain GUI element.
18 *
19 * @version $Revision: $ $Date: 18.03.2012$
20 * @author 2012, last modified by $Author: patrick$
21 */
22public class DefaultGuiElementSequenceDetectionRule implements TemporalRelationshipRule {
23
24    /**
25     * <p>
26     * the GUI element filter to be applied or null if none is specified.
27     * </p>
28     */
29    private List<Class<? extends IGUIElement>> guiElementFilter;
30
31    /**
32     * <p>
33     * instantiates the rule without a GUI element filter
34     * </p>
35     */
36    DefaultGuiElementSequenceDetectionRule() {
37        this.guiElementFilter = null;
38    }
39
40    /**
41     * <p>
42     * instantiates the rule with a GUI element filter. Only those types given in the filter will
43     * be considered during the rule application. For all other types, no subsequences will be
44     * created.
45     * </p>
46     *
47     * @param guiElementFilter the GUI element filter to be applied
48     */
49    DefaultGuiElementSequenceDetectionRule(List<Class<? extends IGUIElement>> guiElementFilter) {
50        this.guiElementFilter = guiElementFilter;
51    }
52
53    /*
54     * (non-Javadoc)
55     *
56     * @see de.ugoe.cs.tasktree.temporalrelation.TemporalRelationshipRule#apply(TaskTreeNode,
57     * TaskTreeBuilder, TaskTreeNodeFactory)
58     */
59    @Override
60    public RuleApplicationResult apply(ITaskTreeNode        parent,
61                                       ITaskTreeBuilder     builder,
62                                       ITaskTreeNodeFactory nodeFactory,
63                                       boolean              finalize)
64    {
65        if (!(parent instanceof ISequence)) {
66            return null;
67        }
68
69        RuleApplicationResult result = new RuleApplicationResult();
70        List<List<IGUIElement>> hierarchies = new ArrayList<List<IGUIElement>>();
71       
72        // collect information about the GUI hierarchy
73        int maxHierarchyDepth = 0;
74        IGUIElement guiElement;
75        List<IGUIElement> guiElements = new ArrayList<IGUIElement>();
76        List<IGUIElement> hierarchy;
77       
78        for (ITaskTreeNode child : parent.getChildren()) {
79            guiElement = getGuiElement(child);
80            guiElements.add(guiElement);
81            hierarchy = getGuiElementHierarchy(guiElement);
82            hierarchies.add(hierarchy);
83            if (hierarchy != null) {
84                maxHierarchyDepth = Math.max(maxHierarchyDepth, hierarchy.size());
85            }
86        }
87       
88        IGUIElement commonDenominator = getCommonDenominator(guiElements);
89        hierarchy = getGuiElementHierarchy(commonDenominator);
90        int initialHierarchyLevel = hierarchy != null ? hierarchy.size() : 0;
91       
92        // now generate sub sequences for the different GUI elements. Start at the hierarchy
93        // level of the children of the common denominator to ensure, that different children are
94        // found. If this level is already the maximum hierarchy depth, we do not need to condense
95        // anything.
96       
97        RuleApplicationStatus status;
98        if (initialHierarchyLevel < maxHierarchyDepth) {
99            status = generateSubSequences(parent, hierarchies, initialHierarchyLevel, finalize,
100                                          builder, nodeFactory, result);
101        }
102        else {
103            status = RuleApplicationStatus.RULE_NOT_APPLIED;
104        }
105           
106        result.setRuleApplicationStatus(status);
107       
108        return result;
109    }
110
111    /**
112     * <p>
113     * generates subsequences for all groups of children of the provided parent, that operate
114     * in different GUI elements at the provided hierarchy level. It will not generate a sub
115     * sequence for the last elements, if the rule application shall not finalize.
116     * </p>
117     *
118     * @param parent            the parent node of which the children shall be grouped
119     * @param hierarchies       the GUI hierarchies for the children of the parent
120     * @param hierarchyLevel    the current hierarchy level to be considered
121     * @param maxHierarchyDepth the maximum hierarchy depth that may apply in this application
122     * @param finalize          true, if the application shall be finalized, false else
123     * @param builder           the builder to use for generating the tree structure
124     * @param nodeFactory       the node factory to use for generating the tree structure
125     * @param result            the result of the rule application to add newly created parent
126     *                          nodes to
127     *                         
128     * @return RULE_APPLICATION_FINISHED, if at least one subsequence was generated,
129     *         RULE_APPLICATION_FEASIBLE, if the application shall not be finalized but some
130     *         children could be condensed if further data was available, and RULE_NOT_APPLIED,
131     *         if no subsequence was created and none is can be created, because no further
132     *         data is expected
133     */
134    private RuleApplicationStatus generateSubSequences(ITaskTreeNode           parent,
135                                                       List<List<IGUIElement>> hierarchies,
136                                                       int                     hierarchyLevel,
137                                                       boolean                 finalize,
138                                                       ITaskTreeBuilder        builder,
139                                                       ITaskTreeNodeFactory    nodeFactory,
140                                                       RuleApplicationResult   result)
141    {
142        IGUIElement currentParent = null;
143        List<IGUIElement> hierarchy;
144        int startingIndex = -1;
145       
146        RuleApplicationStatus status = RuleApplicationStatus.RULE_NOT_APPLIED;
147        boolean subsequenceHasStarted = false;
148        boolean exceedingGuiHierarchyDepth = false;
149        boolean nextGuiElementDiffers = false;
150       
151        currentParent = null;
152        startingIndex = -1;
153
154        int index = 0;
155        while (index < parent.getChildren().size()) {
156            hierarchy = hierarchies.get(index);
157
158            exceedingGuiHierarchyDepth =
159                hierarchy != null ? hierarchyLevel >= hierarchy.size() : true;
160            nextGuiElementDiffers =
161                subsequenceHasStarted &&
162                (exceedingGuiHierarchyDepth || !currentParent.equals(hierarchy.get(hierarchyLevel)));
163
164
165            if (!subsequenceHasStarted && !exceedingGuiHierarchyDepth) {
166                currentParent = hierarchy.get(hierarchyLevel);
167                startingIndex = index;
168                subsequenceHasStarted = true;
169            }
170            else if (nextGuiElementDiffers) {
171                status = condenseSequence(parent, hierarchies, hierarchyLevel, startingIndex,
172                                          index - 1, builder, nodeFactory, result);
173
174                if (status != null) {
175                    index = startingIndex + 1;
176                }
177               
178                if (!exceedingGuiHierarchyDepth) {
179                    currentParent = hierarchy.get(hierarchyLevel);
180                    startingIndex = index;
181                    subsequenceHasStarted = true;
182                }
183                else {
184                    currentParent = null;
185                    startingIndex = -1;
186                    subsequenceHasStarted = false;
187                }
188            }
189           
190            index++;
191        }
192
193        if (finalize) {
194            if (subsequenceHasStarted) {
195                status = condenseSequence
196                    (parent, hierarchies, hierarchyLevel, startingIndex,
197                     parent.getChildren().size() - 1, builder, nodeFactory, result);
198            }
199            else if (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED) {
200                status = RuleApplicationStatus.RULE_NOT_APPLIED;
201            }
202        }
203        else {
204            if ((currentParent != null) &&
205                (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED))
206            {
207                status = RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
208            }
209        }
210
211        return status;
212    }
213
214    /**
215     * <p>
216     * condenses a specified group of children on the provided parent to a subsequences and
217     * calls {@link #generateSubSequences(ITaskTreeNode, List, int, boolean, ITaskTreeBuilder, ITaskTreeNodeFactory, RuleApplicationResult)}
218     * for the newly created subsequence. The method does not condense subgroups consisting of
219     * only one child which is already a sequence.
220     * </p>
221     *
222     * @param parent         the parent task of which children shall be condensed
223     * @param hierarchies    the GUI element hierarchies of the children of the parent
224     * @param hierarchyLevel the currently considered GUI element hierarchy level
225     * @param startIndex     the index of the first child belonging to the subgroup
226     * @param endIndex       the index of the last child belonging to the subgroup
227     * @param builder        the builder to use for generating the tree structure
228     * @param nodeFactory    the node factory to use for generating the tree structure
229     * @param result         the result of the rule application to add newly created parent nodes to
230     *
231     * @return RULE_APPLICATION_FINISHED, if at the subsequence was generated and RULE_NOT_APPLIED,
232     *         if no subsequence was created, because only one child belonged to the group which
233     *         was already a sequence
234     */
235    private RuleApplicationStatus condenseSequence(ITaskTreeNode           parent,
236                                                   List<List<IGUIElement>> hierarchies,
237                                                   int                     hierarchyLevel,
238                                                   int                     startIndex,
239                                                   int                     endIndex,
240                                                   ITaskTreeBuilder        builder,
241                                                   ITaskTreeNodeFactory    nodeFactory,
242                                                   RuleApplicationResult   result)
243    {
244        boolean onlyASingleChildToReduce = (endIndex - startIndex) == 0;
245        boolean singleChildIsSequence = onlyASingleChildToReduce &&
246            parent.getChildren().get(startIndex) instanceof ISequence;
247
248        if (!onlyASingleChildToReduce || !singleChildIsSequence) {
249            ISequence sequence = nodeFactory.createNewSequence();
250           
251            List<List<IGUIElement>> subHierarchies = new ArrayList<List<IGUIElement>>();
252            List<IGUIElement> newHierarchy =
253                hierarchies.get(startIndex).subList(0, hierarchyLevel + 1);
254            builder.setDescription(sequence, "interactions on " +
255                                   newHierarchy.get(newHierarchy.size() - 1).getStringIdentifier());
256
257            for (int i = startIndex; i <= endIndex; i++) {
258                builder.addChild(sequence, parent.getChildren().get(startIndex));
259                builder.removeChild((ISequence) parent, startIndex);
260               
261                subHierarchies.add(hierarchies.remove(startIndex));
262            }
263
264            builder.addChild((ISequence) parent, startIndex, sequence);
265           
266            hierarchies.add(startIndex, newHierarchy);
267           
268            generateSubSequences
269                (sequence, subHierarchies, hierarchyLevel + 1, true, builder, nodeFactory, result);
270
271            result.addNewlyCreatedParentNode(sequence);
272
273            return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
274        }
275        else {
276            return null;
277        }
278
279    }
280
281    /**
282     * <p>
283     * return a common denominator for the provided list of GUI elements, i.e. a GUI element, that
284     * is part of the parent GUI hiearchy of all GUI elements in the list. If there is no common
285     * denominator, the method returns null.
286     * </p>
287     */
288    private IGUIElement getCommonDenominator(List<IGUIElement> guiElements) {
289        IGUIElement commonDenominator = null;
290       
291        if (guiElements.size() > 0) {
292            List<IGUIElement> commonDenominatorPath = new ArrayList<IGUIElement>();
293           
294            // create a reference list using the first GUI element
295            IGUIElement guiElement = guiElements.get(0);
296            while (guiElement != null) {
297                if (guiElementMatchesConsideredTypes(guiElement)) {
298                    commonDenominatorPath.add(0, guiElement);
299                }
300                guiElement = guiElement.getParent();
301            }
302           
303            if (commonDenominatorPath.size() == 0) {
304                return null;
305            }
306           
307            // for each other GUI element, check the reference list for the first element in the
308            // path, that is not common to the current one, and delete it as well as it subsequent
309            // siblings
310            List<IGUIElement> currentPath = new ArrayList<IGUIElement>();
311            for (int i = 1; i < guiElements.size(); i++) {
312                currentPath.clear();
313                guiElement = guiElements.get(i);
314                while (guiElement != null) {
315                    if (guiElementMatchesConsideredTypes(guiElement)) {
316                        currentPath.add(0, guiElement);
317                    }
318                    guiElement = guiElement.getParent();
319                }
320               
321                // determine the index of the first unequal path element
322                int index = 0;
323                while ((index < commonDenominatorPath.size()) && (index < currentPath.size()) &&
324                        commonDenominatorPath.get(index).equals(currentPath.get(index)))
325                {
326                    index++;
327                }
328               
329                // remove all elements from the common denonimator path, that do not match
330                while (index < commonDenominatorPath.size()) {
331                    commonDenominatorPath.remove(index);
332                }
333            }
334           
335            if (commonDenominatorPath.size() > 0) {
336                commonDenominator = commonDenominatorPath.get(commonDenominatorPath.size() - 1);
337            }
338        }
339       
340        return commonDenominator;
341    }
342
343    /**
344     * <p>
345     * returns the GUI element on which all interactions of the provided task takes place. If
346     * the task is a simple event task its target is returned. If the task is a parent task
347     * of several children, the common denominator of the GUI elements of all its children is
348     * returned. The method returns null, if there is no common GUI element for all events
349     * represented by the provided task.
350     * </p>
351     */
352    private IGUIElement getGuiElement(ITaskTreeNode node) {
353        if (node != null) {
354            List<IGUIElement> terminalGuiElements = new ArrayList<IGUIElement>();
355            getTerminalGuiElements(node, terminalGuiElements);
356            return getCommonDenominator(terminalGuiElements);
357        }
358        else {
359            return null;
360        }
361    }
362       
363    /**
364     * <p>
365     * recursive method calling itself to determine all terminal GUI elements of the provided
366     * task. The terminal GUI elements are stored in the provided list.
367     * </p>
368     */
369    private void getTerminalGuiElements(ITaskTreeNode node, List<IGUIElement> terminalGuiElements) {
370        if (node instanceof IEventTask) {
371            if (((IEventTask) node).getEventTarget() instanceof IGUIElement) {
372                IGUIElement terminalGuiElement = (IGUIElement) ((IEventTask) node).getEventTarget();
373                terminalGuiElement =
374                    searchHierarchyForGuiElementWithConsideredType(terminalGuiElement);
375               
376                if (terminalGuiElement != null) {
377                    terminalGuiElements.add(terminalGuiElement);
378                }
379            }
380        }
381        else {
382            for (ITaskTreeNode child : node.getChildren()) {
383                getTerminalGuiElements(child, terminalGuiElements);
384            }
385        }
386    }
387
388    /**
389     * <p>
390     * returns a list of GUI elements that represents the whole GUI element hierarchy of the
391     * provided GUI element. The method considers the GUI element filter applied by this rule.
392     * </p>
393     */
394    private List<IGUIElement> getGuiElementHierarchy(IGUIElement guiElement) {
395        IGUIElement element = guiElement;
396       
397        if (!guiElementMatchesConsideredTypes(element)) {
398            element = searchHierarchyForGuiElementWithConsideredType(element);
399        }
400       
401        List<IGUIElement> hierarchy = new ArrayList<IGUIElement>();
402       
403        while (element != null) {
404            hierarchy.add(0, element);
405            element = searchHierarchyForGuiElementWithConsideredType(element.getParent());
406        }
407       
408        if (hierarchy.size() > 0) {
409            return hierarchy;
410        }
411        else {
412            return null;
413        }
414    }
415
416    /**
417     * <p>
418     * returns for a given GUI element the next GUI element in the upper GUI element hierarchy
419     * that matches the GUI element filter of the rule. If the provided GUI element already
420     * matches the filter, it is returned directly.
421     * </p>
422     */
423    private IGUIElement searchHierarchyForGuiElementWithConsideredType(IGUIElement guiElement) {
424        IGUIElement returnValue = guiElement;
425       
426        while ((returnValue != null) && !guiElementMatchesConsideredTypes(returnValue)) {
427            returnValue = returnValue.getParent();
428        }
429       
430        return returnValue;
431    }
432
433    /**
434     * <p>
435     * checks if the provided GUI element matches the GUI element filter applied by the rule.
436     * </p>
437     */
438    private boolean guiElementMatchesConsideredTypes(IGUIElement guiElement) {
439        if (guiElementFilter == null) {
440            return true;
441        }
442        else {
443            for (Class<? extends IGUIElement> clazz : guiElementFilter) {
444                if (clazz.isInstance(guiElement)) {
445                    return true;
446                }
447            }
448           
449            return false;
450        }
451    }
452
453}
Note: See TracBrowser for help on using the repository browser.