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

Last change on this file since 987 was 987, checked in by pharms, 12 years ago
  • improvement and documentation of the creation of sub sequences based on the GUI hierarchy
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 = hierarchyLevel >= hierarchy.size();
159            nextGuiElementDiffers =
160                subsequenceHasStarted &&
161                (exceedingGuiHierarchyDepth || !currentParent.equals(hierarchy.get(hierarchyLevel)));
162
163
164            if (!subsequenceHasStarted && !exceedingGuiHierarchyDepth) {
165                currentParent = hierarchy.get(hierarchyLevel);
166                startingIndex = index;
167                subsequenceHasStarted = true;
168            }
169            else if (nextGuiElementDiffers) {
170                status = condenseSequence(parent, hierarchies, hierarchyLevel, startingIndex,
171                                          index - 1, builder, nodeFactory, result);
172
173                if (status != null) {
174                    index = startingIndex + 1;
175                }
176               
177                if (!exceedingGuiHierarchyDepth) {
178                    currentParent = hierarchy.get(hierarchyLevel);
179                    startingIndex = index;
180                    subsequenceHasStarted = true;
181                }
182                else {
183                    currentParent = null;
184                    startingIndex = -1;
185                    subsequenceHasStarted = false;
186                }
187            }
188           
189            index++;
190        }
191
192        if (finalize) {
193            if (subsequenceHasStarted) {
194                status = condenseSequence
195                    (parent, hierarchies, hierarchyLevel, startingIndex,
196                     parent.getChildren().size() - 1, builder, nodeFactory, result);
197            }
198            else if (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED) {
199                status = RuleApplicationStatus.RULE_NOT_APPLIED;
200            }
201        }
202        else {
203            if ((currentParent != null) &&
204                (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED))
205            {
206                status = RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
207            }
208        }
209
210        return status;
211    }
212
213    /**
214     * <p>
215     * condenses a specified group of children on the provided parent to a subsequences and
216     * calls {@link #generateSubSequences(ITaskTreeNode, List, int, boolean, ITaskTreeBuilder, ITaskTreeNodeFactory, RuleApplicationResult)}
217     * for the newly created subsequence. The method does not condense subgroups consisting of
218     * only one child which is already a sequence.
219     * </p>
220     *
221     * @param parent         the parent task of which children shall be condensed
222     * @param hierarchies    the GUI element hierarchies of the children of the parent
223     * @param hierarchyLevel the currently considered GUI element hierarchy level
224     * @param startIndex     the index of the first child belonging to the subgroup
225     * @param endIndex       the index of the last child belonging to the subgroup
226     * @param builder        the builder to use for generating the tree structure
227     * @param nodeFactory    the node factory to use for generating the tree structure
228     * @param result         the result of the rule application to add newly created parent nodes to
229     *
230     * @return RULE_APPLICATION_FINISHED, if at the subsequence was generated and RULE_NOT_APPLIED,
231     *         if no subsequence was created, because only one child belonged to the group which
232     *         was already a sequence
233     */
234    private RuleApplicationStatus condenseSequence(ITaskTreeNode           parent,
235                                                   List<List<IGUIElement>> hierarchies,
236                                                   int                     hierarchyLevel,
237                                                   int                     startIndex,
238                                                   int                     endIndex,
239                                                   ITaskTreeBuilder        builder,
240                                                   ITaskTreeNodeFactory    nodeFactory,
241                                                   RuleApplicationResult   result)
242    {
243        boolean onlyASingleChildToReduce = (endIndex - startIndex) == 0;
244        boolean singleChildIsSequence = onlyASingleChildToReduce &&
245            parent.getChildren().get(startIndex) instanceof ISequence;
246
247        if (!onlyASingleChildToReduce || !singleChildIsSequence) {
248            ISequence sequence = nodeFactory.createNewSequence();
249           
250            List<List<IGUIElement>> subHierarchies = new ArrayList<List<IGUIElement>>();
251            List<IGUIElement> newHierarchy =
252                hierarchies.get(startIndex).subList(0, hierarchyLevel + 1);
253            builder.setDescription(sequence, "interactions on " +
254                                   newHierarchy.get(newHierarchy.size() - 1).getStringIdentifier());
255
256            for (int i = startIndex; i <= endIndex; i++) {
257                builder.addChild(sequence, parent.getChildren().get(startIndex));
258                builder.removeChild((ISequence) parent, startIndex);
259               
260                subHierarchies.add(hierarchies.remove(startIndex));
261            }
262
263            builder.addChild((ISequence) parent, startIndex, sequence);
264           
265            hierarchies.add(startIndex, newHierarchy);
266           
267            generateSubSequences
268                (sequence, subHierarchies, hierarchyLevel + 1, true, builder, nodeFactory, result);
269
270            result.addNewlyCreatedParentNode(sequence);
271
272            return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
273        }
274        else {
275            return null;
276        }
277
278    }
279
280    /**
281     * <p>
282     * return a common denominator for the provided list of GUI elements, i.e. a GUI element, that
283     * is part of the parent GUI hiearchy of all GUI elements in the list. If there is no common
284     * denominator, the method returns null.
285     * </p>
286     */
287    private IGUIElement getCommonDenominator(List<IGUIElement> guiElements) {
288        IGUIElement commonDenominator = null;
289       
290        if (guiElements.size() > 0) {
291            List<IGUIElement> commonDenominatorPath = new ArrayList<IGUIElement>();
292           
293            // create a reference list using the first GUI element
294            IGUIElement guiElement = guiElements.get(0);
295            while (guiElement != null) {
296                if (guiElementMatchesConsideredTypes(guiElement)) {
297                    commonDenominatorPath.add(0, guiElement);
298                }
299                guiElement = guiElement.getParent();
300            }
301           
302            if (commonDenominatorPath.size() == 0) {
303                return null;
304            }
305           
306            // for each other GUI element, check the reference list for the first element in the
307            // path, that is not common to the current one, and delete it as well as it subsequent
308            // siblings
309            List<IGUIElement> currentPath = new ArrayList<IGUIElement>();
310            for (int i = 1; i < guiElements.size(); i++) {
311                currentPath.clear();
312                guiElement = guiElements.get(i);
313                while (guiElement != null) {
314                    if (guiElementMatchesConsideredTypes(guiElement)) {
315                        currentPath.add(0, guiElement);
316                    }
317                    guiElement = guiElement.getParent();
318                }
319               
320                // determine the index of the first unequal path element
321                int index = 0;
322                while ((index < commonDenominatorPath.size()) && (index < currentPath.size()) &&
323                        commonDenominatorPath.get(index).equals(currentPath.get(index)))
324                {
325                    index++;
326                }
327               
328                // remove all elements from the common denonimator path, that do not match
329                while (index < commonDenominatorPath.size()) {
330                    commonDenominatorPath.remove(index);
331                }
332            }
333           
334            if (commonDenominatorPath.size() > 0) {
335                commonDenominator = commonDenominatorPath.get(commonDenominatorPath.size() - 1);
336            }
337        }
338       
339        return commonDenominator;
340    }
341
342    /**
343     * <p>
344     * returns the GUI element on which all interactions of the provided task takes place. If
345     * the task is a simple event task its target is returned. If the task is a parent task
346     * of several children, the common denominator of the GUI elements of all its children is
347     * returned. The method returns null, if there is no common GUI element for all events
348     * represented by the provided task.
349     * </p>
350     */
351    private IGUIElement getGuiElement(ITaskTreeNode node) {
352        if (node != null) {
353            List<IGUIElement> terminalGuiElements = new ArrayList<IGUIElement>();
354            getTerminalGuiElements(node, terminalGuiElements);
355            return getCommonDenominator(terminalGuiElements);
356        }
357        else {
358            return null;
359        }
360    }
361       
362    /**
363     * <p>
364     * recursive method calling itself to determine all terminal GUI elements of the provided
365     * task. The terminal GUI elements are stored in the provided list.
366     * </p>
367     */
368    private void getTerminalGuiElements(ITaskTreeNode node, List<IGUIElement> terminalGuiElements) {
369        if (node instanceof IEventTask) {
370            if (((IEventTask) node).getEventTarget() instanceof IGUIElement) {
371                IGUIElement terminalGuiElement = (IGUIElement) ((IEventTask) node).getEventTarget();
372                terminalGuiElement =
373                    searchHierarchyForGuiElementWithConsideredType(terminalGuiElement);
374               
375                if (terminalGuiElement != null) {
376                    terminalGuiElements.add(terminalGuiElement);
377                }
378            }
379        }
380        else {
381            for (ITaskTreeNode child : node.getChildren()) {
382                getTerminalGuiElements(child, terminalGuiElements);
383            }
384        }
385    }
386
387    /**
388     * <p>
389     * returns a list of GUI elements that represents the whole GUI element hierarchy of the
390     * provided GUI element. The method considers the GUI element filter applied by this rule.
391     * </p>
392     */
393    private List<IGUIElement> getGuiElementHierarchy(IGUIElement guiElement) {
394        IGUIElement element = guiElement;
395       
396        if (!guiElementMatchesConsideredTypes(element)) {
397            element = searchHierarchyForGuiElementWithConsideredType(element);
398        }
399       
400        List<IGUIElement> hierarchy = new ArrayList<IGUIElement>();
401       
402        while (element != null) {
403            hierarchy.add(0, element);
404            element = searchHierarchyForGuiElementWithConsideredType(element.getParent());
405        }
406       
407        if (hierarchy.size() > 0) {
408            return hierarchy;
409        }
410        else {
411            return null;
412        }
413    }
414
415    /**
416     * <p>
417     * returns for a given GUI element the next GUI element in the upper GUI element hierarchy
418     * that matches the GUI element filter of the rule. If the provided GUI element already
419     * matches the filter, it is returned directly.
420     * </p>
421     */
422    private IGUIElement searchHierarchyForGuiElementWithConsideredType(IGUIElement guiElement) {
423        IGUIElement returnValue = guiElement;
424       
425        while ((returnValue != null) && !guiElementMatchesConsideredTypes(returnValue)) {
426            returnValue = returnValue.getParent();
427        }
428       
429        return returnValue;
430    }
431
432    /**
433     * <p>
434     * checks if the provided GUI element matches the GUI element filter applied by the rule.
435     * </p>
436     */
437    private boolean guiElementMatchesConsideredTypes(IGUIElement guiElement) {
438        if (guiElementFilter == null) {
439            return true;
440        }
441        else {
442            for (Class<? extends IGUIElement> clazz : guiElementFilter) {
443                if (clazz.isInstance(guiElement)) {
444                    return true;
445                }
446            }
447           
448            return false;
449        }
450    }
451
452}
Note: See TracBrowser for help on using the repository browser.