Index: /trunk/autoquest-plugin-html-test/src/test/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModelTest.java
===================================================================
--- /trunk/autoquest-plugin-html-test/src/test/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModelTest.java	(revision 1286)
+++ /trunk/autoquest-plugin-html-test/src/test/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModelTest.java	(revision 1286)
@@ -0,0 +1,405 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.plugin.html.commands;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementFactory;
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementGroup;
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel.Traverser;
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
+import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
+import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocumentSpec;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElement;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServerSpec;
+import de.ugoe.cs.util.console.GlobalDataContainer;
+import de.ugoe.cs.util.console.TextConsole;
+
+/**
+ * <p>
+ * TODO comment
+ * </p>
+ * 
+ * @author Patrick Harms
+ */
+public class CMDcondenseHTMLGUIModelTest {
+
+    /**
+     * 
+     */
+    @Before
+    public void setUp() {
+        new TextConsole();
+    }
+    
+    /**
+     * 
+     */
+    @Test
+    public void test_condense_01() throws Exception {
+        String[] guiSpec =
+            { "/server/doc1/html/body/div_0" };
+        
+        GUIModel guiModel = generateGUIModel(guiSpec);
+        GlobalDataContainer.getInstance().addData("sequences_targets", guiModel);
+        
+        executeCommand();
+        
+        String[] assertionSpec =
+            { "/server/doc1/html/body/div" };
+        
+        assertGUIModel(guiModel, assertionSpec);
+    }
+
+    /**
+     * 
+     */
+    @Test
+    public void test_condense_02() throws Exception {
+        String[] guiSpec =
+            { "/server/doc1/html/body/div_0",
+              "/server/doc1/html/body/div_1",
+              "/server/doc1/html/body/div_2",
+              "/server/doc1/html/body/div_3"};
+            
+        GUIModel guiModel = generateGUIModel(guiSpec);
+        GlobalDataContainer.getInstance().addData("sequences_targets", guiModel);
+            
+        executeCommand();
+        
+        String[] assertionSpec =
+            { "/server/doc1/html/body/div",
+              "/server/doc1/html/body/div_1",
+              "/server/doc1/html/body/div_2",
+              "/server/doc1/html/body/div_3" };
+        
+        assertGUIModel(guiModel, assertionSpec);
+    }
+
+    /**
+     * 
+     */
+    @Test
+    public void test_condense_03() throws Exception {
+        String[] guiSpec =
+            { "/server/doc1/html/body/div_0",
+              "/server/doc1/html/body/div_1",
+              "/server/doc2/html/body/div_2",
+              "/server/doc2/html/body/div_3"};
+                
+        GUIModel guiModel = generateGUIModel(guiSpec);
+        GlobalDataContainer.getInstance().addData("sequences_targets", guiModel);
+                
+        executeCommand();
+            
+        String[] assertionSpec =
+            { "/server/doc1/html/body/group_doc1/div",
+              "/server/doc1/html/body/group_doc1/div_1",
+              "/server/doc1/html/body/group_doc2/div_2",
+              "/server/doc1/html/body/group_doc2/div_3" };
+        
+        assertGUIModel(guiModel, assertionSpec);
+    }
+
+    /**
+     * 
+     */
+    @Test
+    public void test_condense_04() throws Exception {
+        String[] guiSpec =
+            { "/server/doc1/html/body/div",
+              "/server/doc1/html/body/div_1",
+              "/server/doc2/html/body/div",
+              "/server/doc2/html/body/div_1"};
+                
+        GUIModel guiModel = generateGUIModel(guiSpec);
+        GlobalDataContainer.getInstance().addData("sequences_targets", guiModel);
+                
+        executeCommand();
+            
+        String[] assertionSpec =
+            { "/server/doc1/html/body/div",
+              "/server/doc1/html/body/div_1" };
+        
+        assertGUIModel(guiModel, assertionSpec);
+    }
+
+    /**
+     * 
+     */
+    @Test
+    public void test_condense_05() throws Exception {
+        String[] guiSpec =
+            { "/server/doc1/html/body/div",
+              "/server/doc1/html/body/div_1",
+              "/server/doc2/html/body/div",
+              "/server/doc2/html/body/div_2"};
+                
+        GUIModel guiModel = generateGUIModel(guiSpec);
+        GlobalDataContainer.getInstance().addData("sequences_targets", guiModel);
+                
+        executeCommand();
+            
+        String[] assertionSpec =
+            { "/server/doc1/html/body/div",
+              "/server/doc1/html/body/group_doc1/div_1",
+              "/server/doc1/html/body/group_doc2/div_2" };
+        
+        assertGUIModel(guiModel, assertionSpec);
+    }
+
+    /**
+     * 
+     */
+    @Test
+    public void test_condense_06() throws Exception {
+        String[] guiSpec =
+            { "/server/doc1/html/body/div/div_1",
+              "/server/doc1/html/body/div/div_2",
+              "/server/doc1/html/body/div/div_3",
+              "/server/doc1/html/body/div/div_4",
+              "/server/doc1/html/body/div/div_5",
+              "/server/doc1/html/body/div/div_6",
+              "/server/doc2/html/body/div/div_1",
+              "/server/doc2/html/body/div/div_2",
+              "/server/doc2/html/body/div/div_7",
+              "/server/doc2/html/body/div/div_8",
+              "/server/doc2/html/body/div/div_6",
+              "/server/doc3/html/body/div/div_9",
+              "/server/doc3/html/body/div/div_10",
+              "/server/doc3/html/body/div/div_6",
+              "/server/doc3/html/body/div/div_11",
+              "/server/doc4/html/body/div/div_12",
+              "/server/doc4/html/body/div/div_13",
+              "/server/doc5/html/body/div/div_4",
+              "/server/doc5/html/body/div/div_11" };
+                
+        GUIModel guiModel = generateGUIModel(guiSpec);
+        GlobalDataContainer.getInstance().addData("sequences_targets", guiModel);
+                
+        executeCommand();
+            
+        String[] assertionSpec =
+            { "/server/doc1/html/body/div/group_doc1doc2doc3/group_doc1doc2/div_1",
+              "/server/doc1/html/body/div/group_doc1doc2doc3/group_doc1doc2/div_2",
+              "/server/doc1/html/body/div/group_doc1doc2doc3/group_doc1doc2/group_doc1/div_3",
+              "/server/doc1/html/body/div/group_doc1doc2doc3/group_doc1doc2/group_doc1/div_5",
+              "/server/doc1/html/body/div/group_doc1doc2doc3/group_doc1doc2/group_doc2/div_7",
+              "/server/doc1/html/body/div/group_doc1doc2doc3/group_doc1doc2/group_doc2/div_8",
+              "/server/doc1/html/body/div/group_doc1doc2doc3/div_6",
+              "/server/doc1/html/body/div/group_doc1doc5/div_4",
+              "/server/doc1/html/body/div/group_doc3doc5/div_11",
+              "/server/doc1/html/body/div/group_doc3doc5/group_doc3/div_9",
+              "/server/doc1/html/body/div/group_doc3doc5/group_doc3/div_10",
+              "/server/doc1/html/body/div/group_doc4/div_12",
+              "/server/doc1/html/body/div/group_doc4/div_13" };
+        
+        assertGUIModel(guiModel, assertionSpec);
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param guiSpec
+     * @return
+     * @throws GUIModelException 
+     * @throws IllegalArgumentException 
+     */
+    private GUIModel generateGUIModel(String[] guiSpec)
+        throws IllegalArgumentException, GUIModelException
+    {
+        GUIModel model = new GUIModel(true);
+        
+        Map<String, HTMLServerSpec> servers = new HashMap<String, HTMLServerSpec>();
+        Map<String, HTMLDocumentSpec> documents = new HashMap<String, HTMLDocumentSpec>();
+        Map<String, HTMLPageElementSpec> pageElements = new HashMap<String, HTMLPageElementSpec>();
+        
+        for (String path : guiSpec) {
+            String[] elements = path.split("/");
+            
+            if ("".equals(elements[0])) {
+                elements = Arrays.copyOfRange(elements, 1, elements.length);
+            }
+            
+            List<IGUIElementSpec> pathList = new LinkedList<IGUIElementSpec>();
+            for (int i = 0; i < elements.length; i++) {
+                if (i == 0) {
+                    if (!servers.containsKey(elements[i])) {
+                        servers.put(elements[i], new HTMLServerSpec(elements[i], 80));
+                    }
+                    pathList.add(servers.get(elements[i]));
+                }
+                else if (i == 1) {
+                    if (!documents.containsKey(elements[i])) {
+                        HTMLDocumentSpec doc = new HTMLDocumentSpec
+                            ((HTMLServerSpec) pathList.get(0), elements[i], null, elements[i]);
+                        documents.put(elements[i], doc);
+                    }
+                    pathList.add(documents.get(elements[i]));
+                }
+                else {
+                    if (!pageElements.containsKey(elements[i])) {
+                        String[] infos = elements[i].split("_");
+                    
+                        String tagName = infos[0];
+                    
+                        int index = 0;
+                        String id = null;
+                    
+                        if (infos.length > 1) {
+                            try {
+                                index = Integer.parseInt(infos[1]);
+                            }
+                            catch (Exception e) {
+                                id = infos[1];
+                                index = -1;
+                            }
+                        }
+
+                        HTMLPageElementSpec pageElem = new HTMLPageElementSpec
+                            ((HTMLDocumentSpec) pathList.get(1), tagName, id, index);
+                        pageElements.put(elements[i], pageElem);
+                    }
+
+                    pathList.add(pageElements.get(elements[i]));
+                }
+            }
+            
+            model.integratePath(pathList, GUIElementFactory.getInstance());
+        }
+        
+        return model;
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     */
+    private void executeCommand() {
+        CMDcondenseHTMLGUIModel command = new CMDcondenseHTMLGUIModel();
+        
+        List<Object> parameters = new LinkedList<Object>();
+        parameters.add("sequences");
+        
+        command.run(parameters);
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param guiModel
+     * @param assertionSpec
+     */
+    private void assertGUIModel(GUIModel guiModel, String[] assertionSpec) {
+        List<String> paths = new LinkedList<String>();
+        
+        for (String path : assertionSpec) {
+            paths.add(path);
+        }
+        
+        GUIModel.Traverser traverser = guiModel.getTraverser();
+        
+        assertPaths(traverser, "", paths);
+        
+        assertEquals(0, paths.size());
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param traverser
+     * @param paths
+     */
+    private void assertPaths(Traverser traverser, String path, List<String> paths) {
+        if (traverser.hasFirstChild()) {
+            IGUIElement childElement = traverser.firstChild();
+        
+            while (childElement != null) {
+                String currentPath = path + "/" + getPathElement(childElement);
+                assertPaths(traverser, currentPath, paths);
+                childElement = traverser.nextSibling();
+            }
+        
+            traverser.parent();
+        }
+        else {
+            assertTrue("unexpected path: " + path, paths.remove(path));
+        }
+    }
+
+    /**
+     * <p>
+     * TODO: comment
+     * </p>
+     *
+     * @param childElement
+     * @return
+     */
+    private String getPathElement(IGUIElement childElement) {
+        StringBuffer name = new StringBuffer();
+        
+        if (childElement instanceof HTMLServer) {
+            name.append(((HTMLServer) childElement).getHost());
+        }
+        else if (childElement instanceof HTMLDocument) {
+            name.append(((HTMLDocument) childElement).getPath());
+        }
+        else if (childElement instanceof HTMLPageElement) {
+            name.append(((HTMLPageElement) childElement).getTagName());
+            
+            if (((HTMLPageElement) childElement).getHtmlId() != null) {
+                name.append("_");
+                name.append(((HTMLPageElement) childElement).getHtmlId());
+            }
+            else if (((HTMLPageElement) childElement).getIndex() > 0) {
+                name.append("_");
+                name.append(((HTMLPageElement) childElement).getIndex());
+            }
+        }
+        else if (childElement instanceof GUIElementGroup) {
+            name.append(((GUIElementGroup) childElement).getStringIdentifier());
+            
+            /*for (IGUIElement groupedElement : ((GUIElementGroup) childElement).getGroupedElements())
+            {
+                name.append(getPathElement(groupedElement));
+            }*/
+        }
+        
+        return name.toString();
+    }
+
+}
Index: /trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModel.java
===================================================================
--- /trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModel.java	(revision 1286)
+++ /trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModel.java	(revision 1286)
@@ -0,0 +1,918 @@
+//   Copyright 2012 Georg-August-Universität Göttingen, Germany
+//
+//   Licensed under the Apache License, Version 2.0 (the "License");
+//   you may not use this file except in compliance with the License.
+//   You may obtain a copy of the License at
+//
+//       http://www.apache.org/licenses/LICENSE-2.0
+//
+//   Unless required by applicable law or agreed to in writing, software
+//   distributed under the License is distributed on an "AS IS" BASIS,
+//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//   See the License for the specific language governing permissions and
+//   limitations under the License.
+
+package de.ugoe.cs.autoquest.plugin.html.commands;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+
+import de.ugoe.cs.autoquest.CommandHelpers;
+import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
+import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
+import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
+import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer;
+import de.ugoe.cs.util.console.Command;
+import de.ugoe.cs.util.console.Console;
+import de.ugoe.cs.util.console.GlobalDataContainer;
+
+/**
+ * <p>
+ * This command condenses an HTML GUI model. For HTML GUI models, this is a special task, as, e.g.,
+ * menu structures resisting on several pages of a website are identified separately. However, for
+ * subsequent analysis, they need to be treated as identical. Therefore, this command identifies
+ * pages (i.e. documents) with equal structures and merges them. For differences in the pages, it
+ * adds groups of GUI elements indicating, which GUI element belongs to which pages and not to
+ * others.
+ * </p>
+ * <p>
+ * An example for clarification. Consider the following two pages:
+ * <pre>
+ * server
+ *   |-- document1
+ *   |     \-- html
+ *   |           \-- body
+ *   |                 |-- div (id = menu)
+ *   |                 |     \-- ...
+ *   |                 |-- div (id = textcontent)
+ *   |                 |     \-- ...
+ *   |                 \-- div (id = footer)
+ *   |                       \-- ...
+ *   \-- document2
+ *         \-- html
+ *               \-- body
+ *                     |-- div (id = menu)
+ *                     |     \-- ...
+ *                     |-- div (id = imagecontent)
+ *                     |     \-- ...
+ *                     \-- div (id = footer)
+ *                           \-- ...
+ * </pre>
+ * They only differ in the central div which has the id textcontent for the first document and 
+ * imagecontent for the second. The above GUI model is the result of the monitoring and parsing of
+ * the log files. This command condenses the GUI model to the following structure:
+ * <pre>
+ * server
+ *   \-- document1
+ *         \-- html
+ *               \-- body
+ *                     |-- div (id = menu)
+ *                     |     \-- ...
+ *                     |-- group (document1)
+ *                     |     \-- div (id = textcontent)
+ *                     |           \-- ...
+ *                     |-- group (document2)
+ *                     |     \-- div (id = imagecontent)
+ *                     |           \-- ...
+ *                     \-- div (id = footer)
+ *                           \-- ...
+ * </pre>
+ * This now allows the menu and the footer to be treated as identical over several pages.
+ * </p>
+ * <p>
+ * If several but not all pages share similarities, the created groups refer to several documents
+ * at once.
+ * </p>
+ * 
+ * @author Patrick Harms
+ * @version 1.0
+ */
+public class CMDcondenseHTMLGUIModel implements Command {
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
+     */
+    @Override
+    public void run(List<Object> parameters) {
+        String sequencesName;
+
+        try {
+            sequencesName = (String) parameters.get(0);
+        }
+        catch (Exception e) {
+            throw new IllegalArgumentException("illegal parameters provided: " + e);
+        }
+
+        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName + "_targets");
+        if (dataObject == null) {
+            CommandHelpers.objectNotFoundMessage(sequencesName + "_targets");
+            return;
+        }
+        if (!(dataObject instanceof GUIModel)) {
+            CommandHelpers.objectNotType(sequencesName, "GUIModel");
+            return;
+        }
+
+        GUIModel model = (GUIModel) dataObject;
+
+        for (IGUIElement root : model.getRootElements()) {
+            if (root instanceof HTMLServer) {
+                mergeServer((HTMLServer) root, model);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * Condenses all documents resisting on the same server. For this, the method first identifies
+     * a hierarchy of clusters of similar GUI elements. This is then used to merge similar GUI
+     * elements and to create subgroups for indicating differences between the documents.
+     * </p>
+     *
+     * @param server the server of which all documents shall be condensed
+     * @param model  the GUI model in which the server is referenced
+     */
+    private void mergeServer(HTMLServer server, GUIModel model) {
+        Console.traceln(Level.INFO, "condensing documents of " + server);
+        Console.traceln(Level.FINE, "creating cluster hierarchy of GUI elements for " + server);
+        GUIElementsCluster rootCluster = getSimilarElementClusterHierarchy(server, model);
+
+        //rootCluster.dump(System.out, "");
+            
+        Console.traceln(Level.FINE, "merging GUI elements in same clusters and creating groups");
+        mergeGUIElementsAccordingToClusters(rootCluster, model, "");
+            
+        //model.dump(System.out, "UTF-8");
+        Console.traceln(Level.INFO, "condensed documents of " + server);
+    }
+
+    /**
+     * <p>
+     * determines clusters of similar GUI elements being children of the provided parent. For this,
+     * the method creates a cluster containing all children of the provided parent as similar
+     * GUI elements. Usually, these are all documents on the same server. It then initiates
+     * creating the cluster hierarchy by calling the recursive method
+     * {@link #addChildClusters(GUIElementsCluster, GUIModel, String)}.
+     * </p>
+     *
+     * @param parent the parent GUI element for which the clusters of similar children shall be
+     *               determined (usually an HTML server)
+     * @param model  the GUI model required for identifying children and sub children
+     * 
+     * @return a list of GUI element clusters of the children.
+     */
+    private GUIElementsCluster getSimilarElementClusterHierarchy(IGUIElement parent, GUIModel model)
+    {
+        GUIElementsCluster cluster = new GUIElementsCluster();
+        
+        List<IGUIElement> children = model.getChildren(parent);
+        
+        SimilarGUIElements similarGuiElements = new SimilarGUIElements();
+        
+        for (IGUIElement child : children) {
+            // when starting with the root, the cluster parent is the child itself
+            similarGuiElements.add(new SimilarGUIElement(child, child));
+        }
+        
+        cluster.addSimilarGUIElements(similarGuiElements);
+        addChildClusters(cluster, model, "");
+        
+        return cluster;
+    }
+    
+
+    /**
+     * <p>
+     * Determines child clusters of a cluster of given GUI elements. This method calls itself
+     * recursively for each directly identified child cluster. Furthermore, it moves child clusters
+     * to children of other child clusters if they are a subset of the clustered GUI elements.
+     * </p>
+     *
+     * @param cluster the cluster for which child clusters shall be identified
+     * @param model   the model required to be able to determine child GUI elements
+     */
+    private void addChildClusters(GUIElementsCluster cluster, GUIModel model, String indent) {
+        for (SimilarGUIElements similarGuiElements : cluster.similarChildrenGUIElements) {
+            createSubClustersForChildren(similarGuiElements, cluster, model, indent + "  ");
+        }
+        
+        for (GUIElementsCluster childCluster : cluster.childClusters) {
+            addChildClusters(childCluster, model, indent + "  ");
+        }
+        
+        createClusterHierachies(cluster.childClusters);
+    }
+    
+    /**
+     * <p>
+     * for a set of similar GUI elements, it identifies similar children and clusters them. For
+     * each identified cluster, it adds a new child cluster to the provided parent cluster. GUI
+     * elements having no further children are added to a default cluster.
+     * </p>
+     *
+     * @param similarGuiElements the similar GUI elements of which the children shall be clustered
+     * @param parentCluster      the parent cluster to which newly identified cluster are added
+     *                           as children
+     * @param model              the model required to be able to determine child GUI elements
+     */
+    private void createSubClustersForChildren(SimilarGUIElements similarGuiElements,
+                                              GUIElementsCluster parentCluster,
+                                              GUIModel           model,
+                                              String             indent)
+    {
+        for (SimilarGUIElement similarGuiElement : similarGuiElements) {
+            List<IGUIElement> children = model.getChildren(similarGuiElement.similarGUIElement);
+        
+            if (children.size() > 0) {
+                for (IGUIElement child : children) {
+                    addToClusterOfSimilarElements
+                        (child, similarGuiElement.mainClusterParent, parentCluster.childClusters,
+                         similarGuiElements, model);
+                }
+            }
+            else {
+                // search for a default cluster to add all elements to, which have no children
+                GUIElementsCluster defaultCluster = null;
+                
+                for (GUIElementsCluster candidate : parentCluster.childClusters) {
+                    if (candidate.similarChildrenGUIElements.size() == 0) {
+                        defaultCluster = candidate;
+                        break;
+                    }
+                }
+                
+                if (defaultCluster == null) {
+                    defaultCluster = new GUIElementsCluster();
+                    parentCluster.addChildCluster(defaultCluster);
+                }
+                
+                defaultCluster.clusteredGUIElements.add(similarGuiElement);
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * for a given GUI element, searches the list of known clusters and adds the GUI element to
+     * the cluster, if the cluster already contains a similar GUI element. If not, a new cluster is
+     * created and added to the list of known clusters.
+     * </p>
+     *
+     * @param child         the child for which the cluster is to be determined
+     * @param clusterParent the GUI element for 
+     * @param parents
+     * @param model
+     */
+    private void addToClusterOfSimilarElements(IGUIElement              child,
+                                               IGUIElement              clusterParent,
+                                               List<GUIElementsCluster> knownClusters,
+                                               SimilarGUIElements       similarGuiElements,
+                                               GUIModel                 model)
+    {
+        SimilarGUIElements matchingParents = new SimilarGUIElements();
+        
+        // determine matching parents
+        for (SimilarGUIElement similarGuiElement : similarGuiElements) {
+            for (IGUIElement candidate : model.getChildren(similarGuiElement.similarGUIElement)) {
+                IGUIElementSpec similarityCandidate = candidate.getSpecification();
+                if (similarityCandidate.getSimilarity(child.getSpecification())) {
+                    matchingParents.add(similarGuiElement);
+                    break;
+                }
+            }
+        }
+        
+        // check if an appropriate cluster exists
+        GUIElementsCluster cluster = null;
+        
+        for (GUIElementsCluster clusterCandidate : knownClusters) {
+            if (clusterCandidate.isClusterOf(matchingParents)) {
+                cluster = clusterCandidate;
+                break;
+            }
+        }
+        
+        if (cluster == null) {
+            cluster = new GUIElementsCluster();
+            knownClusters.add(cluster);
+            cluster.setClusteredGUIElements(matchingParents);
+        }
+        
+        cluster.addSimilarChild(child, clusterParent);
+    }
+
+    /**
+     * <p>
+     * clusters of similar children identified for a GUI element may be logical subclusters of
+     * each other. This is, e.g., the case if one cluster contains all elements being part of
+     * document1 and document2 and a further cluster contains all elements of document 1 only. In
+     * this case, the cluster of document1 is added as a child to the other cluster.
+     * </p>
+     *
+     * @param clusters the clusters, for which the child hierarchies shall be created
+     *                 (in-out parameter, as it is changed)
+     */
+    private void createClusterHierachies(List<GUIElementsCluster> clusters) {
+        GUIElementsCluster[] clustersCopy =
+            clusters.toArray(new GUIElementsCluster[clusters.size()]);
+        
+        // sort the array starting with the shortest cluster and ending with the longest
+        Arrays.sort(clustersCopy, new Comparator<GUIElementsCluster>() {
+            @Override
+            public int compare(GUIElementsCluster c1, GUIElementsCluster c2) {
+                return c1.clusteredGUIElements.size() - c2.clusteredGUIElements.size();
+            }
+        });
+        
+        List<GUIElementsCluster> subClustersToHandle = new LinkedList<GUIElementsCluster>();
+        
+        // now add smaller clusters to larger ones, if they are parents
+        for (int i = 0; i < (clustersCopy.length - 1); i++) {
+            GUIElementsCluster potentialChild = clustersCopy[i];
+            
+            // search for the next cluster and add the child
+            for (int j = i + 1; j < clustersCopy.length; j++) {
+                if (clustersCopy[j].isSubCluster(potentialChild)) {
+                    
+                    clustersCopy[j].addChildCluster(potentialChild);
+                    subClustersToHandle.add(clustersCopy[j]);
+                    
+                    for (int k = 0; k < clusters.size(); k++) {
+                        if (clusters.get(k) == potentialChild) {
+                            clusters.remove(k);
+                            break;
+                        }
+                    }
+                    
+                    break;
+                }
+            }
+        }
+        
+        // finally, for all subclusters that were changed, ensure the creation of their internal
+        // hierarchy as well
+        for (GUIElementsCluster subClusterToHandle : subClustersToHandle) {
+            createClusterHierachies(subClusterToHandle.childClusters);
+        }
+    }
+
+    /**
+     * <p>
+     * called for each cluster to merge similar GUI elements depending on the clusters and to
+     * create GUI element groups if required. Calls itself recursively to be also applied on
+     * child clusters.
+     * </p>
+     *
+     * @param cluster the cluster of which the similar children shall be merged
+     * @param model   the model to be adapted through the merge
+     */
+    private void mergeGUIElementsAccordingToClusters(GUIElementsCluster cluster,
+                                                     GUIModel           model,
+                                                     String             indent)
+    {
+        //System.out.println(indent + "handling " + cluster);
+        
+        for (SimilarGUIElements similarGUIElements : cluster.similarChildrenGUIElements) {
+            mergeGUIElements(similarGUIElements, model, indent + "  ");
+        }
+        
+        if (cluster.childClusters.size() > 0) {
+            //System.out.println(indent + "  handling child clusters");
+            
+            for (GUIElementsCluster childCluster : cluster.childClusters) {
+                if (cluster.isDefault() || cluster.clusterParentsMatch(childCluster)) {
+                    // for default cluster or children not creating subgroups, just traverse the
+                    // cluster hierarchy
+                    mergeGUIElementsAccordingToClusters(childCluster, model, indent + "    ");
+                }
+                else {
+                    createClusterGroup(childCluster, model, indent + "    ");
+                }
+            }
+        }
+    }
+
+    /**
+     * <p>
+     * merges similar GUI elements using the provided model
+     * </p>
+     *
+     * @param similarGUIElements the GUI elements to merge
+     * @param model              the model to be used for merging the GUI elements
+     */
+    private void mergeGUIElements(SimilarGUIElements similarGUIElements,
+                                  GUIModel           model,
+                                  String             indent)
+    {
+        IGUIElement mergeResult = similarGUIElements.get(0).similarGUIElement;
+
+        while (similarGUIElements.size() > 1) {
+            //System.out.println(indent + "merging " + mergeResult + " and " +
+            //                   similarGUIElements.get(1).similarGUIElement);
+            mergeResult = model.mergeGUIElements
+                (mergeResult, similarGUIElements.remove(1).similarGUIElement, false);
+        }
+    }
+
+    /**
+     * <p>
+     * creates a group of GUI elements to represent a cluster. Uses the provided model for group
+     * creation.
+     * </p>
+     *
+     * @param cluster the cluster for which the group shall be created
+     * @param model   the model to be used for creating the groups
+    */
+    private void createClusterGroup(GUIElementsCluster cluster, GUIModel model, String indent) {
+        //System.out.println(indent + "creating group for " + cluster);
+
+        List<IGUIElement> guiElementsToGroup = new LinkedList<IGUIElement>();
+        
+        for (SimilarGUIElements similarGUIElements : cluster.similarChildrenGUIElements) {
+            mergeGUIElements(similarGUIElements, model, indent);
+            guiElementsToGroup.addAll(similarGUIElements.toGUIElementList());
+        }
+        
+        //System.out.println(indent + "  iterating child clusters of " + cluster);
+        for (GUIElementsCluster childCluster : cluster.childClusters) {
+            if (cluster.isDefault() || cluster.clusterParentsMatch(childCluster)) {
+                // for default cluster or children not creating subgroups, just traverse the
+                // cluster hierarchy
+                mergeGUIElementsAccordingToClusters(childCluster, model, indent + "  ");
+            }
+            else {
+                createClusterGroup(childCluster, model, indent + "  ");
+            }
+            
+            if (childCluster.getGroup() != null) {
+                if (cluster.isSubCluster(childCluster)) {
+                    guiElementsToGroup.add(childCluster.getGroup());
+                }
+                else {
+                    guiElementsToGroup.add(childCluster.getGroup().getParent());
+                }
+            }
+        }
+        
+        //System.out.println(indent + "grouping: " + guiElementsToGroup);
+        IGUIElement group = model.groupGUIElements(guiElementsToGroup, getGroupName(cluster));
+        //System.out.println(indent + "  created group for " + cluster + ": " + group);
+        
+        cluster.setGroup(group);
+    }
+
+    /**
+     * <p>
+     * determines a name for a group to be created for the provided cluster
+     * </p>
+     *
+     * @param cluster the cluster for which a group name shall be determined
+     * 
+     * @return an appropriate name
+     */
+    private String getGroupName(GUIElementsCluster cluster) {
+        StringBuffer name = new StringBuffer();
+        
+        name.append("group_");
+        
+        for (SimilarGUIElement guiElement : cluster.clusteredGUIElements) {
+            if (guiElement.mainClusterParent instanceof HTMLDocument) {
+                name.append(((HTMLDocument) guiElement.mainClusterParent).getPath());
+            }
+            else {
+                name.append(guiElement.mainClusterParent.getStringIdentifier());
+            }
+        }
+        
+        return name.toString();
+    }
+
+    /*
+     * (non-Javadoc)
+     * 
+     * @see de.ugoe.cs.util.console.Command#help()
+     */
+    @Override
+    public String help() {
+        return "condenseHTMLGUIModel <sequence>";
+    }
+
+    /**
+     * <p>
+     * represents a cluster of similar GUI elements. It consists of a list of similar GUI elements
+     * represented by the cluster somewhere in the GUI element hierarchy. It contains, furthermore,
+     * a list of GUI elements that belong to the root cluster of the cluster hierarchy, which are
+     * usually the similar documents. It also refers to child clusters, and if created, a GUI
+     * element group representing the cluster.
+     * </p>
+     */
+    private static class GUIElementsCluster {
+        
+        /**
+         * <p>
+         * the similar children on the same level in the GUI model represented through the cluster
+         * </p>
+         */
+        private List<SimilarGUIElements> similarChildrenGUIElements =
+            new LinkedList<SimilarGUIElements>();
+        
+        /**
+         * <p>
+         * the similar root GUI elements on the first level of the created hierarchy (usually
+         * the documents on a website) to which the similar GUI elements belong
+         * </p>
+         */
+        private SimilarGUIElements clusteredGUIElements = new SimilarGUIElements();
+        
+        /**
+         * <p>
+         * reference to the child clusters.
+         * </p>
+         */
+        private List<GUIElementsCluster> childClusters = new LinkedList<GUIElementsCluster>();
+        
+        /**
+         * <p>
+         * reference to the GUI element group if one is created for the cluster
+         * </p>
+         */
+        private IGUIElement group = null;
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return getName();
+        }
+
+        /**
+         * <p>
+         * checks, if the main cluster parents, i.e., the documents of this and the provided cluster
+         * match
+         * </p>
+         *
+         * @param other the other cluster of which the main cluster parents shall be compared to
+         *              this
+         *              
+         * @return true if they match, false else
+         */
+        private boolean clusterParentsMatch(GUIElementsCluster other) {
+            // cluster parent may already be merged and therefore equals --> use system identity
+            // hash code for uniqueness
+            Set<Integer> mainClusterParents1 = new HashSet<Integer>();
+            for (SimilarGUIElement clusteredElem1 : clusteredGUIElements) {
+                mainClusterParents1.add(System.identityHashCode(clusteredElem1.mainClusterParent));
+            }
+            
+            Set<Integer> mainClusterParents2 = new HashSet<Integer>();
+            for (SimilarGUIElement clusteredElem2 : other.clusteredGUIElements) {
+                mainClusterParents2.add(System.identityHashCode(clusteredElem2.mainClusterParent));
+            }
+            
+            return mainClusterParents1.equals(mainClusterParents2);
+        }
+
+        /**
+         * <p>
+         * returns true, if this cluster is a default cluster
+         * </p>
+         *
+         * @return
+         */
+        public boolean isDefault() {
+            return clusteredGUIElements.size() <= 0;
+        }
+
+        /**
+         * <p>
+         * sets the GUI element group created for this cluster
+         * </p>
+         *
+         * @param group the GUI element group created for this cluster
+         */
+        private void setGroup(IGUIElement group) {
+            this.group = group;
+        }
+
+        /**
+         * <p>
+         * returns the GUI element group created for this cluster
+         * </p>
+         *
+         * @return the GUI element group created for this cluster
+         */
+        private IGUIElement getGroup() {
+            return group;
+        }
+
+        /**
+         * <p>
+         * checks if the given cluster should be a child of this cluster. It should be a child
+         * if its list of clustered documents is a subset of the list of clustered documents of
+         * this cluster.
+         * </p>
+         *
+         * @param potentialChild the cluster to check
+         * 
+         * @return true, if the cluster should be a child, false else
+         */
+        private boolean isSubCluster(GUIElementsCluster potentialChild) {
+            return 
+                (potentialChild.clusteredGUIElements.size() < clusteredGUIElements.size()) &&
+                (clusteredGUIElements.containsAll(potentialChild.clusteredGUIElements));
+        }
+
+        /**
+         * <p>
+         * sets the list of clustered GUI elements, i.e., documents of which this cluster contains
+         * similar GUI elements
+         * </p>
+         *
+         * @param clusteredGUIElements the new list of clustered GUI elements
+         */
+        private void setClusteredGUIElements(SimilarGUIElements clusteredGUIElements) {
+            this.clusteredGUIElements = clusteredGUIElements;
+        }
+
+        /**
+         * <p>
+         * adds a child cluster to this cluster
+         * </p>
+         *
+         * @param cluster the new child cluster
+         */
+        private void addChildCluster(GUIElementsCluster cluster) {
+            childClusters.add(cluster);
+        }
+
+        /**
+         * <p>
+         * adds similar GUI elements to the list of similar GUI elements
+         * </p>
+         */
+        private void addSimilarGUIElements(SimilarGUIElements similarGuiElements) {
+            similarChildrenGUIElements.add(similarGuiElements);
+        }
+
+        /**
+         * <p>
+         * determines a list of similar GUI elements to which the provided GUI element belongs.
+         * If it finds one, it adds the GUI element to that list. If not, it creates a new one.
+         * </p>
+         *
+         * @param child         the child to to be added to the list of similar GUI elements
+         * @param clusterParent the main parent of the cluster to which the GUI element belongs
+         *                      (usually the document)
+         */
+        private void addSimilarChild(IGUIElement child, IGUIElement clusterParent) {
+            SimilarGUIElements similarGUIElements = null;
+            
+            for (SimilarGUIElements candidate : similarChildrenGUIElements) {
+                if (candidate.elementsMatch(child)) {
+                    similarGUIElements = candidate;
+                }
+            }
+            
+            if (similarGUIElements == null) {
+                similarGUIElements = new SimilarGUIElements();
+                similarChildrenGUIElements.add(similarGUIElements);
+            }
+            
+            similarGUIElements.add(new SimilarGUIElement(child, clusterParent));
+        }
+
+        /**
+         * <p>
+         * checks, if this cluster is a cluster representing the provided main cluster parents
+         * </p>
+         */
+        private boolean isClusterOf(SimilarGUIElements checkedClusterParents) {
+            return (clusteredGUIElements.size() == checkedClusterParents.size()) &&
+                   (clusteredGUIElements.containsAll(checkedClusterParents));
+        }
+
+        /**
+         * <p>
+         * returns a name for this cluster
+         * </p>
+         */
+        private String getName() {
+            StringBuffer ret = new StringBuffer("cluster(");
+            
+            if (clusteredGUIElements.size() > 0) {
+                for (SimilarGUIElement similarGUIElement : clusteredGUIElements) {
+                    if (ret.length() > "cluster(".length()) {
+                        ret.append(", ");
+                    }
+
+                    ret.append(similarGUIElement);
+                }
+            }
+            else {
+                ret.append("default");
+            }
+            
+            ret.append(")");
+            
+            return ret.toString();
+        }
+        
+        /**
+         * <p>
+         * dumps infos of this cluster to the provided stream
+         * </p>
+         */
+//        private void dump(PrintStream out, String indent) {
+//            out.print(indent);
+//            out.print(getName());
+//            out.print(" { ");
+//            
+//            if (similarChildrenGUIElements.size() > 0) {
+//                out.println();
+//                out.print(indent);
+//                out.println("  similar children {");
+//
+//                for (SimilarGUIElements similarGuiElements : similarChildrenGUIElements) {
+//                    similarGuiElements.dump(out, indent + "    ");
+//                }
+//
+//                out.print(indent);
+//                out.println("  }");
+//
+//                if (childClusters.size() > 0) {
+//                    out.print(indent);
+//                    out.println("  child clusters {");
+//
+//                    for (GUIElementsCluster childCluster : childClusters) {
+//                        childCluster.dump(out, indent + "    ");
+//                    }
+//
+//                    out.print(indent);
+//                    out.println("  }");
+//                }
+//
+//                out.print(indent);
+//            }
+//            else if (clusteredGUIElements.size() > 0) {
+//                out.println();
+//                out.print(indent);
+//                out.println("  clustered GUIElements {");
+//
+//                for (SimilarGUIElement clusteredGUIElement : clusteredGUIElements) {
+//                    clusteredGUIElement.dump(out, indent + "    ");
+//                }
+//
+//                out.print(indent);
+//                out.println("  }");
+//                out.print(indent);
+//            }
+//            
+//            out.println("}");
+//        }
+
+    }
+
+    /**
+     * <p>
+     * represents a list of similar GUI elements
+     * </p>
+     */
+    private static class SimilarGUIElements extends LinkedList<SimilarGUIElement> {
+
+        /** 
+         * <p>
+         * default serial version UID
+         * </p>
+         */
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * <p>
+         * checks if the provided GUI element is similar to at least on of the GUI elements
+         * represented by this list.
+         * </p>
+         */
+        private boolean elementsMatch(IGUIElement otherGUIElement) {
+            // it is sufficient, if one of the elements (if any) matches, as the similarity 
+            // check must be transitive
+            if (size() > 0) {
+                return get(0).similarGUIElement.getSpecification().getSimilarity
+                           (otherGUIElement.getSpecification());
+            }
+            else {
+                return true;
+            }
+        }
+    
+        /**
+         * <p>
+         * transforms this list to a list of GUI elements
+         * </p>
+         */
+        private List<IGUIElement> toGUIElementList() {
+            List<IGUIElement> guiElementList = new LinkedList<IGUIElement>();
+            
+            for (SimilarGUIElement similarGUIElement : this) {
+                guiElementList.add(similarGUIElement.similarGUIElement);
+            }
+
+            return guiElementList;
+        }
+
+        /**
+         * <p>
+         * dumps infos of this list to the provided stream
+         * </p>
+         */
+//        private void dump(PrintStream out, String indent) {
+//            out.print(indent);
+//            out.print("{ ");
+//            
+//            for (int i = 0; i < super.size(); i++) {
+//                if (i > 0) {
+//                    out.print(", ");
+//                }
+//                out.print(super.get(i));
+//            }
+//            
+//            out.println(" }");
+//        }
+    }
+    
+    /**
+     * <p>
+     * represents a single similar GUI element and the main cluster parent, i.e., the document to
+     * which it belongs.
+     * </p>
+     */
+    private static class SimilarGUIElement {
+        
+        /**
+         * <p>
+         * the represented GUI element
+         * </p>
+         */
+        private IGUIElement similarGUIElement;
+        
+        /**
+         * <p>
+         * the main cluster parent, i.e., usually the document of the represented GUI element
+         * </p>
+         */
+        private IGUIElement mainClusterParent;
+        
+        /**
+         * <p>
+         * simple constructor to initialize the objects
+         * </p>
+         */
+        private SimilarGUIElement(IGUIElement similarGUIElement, IGUIElement clusterParent) {
+            this.similarGUIElement = similarGUIElement;
+            this.mainClusterParent = clusterParent;
+        }
+
+        /**
+         * <p>
+         * dumps infos about this object to the provided stream
+         * </p>
+         */
+//        private void dump(PrintStream out, String indent) {
+//            out.print(indent);
+//            out.println(this);
+//        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#equals(java.lang.Object)
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            else if (obj instanceof SimilarGUIElement) {
+                return (similarGUIElement.equals(((SimilarGUIElement) obj).similarGUIElement));
+            }
+            else {
+                return false;
+            }
+        }
+
+        /* (non-Javadoc)
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            return similarGUIElement + " (" + mainClusterParent + ")";
+        }
+    }
+        
+}
