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

Last change on this file since 1117 was 1117, checked in by pharms, 11 years ago
  • added toString methods
File size: 20.2 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    /* (non-Javadoc)
102     * @see java.lang.Object#toString()
103     */
104    @Override
105    public String toString() {
106        return "DefaultGuiElementSequenceDetectionRule";
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.RULE_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.RULE_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        while (index < parent.getChildren().size()) {
205            hierarchy = hierarchies.get(index);
206
207            exceedingGuiHierarchyDepth =
208                hierarchy != null ? hierarchyLevel >= hierarchy.size() : true;
209            nextGuiElementDiffers =
210                subsequenceHasStarted &&
211                (exceedingGuiHierarchyDepth || !currentParent.equals(hierarchy.get(hierarchyLevel)));
212
213
214            if (!subsequenceHasStarted && !exceedingGuiHierarchyDepth) {
215                currentParent = hierarchy.get(hierarchyLevel);
216                startingIndex = index;
217                subsequenceHasStarted = true;
218            }
219            else if (nextGuiElementDiffers) {
220                status = condenseSequence
221                    (parent, hierarchies, hierarchyLevel, startingIndex, index - 1, result);
222
223                if (status != null) {
224                    index = startingIndex + 1;
225                }
226               
227                if (!exceedingGuiHierarchyDepth) {
228                    currentParent = hierarchy.get(hierarchyLevel);
229                    startingIndex = index;
230                    subsequenceHasStarted = true;
231                }
232                else {
233                    currentParent = null;
234                    startingIndex = -1;
235                    subsequenceHasStarted = false;
236                }
237            }
238           
239            index++;
240        }
241
242        if (finalize) {
243            if (subsequenceHasStarted) {
244                status = condenseSequence
245                    (parent, hierarchies, hierarchyLevel, startingIndex,
246                     parent.getChildren().size() - 1, result);
247            }
248            else if (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED) {
249                status = RuleApplicationStatus.RULE_NOT_APPLIED;
250            }
251        }
252        else {
253            if ((currentParent != null) &&
254                (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED))
255            {
256                status = RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
257            }
258        }
259
260        return status;
261    }
262
263    /**
264     * <p>
265     * condenses a specified group of children on the provided parent to a subsequences and
266     * calls {@link #generateSubSequences(ITaskTreeNode, List, int, boolean, ITaskTreeBuilder, ITaskTreeNodeFactory, RuleApplicationResult)}
267     * for the newly created subsequence. The method does not condense subgroups consisting of
268     * only one child which is already a sequence.
269     * </p>
270     *
271     * @param parent         the parent task of which children shall be condensed
272     * @param hierarchies    the GUI element hierarchies of the children of the parent
273     * @param hierarchyLevel the currently considered GUI element hierarchy level
274     * @param startIndex     the index of the first child belonging to the subgroup
275     * @param endIndex       the index of the last child belonging to the subgroup
276     * @param result         the result of the rule application to add newly created parent nodes to
277     *
278     * @return RULE_APPLICATION_FINISHED, if at the subsequence was generated and RULE_NOT_APPLIED,
279     *         if no subsequence was created, because only one child belonged to the group which
280     *         was already a sequence
281     */
282    private RuleApplicationStatus condenseSequence(ITaskTreeNode           parent,
283                                                   List<List<IGUIElement>> hierarchies,
284                                                   int                     hierarchyLevel,
285                                                   int                     startIndex,
286                                                   int                     endIndex,
287                                                   RuleApplicationResult   result)
288    {
289        boolean onlyASingleChildToReduce = (endIndex - startIndex) == 0;
290        boolean singleChildIsSequence = onlyASingleChildToReduce &&
291            parent.getChildren().get(startIndex) instanceof ISequence;
292
293        if (!onlyASingleChildToReduce || !singleChildIsSequence) {
294            ISequence sequence = taskTreeNodeFactory.createNewSequence();
295           
296            List<List<IGUIElement>> subHierarchies = new ArrayList<List<IGUIElement>>();
297            List<IGUIElement> newHierarchy =
298                hierarchies.get(startIndex).subList(0, hierarchyLevel + 1);
299            taskTreeBuilder.setDescription
300                (sequence, "interactions on " +
301                 newHierarchy.get(newHierarchy.size() - 1).getStringIdentifier());
302
303            for (int i = startIndex; i <= endIndex; i++) {
304                taskTreeBuilder.addChild(sequence, parent.getChildren().get(startIndex));
305                taskTreeBuilder.removeChild((ISequence) parent, startIndex);
306               
307                subHierarchies.add(hierarchies.remove(startIndex));
308            }
309
310            taskTreeBuilder.addChild((ISequence) parent, startIndex, sequence);
311           
312            hierarchies.add(startIndex, newHierarchy);
313           
314            generateSubSequences(sequence, subHierarchies, hierarchyLevel + 1, true, result);
315
316            result.addNewlyCreatedParentNode(sequence);
317
318            return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
319        }
320        else {
321            return null;
322        }
323
324    }
325
326    /**
327     * <p>
328     * return a common denominator for the provided list of GUI elements, i.e. a GUI element, that
329     * is part of the parent GUI hiearchy of all GUI elements in the list. If there is no common
330     * denominator, the method returns null.
331     * </p>
332     */
333    private IGUIElement getCommonDenominator(List<IGUIElement> guiElements) {
334        IGUIElement commonDenominator = null;
335       
336        if (guiElements.size() > 0) {
337            List<IGUIElement> commonDenominatorPath = new ArrayList<IGUIElement>();
338           
339            // create a reference list using the first GUI element
340            IGUIElement guiElement = guiElements.get(0);
341            while (guiElement != null) {
342                if (guiElementMatchesConsideredTypes(guiElement)) {
343                    commonDenominatorPath.add(0, guiElement);
344                }
345                guiElement = guiElement.getParent();
346            }
347           
348            if (commonDenominatorPath.size() == 0) {
349                return null;
350            }
351           
352            // for each other GUI element, check the reference list for the first element in the
353            // path, that is not common to the current one, and delete it as well as it subsequent
354            // siblings
355            List<IGUIElement> currentPath = new ArrayList<IGUIElement>();
356            for (int i = 1; i < guiElements.size(); i++) {
357                currentPath.clear();
358                guiElement = guiElements.get(i);
359                while (guiElement != null) {
360                    if (guiElementMatchesConsideredTypes(guiElement)) {
361                        currentPath.add(0, guiElement);
362                    }
363                    guiElement = guiElement.getParent();
364                }
365               
366                // determine the index of the first unequal path element
367                int index = 0;
368                while ((index < commonDenominatorPath.size()) && (index < currentPath.size()) &&
369                        commonDenominatorPath.get(index).equals(currentPath.get(index)))
370                {
371                    index++;
372                }
373               
374                // remove all elements from the common denonimator path, that do not match
375                while (index < commonDenominatorPath.size()) {
376                    commonDenominatorPath.remove(index);
377                }
378            }
379           
380            if (commonDenominatorPath.size() > 0) {
381                commonDenominator = commonDenominatorPath.get(commonDenominatorPath.size() - 1);
382            }
383        }
384       
385        return commonDenominator;
386    }
387
388    /**
389     * <p>
390     * returns the GUI element on which all interactions of the provided task takes place. If
391     * the task is a simple event task its target is returned. If the task is a parent task
392     * of several children, the common denominator of the GUI elements of all its children is
393     * returned. The method returns null, if there is no common GUI element for all events
394     * represented by the provided task.
395     * </p>
396     */
397    private IGUIElement getGuiElement(ITaskTreeNode node) {
398        if (node != null) {
399            List<IGUIElement> terminalGuiElements = new ArrayList<IGUIElement>();
400            getTerminalGuiElements(node, terminalGuiElements);
401            return getCommonDenominator(terminalGuiElements);
402        }
403        else {
404            return null;
405        }
406    }
407       
408    /**
409     * <p>
410     * recursive method calling itself to determine all terminal GUI elements of the provided
411     * task. The terminal GUI elements are stored in the provided list.
412     * </p>
413     */
414    private void getTerminalGuiElements(ITaskTreeNode node, List<IGUIElement> terminalGuiElements) {
415        if (node instanceof IEventTask) {
416            if (((IEventTask) node).getEventTarget() instanceof IGUIElement) {
417                IGUIElement terminalGuiElement = (IGUIElement) ((IEventTask) node).getEventTarget();
418                terminalGuiElement =
419                    searchHierarchyForGuiElementWithConsideredType(terminalGuiElement);
420               
421                if (terminalGuiElement != null) {
422                    terminalGuiElements.add(terminalGuiElement);
423                }
424            }
425        }
426        else {
427            for (ITaskTreeNode child : node.getChildren()) {
428                getTerminalGuiElements(child, terminalGuiElements);
429            }
430        }
431    }
432
433    /**
434     * <p>
435     * returns a list of GUI elements that represents the whole GUI element hierarchy of the
436     * provided GUI element. The method considers the GUI element filter applied by this rule.
437     * </p>
438     */
439    private List<IGUIElement> getGuiElementHierarchy(IGUIElement guiElement) {
440        IGUIElement element = guiElement;
441       
442        if (!guiElementMatchesConsideredTypes(element)) {
443            element = searchHierarchyForGuiElementWithConsideredType(element);
444        }
445       
446        List<IGUIElement> hierarchy = new ArrayList<IGUIElement>();
447       
448        while (element != null) {
449            hierarchy.add(0, element);
450            element = searchHierarchyForGuiElementWithConsideredType(element.getParent());
451        }
452       
453        if (hierarchy.size() > 0) {
454            return hierarchy;
455        }
456        else {
457            return null;
458        }
459    }
460
461    /**
462     * <p>
463     * returns for a given GUI element the next GUI element in the upper GUI element hierarchy
464     * that matches the GUI element filter of the rule. If the provided GUI element already
465     * matches the filter, it is returned directly.
466     * </p>
467     */
468    private IGUIElement searchHierarchyForGuiElementWithConsideredType(IGUIElement guiElement) {
469        IGUIElement returnValue = guiElement;
470       
471        while ((returnValue != null) && !guiElementMatchesConsideredTypes(returnValue)) {
472            returnValue = returnValue.getParent();
473        }
474       
475        return returnValue;
476    }
477
478    /**
479     * <p>
480     * checks if the provided GUI element matches the GUI element filter applied by the rule.
481     * </p>
482     */
483    private boolean guiElementMatchesConsideredTypes(IGUIElement guiElement) {
484        if (guiElementFilter == null) {
485            return true;
486        }
487        else {
488            for (Class<? extends IGUIElement> clazz : guiElementFilter) {
489                if (clazz.isInstance(guiElement)) {
490                    return true;
491                }
492            }
493           
494            return false;
495        }
496    }
497
498}
Note: See TracBrowser for help on using the repository browser.