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

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