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

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