Index: trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultGuiElementSequenceDetectionRule.java
===================================================================
--- trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultGuiElementSequenceDetectionRule.java	(revision 972)
+++ trunk/autoquest-core-tasktrees/src/main/java/de/ugoe/cs/autoquest/tasktrees/temporalrelation/DefaultGuiElementSequenceDetectionRule.java	(revision 987)
@@ -12,8 +12,8 @@
 
 /**
- * This rule generates sequences depending on a GUI model. All actions occurring on the same GUI
- * element are put together in a sequence. All sequences and actions within a common panel are also
- * put together to a sequence. Through this, an event sequence gets a logical structure and shows,
- * which interactions that happen together also happen in the same GUI context.
+ * This rule structures the task tree based on GUI elements of the GUI model. The rule can
+ * be provided with a filter for considered GUI elements. It generates sub sequences for any
+ * GUI element in the hierarchy matching the filter so that each sequence represents all
+ * interactions in a certain GUI element.
  * 
  * @version $Revision: $ $Date: 18.03.2012$
@@ -21,4 +21,33 @@
  */
 public class DefaultGuiElementSequenceDetectionRule implements TemporalRelationshipRule {
+
+    /**
+     * <p>
+     * the GUI element filter to be applied or null if none is specified.
+     * </p>
+     */
+    private List<Class<? extends IGUIElement>> guiElementFilter;
+
+    /**
+     * <p>
+     * instantiates the rule without a GUI element filter
+     * </p>
+     */
+    DefaultGuiElementSequenceDetectionRule() {
+        this.guiElementFilter = null;
+    }
+
+    /**
+     * <p>
+     * instantiates the rule with a GUI element filter. Only those types given in the filter will
+     * be considered during the rule application. For all other types, no subsequences will be
+     * created.
+     * </p>
+     *
+     * @param guiElementFilter the GUI element filter to be applied
+     */
+    DefaultGuiElementSequenceDetectionRule(List<Class<? extends IGUIElement>> guiElementFilter) {
+        this.guiElementFilter = guiElementFilter;
+    }
 
     /*
@@ -39,200 +68,220 @@
 
         RuleApplicationResult result = new RuleApplicationResult();
-        
-        IGUIElement lastGuiElement = null;
+        List<List<IGUIElement>> hierarchies = new ArrayList<List<IGUIElement>>();
+        
+        // collect information about the GUI hierarchy
+        int maxHierarchyDepth = 0;
+        IGUIElement guiElement;
+        List<IGUIElement> guiElements = new ArrayList<IGUIElement>();
+        List<IGUIElement> hierarchy;
+        
+        for (ITaskTreeNode child : parent.getChildren()) {
+            guiElement = getGuiElement(child);
+            guiElements.add(guiElement);
+            hierarchy = getGuiElementHierarchy(guiElement);
+            hierarchies.add(hierarchy);
+            if (hierarchy != null) {
+                maxHierarchyDepth = Math.max(maxHierarchyDepth, hierarchy.size());
+            }
+        }
+        
+        IGUIElement commonDenominator = getCommonDenominator(guiElements);
+        hierarchy = getGuiElementHierarchy(commonDenominator);
+        int initialHierarchyLevel = hierarchy != null ? hierarchy.size() : 0;
+        
+        // now generate sub sequences for the different GUI elements. Start at the hierarchy
+        // level of the children of the common denominator to ensure, that different children are
+        // found. If this level is already the maximum hierarchy depth, we do not need to condense
+        // anything.
+        
+        RuleApplicationStatus status;
+        if (initialHierarchyLevel < maxHierarchyDepth) {
+            status = generateSubSequences(parent, hierarchies, initialHierarchyLevel, finalize,
+                                          builder, nodeFactory, result);
+        }
+        else {
+            status = RuleApplicationStatus.RULE_NOT_APPLIED;
+        }
+            
+        result.setRuleApplicationStatus(status);
+        
+        return result;
+    }
+
+    /**
+     * <p>
+     * generates subsequences for all groups of children of the provided parent, that operate
+     * in different GUI elements at the provided hierarchy level. It will not generate a sub
+     * sequence for the last elements, if the rule application shall not finalize.
+     * </p>
+     *
+     * @param parent            the parent node of which the children shall be grouped
+     * @param hierarchies       the GUI hierarchies for the children of the parent
+     * @param hierarchyLevel    the current hierarchy level to be considered
+     * @param maxHierarchyDepth the maximum hierarchy depth that may apply in this application
+     * @param finalize          true, if the application shall be finalized, false else
+     * @param builder           the builder to use for generating the tree structure
+     * @param nodeFactory       the node factory to use for generating the tree structure
+     * @param result            the result of the rule application to add newly created parent
+     *                          nodes to
+     *                          
+     * @return RULE_APPLICATION_FINISHED, if at least one subsequence was generated,
+     *         RULE_APPLICATION_FEASIBLE, if the application shall not be finalized but some
+     *         children could be condensed if further data was available, and RULE_NOT_APPLIED,
+     *         if no subsequence was created and none is can be created, because no further
+     *         data is expected
+     */
+    private RuleApplicationStatus generateSubSequences(ITaskTreeNode           parent,
+                                                       List<List<IGUIElement>> hierarchies,
+                                                       int                     hierarchyLevel,
+                                                       boolean                 finalize,
+                                                       ITaskTreeBuilder        builder,
+                                                       ITaskTreeNodeFactory    nodeFactory,
+                                                       RuleApplicationResult   result)
+    {
+        IGUIElement currentParent = null;
+        List<IGUIElement> hierarchy;
+        int startingIndex = -1;
+        
+        RuleApplicationStatus status = RuleApplicationStatus.RULE_NOT_APPLIED;
+        boolean subsequenceHasStarted = false;
+        boolean exceedingGuiHierarchyDepth = false;
+        boolean nextGuiElementDiffers = false;
+       
+        currentParent = null;
+        startingIndex = -1;
+
         int index = 0;
         while (index < parent.getChildren().size()) {
-            ITaskTreeNode child = parent.getChildren().get(index);
-            IGUIElement currentGuiElement = getGUIElement(child);
-            if ((index > 0) && (!lastGuiElement.equals(currentGuiElement))) {
-                ReducableCommonDenominator commonDenominator =
-                    getNextReducableCommonDenominator(parent, index - 1);
-                    
-                if (commonDenominator != null) {
-                    // condense only if not all children would be condensed or if we can be sure,
-                    // that there will be no further child that should be included in the condensed
-                    // sequence
-                    if ((commonDenominator.noOfTasks < parent.getChildren().size()) &&
-                        (!isOnGuiElementPath(commonDenominator.commonGuiElement, currentGuiElement)))
-                    {
-                        condenseTasksToSequence(parent, index, commonDenominator.noOfTasks,
-                                                builder, nodeFactory, result);
-
-                        result.setRuleApplicationStatus
-                            (RuleApplicationStatus.RULE_APPLICATION_FINISHED);
-                        return result;
-                    }
-                    else {
-                        // the common denominator is on the parent path of the next GUI element.
-                        // Therefore, the current sequences is not finished yet. So break up.
-                        result.setRuleApplicationStatus
-                            (RuleApplicationStatus.RULE_APPLICATION_FEASIBLE);
-                    }
-                }
-            }
-
-            lastGuiElement = currentGuiElement;
+            hierarchy = hierarchies.get(index);
+
+            exceedingGuiHierarchyDepth = hierarchyLevel >= hierarchy.size();
+            nextGuiElementDiffers =
+                subsequenceHasStarted &&
+                (exceedingGuiHierarchyDepth || !currentParent.equals(hierarchy.get(hierarchyLevel)));
+
+
+            if (!subsequenceHasStarted && !exceedingGuiHierarchyDepth) {
+                currentParent = hierarchy.get(hierarchyLevel);
+                startingIndex = index;
+                subsequenceHasStarted = true;
+            }
+            else if (nextGuiElementDiffers) {
+                status = condenseSequence(parent, hierarchies, hierarchyLevel, startingIndex,
+                                          index - 1, builder, nodeFactory, result);
+
+                if (status != null) {
+                    index = startingIndex + 1;
+                }
+                
+                if (!exceedingGuiHierarchyDepth) {
+                    currentParent = hierarchy.get(hierarchyLevel);
+                    startingIndex = index;
+                    subsequenceHasStarted = true;
+                }
+                else {
+                    currentParent = null;
+                    startingIndex = -1;
+                    subsequenceHasStarted = false;
+                }
+            }
+            
             index++;
         }
 
-        ReducableCommonDenominator commonDenominator =
-            getNextReducableCommonDenominator(parent, parent.getChildren().size() - 1);
-        
-        if ((commonDenominator != null) &&
-            (commonDenominator.noOfTasks < parent.getChildren().size()))
-        {
-            if (finalize) {
-                condenseTasksToSequence
-                    (parent, index, commonDenominator.noOfTasks, builder, nodeFactory, result);
+        if (finalize) {
+            if (subsequenceHasStarted) {
+                status = condenseSequence
+                    (parent, hierarchies, hierarchyLevel, startingIndex,
+                     parent.getChildren().size() - 1, builder, nodeFactory, result);
+            }
+            else if (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED) {
+                status = RuleApplicationStatus.RULE_NOT_APPLIED;
+            }
+        }
+        else {
+            if ((currentParent != null) &&
+                (status != RuleApplicationStatus.RULE_APPLICATION_FINISHED))
+            {
+                status = RuleApplicationStatus.RULE_APPLICATION_FEASIBLE;
+            }
+        }
+
+        return status;
+    }
+
+    /**
+     * <p>
+     * condenses a specified group of children on the provided parent to a subsequences and
+     * calls {@link #generateSubSequences(ITaskTreeNode, List, int, boolean, ITaskTreeBuilder, ITaskTreeNodeFactory, RuleApplicationResult)}
+     * for the newly created subsequence. The method does not condense subgroups consisting of
+     * only one child which is already a sequence.
+     * </p>
+     *
+     * @param parent         the parent task of which children shall be condensed
+     * @param hierarchies    the GUI element hierarchies of the children of the parent
+     * @param hierarchyLevel the currently considered GUI element hierarchy level
+     * @param startIndex     the index of the first child belonging to the subgroup
+     * @param endIndex       the index of the last child belonging to the subgroup
+     * @param builder        the builder to use for generating the tree structure
+     * @param nodeFactory    the node factory to use for generating the tree structure
+     * @param result         the result of the rule application to add newly created parent nodes to
+     * 
+     * @return RULE_APPLICATION_FINISHED, if at the subsequence was generated and RULE_NOT_APPLIED,
+     *         if no subsequence was created, because only one child belonged to the group which
+     *         was already a sequence
+     */
+    private RuleApplicationStatus condenseSequence(ITaskTreeNode           parent,
+                                                   List<List<IGUIElement>> hierarchies,
+                                                   int                     hierarchyLevel,
+                                                   int                     startIndex,
+                                                   int                     endIndex,
+                                                   ITaskTreeBuilder        builder,
+                                                   ITaskTreeNodeFactory    nodeFactory,
+                                                   RuleApplicationResult   result)
+    {
+        boolean onlyASingleChildToReduce = (endIndex - startIndex) == 0;
+        boolean singleChildIsSequence = onlyASingleChildToReduce &&
+            parent.getChildren().get(startIndex) instanceof ISequence;
+
+        if (!onlyASingleChildToReduce || !singleChildIsSequence) {
+            ISequence sequence = nodeFactory.createNewSequence();
+            
+            List<List<IGUIElement>> subHierarchies = new ArrayList<List<IGUIElement>>();
+            List<IGUIElement> newHierarchy =
+                hierarchies.get(startIndex).subList(0, hierarchyLevel + 1);
+            builder.setDescription(sequence, "interactions on " +
+                                   newHierarchy.get(newHierarchy.size() - 1).getStringIdentifier());
+
+            for (int i = startIndex; i <= endIndex; i++) {
+                builder.addChild(sequence, parent.getChildren().get(startIndex));
+                builder.removeChild((ISequence) parent, startIndex);
                 
-                result.setRuleApplicationStatus(RuleApplicationStatus.RULE_APPLICATION_FINISHED);
-                
-                return result;
-            }
-            else {
-                result.setRuleApplicationStatus(RuleApplicationStatus.RULE_APPLICATION_FEASIBLE);
-            }
-        }
-
-        return result;
-    }
-
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param guiElement
-     * @param detectedTasks
-     * @param parent
-     * @param index
-     * @param builder
-     * @param nodeFactory
-     * @return
-     */
-    private void condenseTasksToSequence(ITaskTreeNode         parent,
-                                         int                   parentIndex,
-                                         int                   noOfTasks,
-                                         ITaskTreeBuilder      builder,
-                                         ITaskTreeNodeFactory  nodeFactory,
-                                         RuleApplicationResult result)
-    {
-        ISequence newSequence = nodeFactory.createNewSequence();
-        for (int i = 0; i < noOfTasks; i++) {
-            builder.addChild(newSequence, parent.getChildren().get(parentIndex - noOfTasks));
-            // remove exactly the same number of children from the parent.
-            builder.removeChild((ISequence) parent, parentIndex - noOfTasks);
-        }
-                
-        builder.addChild((ISequence) parent, parentIndex - noOfTasks, newSequence);
-        result.addNewlyCreatedParentNode(newSequence);
-    }
-
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param detectedTasks
-     * @return
-     */
-    private ReducableCommonDenominator getNextReducableCommonDenominator(ITaskTreeNode parent,
-                                                                         int           childIndex)
-    {
-        ReducableCommonDenominator commonDenominator = null;
-        
-        // a common denominator can only exist for at least two task tree nodes
-        if (childIndex > 0) {
-            // start with the last one
-            int pos = childIndex;
-
-            commonDenominator = new ReducableCommonDenominator();
-            
-            // check for further predecessors, if they match the same common denominator
-            IGUIElement currentCommonDenominator = null;
-            do {
-                if (--pos < 0) {
-                    currentCommonDenominator = null;
-                }
-                else {
-                    currentCommonDenominator = getCommonDenominator
-                        (getGUIElement(parent.getChildren().get(pos)),
-                         getGUIElement(parent.getChildren().get(pos + 1)));
-                }
-                
-                if (commonDenominator.commonGuiElement == null) {
-                    commonDenominator.commonGuiElement = currentCommonDenominator;
-                }
-            }
-            while ((commonDenominator.commonGuiElement != null) &&
-                   (commonDenominator.commonGuiElement.equals(currentCommonDenominator)));
-            
-            if (commonDenominator.commonGuiElement != null) {
-                // pos points to the last element, that has not the same common denominator.
-                // This one must be subtracted from the task number as well
-                commonDenominator.noOfTasks = childIndex - pos;
-            }
-            else {
-                commonDenominator = null;
-            }
-        }
-        
-        return commonDenominator;
-    }
-
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param child
-     * @return
-     */
-    private IGUIElement getGUIElement(ITaskTreeNode node) {
-        List<IGUIElement> terminalGUIElements = new ArrayList<IGUIElement>();
-        getTerminalGUIElements(node, terminalGUIElements);
-        return getCommonDenominator(terminalGUIElements);
-    }
-        
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param detectedTaskGroups
-     * @return
-     */
-    /*private IGUIElement getCommonDenominator(Stack<Task> detectedTasks, int start) {
-        List<IGUIElement> allGUIElements = new ArrayList<IGUIElement>();
-        
-        for (int i = start; i < detectedTasks.size(); i++) {
-            allGUIElements.add(detectedTasks.get(i).commonGuiElement);
-        }
-        
-        return getCommonDenominator(allGUIElements);
-    }*/
-
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param child
-     * @return
-     */
-    private IGUIElement getCommonDenominator(IGUIElement guiElement1, IGUIElement guiElement2) {
-        List<IGUIElement> allGUIElements = new ArrayList<IGUIElement>();
-        allGUIElements.add(guiElement1);
-        allGUIElements.add(guiElement2);
-        return getCommonDenominator(allGUIElements);
-    }
-
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param child
-     * @return
+                subHierarchies.add(hierarchies.remove(startIndex));
+            }
+
+            builder.addChild((ISequence) parent, startIndex, sequence);
+            
+            hierarchies.add(startIndex, newHierarchy);
+            
+            generateSubSequences
+                (sequence, subHierarchies, hierarchyLevel + 1, true, builder, nodeFactory, result);
+
+            result.addNewlyCreatedParentNode(sequence);
+
+            return RuleApplicationStatus.RULE_APPLICATION_FINISHED;
+        }
+        else {
+            return null;
+        }
+
+    }
+
+    /**
+     * <p>
+     * return a common denominator for the provided list of GUI elements, i.e. a GUI element, that
+     * is part of the parent GUI hiearchy of all GUI elements in the list. If there is no common
+     * denominator, the method returns null.
+     * </p>
      */
     private IGUIElement getCommonDenominator(List<IGUIElement> guiElements) {
@@ -245,6 +294,12 @@
             IGUIElement guiElement = guiElements.get(0);
             while (guiElement != null) {
-                commonDenominatorPath.add(0, guiElement);
+                if (guiElementMatchesConsideredTypes(guiElement)) {
+                    commonDenominatorPath.add(0, guiElement);
+                }
                 guiElement = guiElement.getParent();
+            }
+            
+            if (commonDenominatorPath.size() == 0) {
+                return null;
             }
             
@@ -257,5 +312,7 @@
                 guiElement = guiElements.get(i);
                 while (guiElement != null) {
-                    currentPath.add(0, guiElement);
+                    if (guiElementMatchesConsideredTypes(guiElement)) {
+                        currentPath.add(0, guiElement);
+                    }
                     guiElement = guiElement.getParent();
                 }
@@ -285,64 +342,111 @@
     /**
      * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param child
-     * @return
-     */
-    private void getTerminalGUIElements(ITaskTreeNode node, List<IGUIElement> terminalGUIElements) {
+     * returns the GUI element on which all interactions of the provided task takes place. If
+     * the task is a simple event task its target is returned. If the task is a parent task
+     * of several children, the common denominator of the GUI elements of all its children is
+     * returned. The method returns null, if there is no common GUI element for all events
+     * represented by the provided task.
+     * </p>
+     */
+    private IGUIElement getGuiElement(ITaskTreeNode node) {
+        if (node != null) {
+            List<IGUIElement> terminalGuiElements = new ArrayList<IGUIElement>();
+            getTerminalGuiElements(node, terminalGuiElements);
+            return getCommonDenominator(terminalGuiElements);
+        }
+        else {
+            return null;
+        }
+    }
+        
+    /**
+     * <p>
+     * recursive method calling itself to determine all terminal GUI elements of the provided
+     * task. The terminal GUI elements are stored in the provided list.
+     * </p>
+     */
+    private void getTerminalGuiElements(ITaskTreeNode node, List<IGUIElement> terminalGuiElements) {
         if (node instanceof IEventTask) {
             if (((IEventTask) node).getEventTarget() instanceof IGUIElement) {
-                terminalGUIElements.add((IGUIElement) ((IEventTask) node).getEventTarget());
+                IGUIElement terminalGuiElement = (IGUIElement) ((IEventTask) node).getEventTarget();
+                terminalGuiElement =
+                    searchHierarchyForGuiElementWithConsideredType(terminalGuiElement);
+                
+                if (terminalGuiElement != null) {
+                    terminalGuiElements.add(terminalGuiElement);
+                }
             }
         }
         else {
             for (ITaskTreeNode child : node.getChildren()) {
-                getTerminalGUIElements(child, terminalGUIElements);
-            }
-        }
-    }
-
-    /**
-     * <p>
-     * TODO: comment
-     * </p>
-     *
-     * @param currentCommonDenominator
-     * @param guiElement
-     * @return
-     */
-    private boolean isOnGuiElementPath(IGUIElement potentialPathElement, IGUIElement child) {
-        IGUIElement guiElement = child;
-        
-        while (guiElement != null) {
-            if (guiElement.equals(potentialPathElement)) {
-                return true;
-            }
-            guiElement = guiElement.getParent();
-        }
-        
-        return false;
-    }
-
-    /**
-     * 
-     */
-    private static class ReducableCommonDenominator {
-        
-        /** the GUI element being the common denominator */
-        private IGUIElement commonGuiElement;
-        
-        /** the number of tasks that match the common denominator */
-        private int noOfTasks;
-
-        /* (non-Javadoc)
-         * @see java.lang.Object#toString()
-         */
-        @Override
-        public String toString() {
-            return noOfTasks + " tasks on " + commonGuiElement;
-        }
-        
-    }
+                getTerminalGuiElements(child, terminalGuiElements);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * returns a list of GUI elements that represents the whole GUI element hierarchy of the
+     * provided GUI element. The method considers the GUI element filter applied by this rule.
+     * </p>
+     */
+    private List<IGUIElement> getGuiElementHierarchy(IGUIElement guiElement) {
+        IGUIElement element = guiElement;
+        
+        if (!guiElementMatchesConsideredTypes(element)) {
+            element = searchHierarchyForGuiElementWithConsideredType(element);
+        }
+        
+        List<IGUIElement> hierarchy = new ArrayList<IGUIElement>();
+        
+        while (element != null) {
+            hierarchy.add(0, element);
+            element = searchHierarchyForGuiElementWithConsideredType(element.getParent());
+        }
+        
+        if (hierarchy.size() > 0) {
+            return hierarchy;
+        }
+        else {
+            return null;
+        }
+    }
+
+    /**
+     * <p>
+     * returns for a given GUI element the next GUI element in the upper GUI element hierarchy
+     * that matches the GUI element filter of the rule. If the provided GUI element already
+     * matches the filter, it is returned directly.
+     * </p>
+     */
+    private IGUIElement searchHierarchyForGuiElementWithConsideredType(IGUIElement guiElement) {
+        IGUIElement returnValue = guiElement;
+        
+        while ((returnValue != null) && !guiElementMatchesConsideredTypes(returnValue)) {
+            returnValue = returnValue.getParent();
+        }
+        
+        return returnValue;
+    }
+
+    /**
+     * <p>
+     * checks if the provided GUI element matches the GUI element filter applied by the rule.
+     * </p>
+     */
+    private boolean guiElementMatchesConsideredTypes(IGUIElement guiElement) {
+        if (guiElementFilter == null) {
+            return true;
+        }
+        else {
+            for (Class<? extends IGUIElement> clazz : guiElementFilter) {
+                if (clazz.isInstance(guiElement)) {
+                    return true;
+                }
+            }
+            
+            return false;
+        }
+    }
+
 }
