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

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