source: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/commands/CMDcondenseHTMLGUIModel.java @ 1491

Last change on this file since 1491 was 1491, checked in by pharms, 10 years ago
  • implemented a check for the GUI element distance measure. Will be removed in next check in.
File size: 42.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.plugin.html.commands;
16
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Comparator;
21import java.util.HashSet;
22import java.util.IdentityHashMap;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27import java.util.logging.Level;
28
29import de.ugoe.cs.autoquest.CommandHelpers;
30import de.ugoe.cs.autoquest.SequenceInstanceOf;
31import de.ugoe.cs.autoquest.eventcore.Event;
32import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
33import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
34import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec;
35import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument;
36import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer;
37import de.ugoe.cs.util.console.Command;
38import de.ugoe.cs.util.console.Console;
39import de.ugoe.cs.util.console.GlobalDataContainer;
40
41/**
42 * <p>
43 * This command condenses an HTML GUI model. For HTML GUI models, this is a special task, as, e.g.,
44 * menu structures resisting on several pages of a website are identified separately. However, for
45 * subsequent analysis, they need to be treated as identical. Therefore, this command identifies
46 * pages (i.e. documents) with equal structures and merges them. For differences in the pages, it
47 * adds groups of GUI elements indicating, which GUI element belongs to which pages and not to
48 * others.
49 * </p>
50 * <p>
51 * An example for clarification. Consider the following two pages:
52 * <pre>
53 * server
54 *   |-- document1
55 *   |     \-- html
56 *   |           \-- body
57 *   |                 |-- div (id = menu)
58 *   |                 |     \-- ...
59 *   |                 |-- div (id = textcontent)
60 *   |                 |     \-- ...
61 *   |                 \-- div (id = footer)
62 *   |                       \-- ...
63 *   \-- document2
64 *         \-- html
65 *               \-- body
66 *                     |-- div (id = menu)
67 *                     |     \-- ...
68 *                     |-- div (id = imagecontent)
69 *                     |     \-- ...
70 *                     \-- div (id = footer)
71 *                           \-- ...
72 * </pre>
73 * They only differ in the central div which has the id textcontent for the first document and
74 * imagecontent for the second. The above GUI model is the result of the monitoring and parsing of
75 * the log files. This command condenses the GUI model to the following structure:
76 * <pre>
77 * server
78 *   \-- document1
79 *         \-- html
80 *               \-- body
81 *                     |-- div (id = menu)
82 *                     |     \-- ...
83 *                     |-- group (document1)
84 *                     |     \-- div (id = textcontent)
85 *                     |           \-- ...
86 *                     |-- group (document2)
87 *                     |     \-- div (id = imagecontent)
88 *                     |           \-- ...
89 *                     \-- div (id = footer)
90 *                           \-- ...
91 * </pre>
92 * This now allows the menu and the footer to be treated as identical over several pages.
93 * </p>
94 * <p>
95 * If several but not all pages share similarities, the created groups refer to several documents
96 * at once.
97 * </p>
98 *
99 * @author Patrick Harms
100 * @version 1.0
101 */
102public class CMDcondenseHTMLGUIModel implements Command {
103
104    /*
105     * (non-Javadoc)
106     *
107     * @see de.ugoe.cs.util.console.Command#run(java.util.List)
108     */
109    @Override
110    public void run(List<Object> parameters) {
111        String sequencesName;
112
113        try {
114            sequencesName = (String) parameters.get(0);
115        }
116        catch (Exception e) {
117            throw new IllegalArgumentException("illegal parameters provided: " + e);
118        }
119
120        Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName + "_targets");
121        if (dataObject == null) {
122            CommandHelpers.objectNotFoundMessage(sequencesName + "_targets");
123            return;
124        }
125        if (!(dataObject instanceof GUIModel)) {
126            CommandHelpers.objectNotType(sequencesName, "GUIModel");
127            return;
128        }
129
130        GUIModel model = (GUIModel) dataObject;
131       
132        // >>>>>>>>>>>>>>>>>>>>>>>>>>>> Just to test the distance measure
133       
134        System.out.println("checking distance measure correctness");
135        Collection<List<Event>> sequences = null;
136        dataObject = GlobalDataContainer.getInstance().getData(sequencesName);
137        if (dataObject == null) {
138            CommandHelpers.objectNotFoundMessage(sequencesName);
139            return;
140        }
141        if (!SequenceInstanceOf.isCollectionOfSequences(dataObject)) {
142            CommandHelpers.objectNotType(sequencesName, "Collection<List<Event<?>>>");
143            return;
144        }
145
146        sequences = (Collection<List<Event>>) dataObject;
147       
148        Map<IGUIElement, IGUIElement> guiElements = new IdentityHashMap<IGUIElement, IGUIElement>();
149       
150        for (List<Event> sequence : sequences) {
151            for (Event event : sequence) {
152                guiElements.put((IGUIElement) event.getTarget(), (IGUIElement) event.getTarget());
153            }
154        }
155       
156        System.out.println("got " + guiElements.size() + " utilized GUI elements");
157       
158        IGUIElement[] guiElementArray =
159            guiElements.keySet().toArray(new IGUIElement[guiElements.size()]);
160       
161        Arrays.sort(guiElementArray, new Comparator<IGUIElement>() {
162            @Override
163            public int compare(IGUIElement o1, IGUIElement o2) {
164                return o1.getStringIdentifier().compareTo(o2.getStringIdentifier());
165            }
166           
167        });
168       
169        int checkSize = Math.min(guiElements.size() * guiElements.size(), 100000000);
170        double[] expectedDistances = new double[checkSize];
171        int index = 0;
172       
173        OUTER:
174        for (int i = 0; i < guiElementArray.length; i++) {
175            for (int j = 0; j < guiElementArray.length; j++) {
176                if (i == j) {
177                    continue;
178                }
179               
180                if (index == 1703) {
181                    System.out.println("blub");
182                }
183               
184                expectedDistances[index++] = guiElementArray[i].getDistanceTo(guiElementArray[j]);
185               
186                if (index == checkSize) {
187                    break OUTER;
188                }
189            }
190        }
191
192        System.out.println("calculated " + expectedDistances.length + " distances");
193
194        // <<<<<<<<<<<<<<<<<<<<<<<<<<<< Just to test the distance measure
195
196        for (IGUIElement root : model.getRootElements()) {
197            if (root instanceof HTMLServer) {
198                try {
199                    mergeServer((HTMLServer) root, model);
200                }
201                catch (Exception e) {
202                    Console.printerrln("problems while condensing model of server " + root);
203                    Console.logException(e);
204                    break;
205                }
206            }
207        }
208
209        // >>>>>>>>>>>>>>>>>>>>>>>>>>>> Just to test the distance measure
210        index = 0;
211       
212        System.out.println("checking, if distances are still the same");
213        OUTER:
214        for (int i = 0; i < guiElementArray.length; i++) {
215            for (int j = 0; j < guiElementArray.length; j++) {
216                if (i == j) {
217                    continue;
218                }
219
220                if (index == 1703) {
221                    System.out.println("blub");
222                }
223               
224                double newDistance = guiElementArray[i].getDistanceTo(guiElementArray[j]);
225                if ((expectedDistances[index] != newDistance) &&
226                    (expectedDistances[index] != 0.5) && (newDistance != 0.2))
227                {
228                    System.out.println((index ) + ": distance between " + guiElementArray[i] +
229                                       " and " + guiElementArray[j] + " changed due to condense. " +
230                                       "Was " + expectedDistances[index] + " and is now " +
231                                       guiElementArray[i].getDistanceTo(guiElementArray[j]) + ".");
232                }
233               
234                index++;
235               
236                if (index == checkSize) {
237                    break OUTER;
238                }
239            }
240        }
241        // <<<<<<<<<<<<<<<<<<<<<<<<<<<< Just to test the distance measure
242       
243    }
244
245    /**
246     * <p>
247     * Condenses all documents resisting on the same server. For this, the method first identifies
248     * a hierarchy of clusters of similar GUI elements. This is then used to merge similar GUI
249     * elements and to create subgroups for indicating differences between the documents.
250     * </p>
251     *
252     * @param server the server of which all documents shall be condensed
253     * @param model  the GUI model in which the server is referenced
254     */
255    private void mergeServer(HTMLServer server, GUIModel model) {
256        Console.traceln(Level.INFO, "condensing documents of " + server);
257        Console.traceln(Level.FINE, "creating cluster hierarchy of GUI elements for " + server);
258        GUIElementsCluster rootCluster = getSimilarElementClusterHierarchy(server, model);
259
260        //rootCluster.dump(System.out, "");
261           
262        Console.traceln(Level.FINE, "merging GUI elements in same clusters and creating groups");
263        mergeGUIElementsAccordingToClusters(rootCluster, model, "");
264           
265        //model.dump(System.out, "UTF-8");
266        Console.traceln(Level.INFO, "condensed documents of " + server);
267    }
268
269    /**
270     * <p>
271     * determines clusters of similar GUI elements being children of the provided server. For this,
272     * the method creates a cluster containing all children of the provided server as similar
273     * GUI elements. Usually, these are all documents on the server. It then initiates creating
274     * the cluster hierarchy by calling the recursive method
275     * {@link #addChildClusters(GUIElementsCluster, GUIModel, String)}.
276     * </p>
277     *
278     * @param server the server for which the clusters of similar children shall be determined
279     * @param model  the GUI model required for identifying children and sub children
280     *
281     * @return a GUI element cluster representing the server.
282     */
283    private GUIElementsCluster getSimilarElementClusterHierarchy(HTMLServer server, GUIModel model)
284    {
285        GUIElementsCluster cluster = new GUIElementsCluster();
286                List<IGUIElement> children = model.getChildren(server);
287
288       
289        SimilarGUIElements similarGuiElements = new SimilarGUIElements();
290       
291        for (IGUIElement child : children) {             // when starting with the root, the cluster parent is the child itself, i.e. the
292            // document. We expect all documents to be similar.
293
294            similarGuiElements.add(new SimilarGUIElement(child, child));
295        }
296       
297        cluster.addSimilarGUIElements(similarGuiElements);
298       
299        // create the cluster structure
300        addChildClusters(cluster, model, "");
301       
302        return cluster;
303    }
304   
305
306    /**
307     * <p>
308     * Determines child clusters of a cluster of given GUI elements. This method calls itself
309     * recursively for each directly identified child cluster. Furthermore, it moves child clusters
310     * to children of other child clusters if they are a subset of the clustered GUI elements.
311     * </p>
312     *
313     * @param cluster the cluster for which child clusters shall be identified
314     * @param model   the model required to be able to determine child GUI elements
315     */
316    private void addChildClusters(GUIElementsCluster cluster, GUIModel model, String indent) {
317        for (SimilarGUIElements similarGuiElements : cluster.similarChildrenGUIElements) {
318            createSubClustersForChildren(similarGuiElements, cluster, model, indent + "  ");
319        }
320       
321        for (GUIElementsCluster childCluster : cluster.childClusters) {
322            addChildClusters(childCluster, model, indent + "  ");
323        }
324       
325        createClusterHierachies(cluster);
326    }
327   
328    /**
329     * <p>
330     * for a set of similar GUI elements, it identifies similar children and clusters them. For
331     * each identified cluster, it adds a new child cluster to the provided parent cluster. GUI
332     * elements having no further children are ignored.
333     * </p>
334     *
335     * @param similarGuiElements the similar GUI elements of which the children shall be clustered
336     * @param parentCluster      the parent cluster to which newly identified cluster are added
337     *                           as children
338     * @param model              the model required to be able to determine child GUI elements
339     */
340    private void createSubClustersForChildren(SimilarGUIElements similarGuiElements,
341                                              GUIElementsCluster parentCluster,
342                                              GUIModel           model,
343                                              String             indent)
344    {
345        for (SimilarGUIElement similarGuiElement : similarGuiElements) {
346            List<IGUIElement> children = model.getChildren(similarGuiElement.similarGUIElement);
347       
348            if (children.size() > 0) {
349                for (IGUIElement child : children) {
350                    addToClusterOfSimilarElements
351                        (child, similarGuiElement.mainClusterParent, parentCluster,
352                         similarGuiElements, model);
353                }
354            }
355//            else {
356//                // search for a default cluster to add all elements to, which have no children
357//                GUIElementsCluster defaultCluster = null;
358//               
359//                for (GUIElementsCluster candidate : parentCluster.childClusters) {
360//                    if (candidate.similarChildrenGUIElements.size() == 0) {
361//                        defaultCluster = candidate;
362//                        break;
363//                    }
364//                }
365//               
366//                if (defaultCluster == null) {
367//                    defaultCluster = new GUIElementsCluster();
368//                    parentCluster.addChildCluster(defaultCluster);
369//                }
370//               
371//                defaultCluster.clusteredGUIElements.add(similarGuiElement);
372//            }
373        }
374    }
375
376    /**
377     * <p>
378     * for a given GUI element, searches the list of child clusters of the parent cluster and adds
379     * the GUI element to the cluster, if the cluster already contains a similar GUI element. If
380     * not, a new cluster is created and added to the list of known clusters.
381     * </p>
382     *
383     * @param child              the child for which the cluster is to be determined
384     * @param clusterParent      the root GUI element of the parent cluster
385     * @param parentCluster      the parent cluster whose child clusters shall be filled
386     * @param similarGuiElements the similar GUI elements currently matched to each other
387     * @param model              the GUI model required to determine children of GUI elements
388     */
389    private void addToClusterOfSimilarElements(IGUIElement        child,
390                                               IGUIElement        clusterParent,
391                                               GUIElementsCluster parentCluster,
392                                               SimilarGUIElements similarGuiElements,
393                                               GUIModel           model)
394    {
395        SimilarGUIElements matchingParents = new SimilarGUIElements();
396       
397        // determine matching parents
398        for (SimilarGUIElement similarGuiElement : similarGuiElements) {
399            for (IGUIElement candidate : model.getChildren(similarGuiElement.similarGUIElement)) {
400                IGUIElementSpec similarityCandidate = candidate.getSpecification();
401                if (similarityCandidate.getSimilarity(child.getSpecification())) {
402                    matchingParents.add(similarGuiElement);
403                    break;
404                }
405            }
406        }
407       
408        // check if an appropriate cluster exists
409        GUIElementsCluster cluster = null;
410       
411        for (GUIElementsCluster clusterCandidate : parentCluster.childClusters) {
412            if (clusterCandidate.isClusterOf(matchingParents)) {
413                cluster = clusterCandidate;
414                break;
415            }
416        }
417       
418        if (cluster == null) {
419            cluster = new GUIElementsCluster();
420            parentCluster.addChildCluster(cluster);
421            cluster.setClusteredGUIElements(matchingParents);
422        }
423       
424        cluster.addSimilarChild(child, clusterParent);
425    }
426
427    /**
428     * <p>
429     * clusters of similar children identified for a GUI element may be logical subclusters of
430     * each other. This is, e.g., the case if one cluster contains all elements being part of
431     * document1 and document2 and a further cluster contains all elements of document 1 only. In
432     * this case, the cluster of document1 is added as a child to the other cluster. In this case
433     * it is furthermore required, that a common cluster as child of the document1/document2 cluster
434     * is created carrying the common GUI elements for both clusters.
435     * </p>
436     *
437     * @param parentCluster the parent cluster for whose children the child hierarchies shall be
438     *                      created (in-out parameter, as it is changed)
439     */
440    private void createClusterHierachies(GUIElementsCluster parentCluster) {
441        GUIElementsCluster[] clustersCopy = parentCluster.childClusters.toArray
442            (new GUIElementsCluster[parentCluster.childClusters.size()]);
443       
444        // sort the array starting with the shortest cluster and ending with the longest
445        Arrays.sort(clustersCopy, new Comparator<GUIElementsCluster>() {
446            @Override
447            public int compare(GUIElementsCluster c1, GUIElementsCluster c2) {
448                return c1.clusteredGUIElements.size() - c2.clusteredGUIElements.size();
449            }
450        });
451       
452        Set<GUIElementsCluster> subClustersToHandle = new HashSet<GUIElementsCluster>();
453       
454        // now add smaller clusters to larger ones, if they are parents
455        for (int i = 0; i < (clustersCopy.length - 1); i++) {
456            GUIElementsCluster potentialChild = clustersCopy[i];
457           
458            // search for the next cluster and add the child
459            for (int j = i + 1; j < clustersCopy.length; j++) {
460                if (clustersCopy[j].isSubCluster(potentialChild)) {
461                   
462                    clustersCopy[j].addChildCluster(potentialChild);
463                    subClustersToHandle.add(clustersCopy[j]);
464                   
465                    for (int k = 0; k < parentCluster.childClusters.size(); k++) {
466                        if (parentCluster.childClusters.get(k) == potentialChild) {
467                            parentCluster.childClusters.remove(k);
468                            break;
469                        }
470                    }
471                   
472                    break;
473                }
474            }
475        }
476       
477        if (subClustersToHandle.size() > 0) {
478            // finally, for all subclusters that were changed, ensure the creation of their internal
479            // hierarchy as well
480            for (GUIElementsCluster subClusterToHandle : subClustersToHandle) {
481                // we need a dedicated common cluster --> add it
482                createClusterHierachies(subClusterToHandle);
483               
484                /*GUIElementsCluster commonCluster = new GUIElementsCluster();
485                //commonCluster.setClusteredGUIElements(subClusterToHandle.clusteredGUIElements);
486                commonCluster.similarChildrenGUIElements.addAll
487                    (subClusterToHandle.similarChildrenGUIElements);
488               
489                subClusterToHandle.similarChildrenGUIElements.clear();
490                subClusterToHandle.childClusters.add(0, commonCluster);*/
491            }
492        }
493    }
494
495    /**
496     * <p>
497     * called for each cluster to merge similar GUI elements depending on the clusters and to
498     * create GUI element groups if required. Calls itself recursively to be also applied on
499     * child clusters.
500     * </p>
501     *
502     * @param cluster the cluster of which the similar children shall be merged
503     * @param model   the model to be adapted through the merge
504     */
505    private void mergeGUIElementsAccordingToClusters(GUIElementsCluster cluster,
506                                                     GUIModel           model,
507                                                     String             indent)
508    {
509        //System.out.println(indent + "handling " + cluster);
510       
511        for (SimilarGUIElements similarGUIElements : cluster.similarChildrenGUIElements) {
512            mergeGUIElements(similarGUIElements, model, indent + "  ");
513        }
514       
515        if (cluster.childClusters.size() > 0) {
516            //System.out.println(indent + "  handling child clusters");
517           
518            for (GUIElementsCluster childCluster : cluster.childClusters) {
519                if (cluster.isDefault() || cluster.clusterParentsMatch(childCluster)) {
520                    // for default cluster or children not creating subgroups, just traverse the
521                    // cluster hierarchy
522                    mergeGUIElementsAccordingToClusters(childCluster, model, indent + "    ");
523                }
524                else {
525                    createClusterGroup(childCluster, model, indent + "    ");
526                }
527            }
528        }
529    }
530
531    /**
532     * <p>
533     * merges similar GUI elements using the provided model
534     * </p>
535     *
536     * @param similarGUIElements the GUI elements to merge
537     * @param model              the model to be used for merging the GUI elements
538     */
539    private void mergeGUIElements(SimilarGUIElements similarGUIElements,
540                                  GUIModel           model,
541                                  String             indent)
542    {
543        if (similarGUIElements.size() > 0) {
544            IGUIElement mergeResult = similarGUIElements.get(0).similarGUIElement;
545
546            while (similarGUIElements.size() > 1) {
547                //System.out.println(indent + "merging " + mergeResult + " and " +
548                //                   similarGUIElements.get(1).similarGUIElement);
549                mergeResult = model.mergeGUIElements
550                        (mergeResult, similarGUIElements.remove(1).similarGUIElement, false);
551            }
552        }
553    }
554
555    /**
556     * <p>
557     * creates a group of GUI elements to represent a cluster. Uses the provided model for group
558     * creation.
559     * </p>
560     *
561     * @param cluster the cluster for which the group shall be created
562     * @param model   the model to be used for creating the groups
563    */
564    private void createClusterGroup(GUIElementsCluster cluster, GUIModel model, String indent) {
565        //System.out.println(indent + "creating group for " + cluster);
566
567        List<IGUIElement> guiElementsToGroup = new LinkedList<IGUIElement>();
568       
569        for (SimilarGUIElements similarGUIElements : cluster.similarChildrenGUIElements) {
570            mergeGUIElements(similarGUIElements, model, indent);
571            guiElementsToGroup.addAll(similarGUIElements.toGUIElementList());
572        }
573       
574        //System.out.println(indent + "  iterating child clusters of " + cluster);
575        for (GUIElementsCluster childCluster : cluster.childClusters) {
576            if (cluster.isDefault() || cluster.clusterParentsMatch(childCluster)) {
577                // for default cluster or children not creating subgroups, just traverse the
578                // cluster hierarchy
579                mergeGUIElementsAccordingToClusters(childCluster, model, indent + "  ");
580            }
581            else {
582                createClusterGroup(childCluster, model, indent + "  ");
583            }
584           
585            if (childCluster.getGroup() != null) {
586                if (cluster.isSubCluster(childCluster)) {
587                    guiElementsToGroup.add(childCluster.getGroup());
588                }
589                else {
590                    guiElementsToGroup.add(childCluster.getGroup().getParent());
591                }
592            }
593        }
594       
595        //System.out.println(indent + "grouping: " + guiElementsToGroup);
596        IGUIElement group = model.groupGUIElements(guiElementsToGroup, getGroupName(cluster));
597        //System.out.println(indent + "  created group for " + cluster + ": " + group);
598       
599        cluster.setGroup(group);
600    }
601
602    /**
603     * <p>
604     * determines a name for a group to be created for the provided cluster
605     * </p>
606     *
607     * @param cluster the cluster for which a group name shall be determined
608     *
609     * @return an appropriate name
610     */
611    private String getGroupName(GUIElementsCluster cluster) {
612        StringBuffer name = new StringBuffer();
613       
614        name.append("group_");
615       
616        if (cluster.clusteredGUIElements.size() > 0) {
617            List<List<String>> pathParts = new ArrayList<List<String>>();
618           
619            for (SimilarGUIElement guiElement : cluster.clusteredGUIElements) {
620                if (guiElement.mainClusterParent instanceof HTMLDocument) {
621                    String path = ((HTMLDocument) guiElement.mainClusterParent).getPath();
622                   
623                    if (path.startsWith("/")) {
624                        path = path.substring(1);
625                    }
626                   
627                    if (path.endsWith("/")) {
628                        path = path.substring(0, path.length() - 1);
629                    }
630                   
631                    String[] pathElements = path.split("/");
632                   
633                    for (int i = 0; i < pathElements.length; i++) {
634                        if (pathParts.size() <= i) {
635                            pathParts.add(new LinkedList<String>());
636                        }
637                        if (!pathParts.get(i).contains(pathElements[i])) {
638                            pathParts.get(i).add(pathElements[i]);
639                        }
640                    }
641                }
642                else {
643                    if (pathParts.size() < 1) {
644                        pathParts.add(new LinkedList<String>());
645                    }
646                    if (!pathParts.get(0).contains(guiElement.mainClusterParent.getStringIdentifier()))
647                    {
648                        pathParts.get(0).add(guiElement.mainClusterParent.getStringIdentifier());
649                    }
650                }
651            }
652           
653            for (List<String> pathPart : pathParts) {
654                name.append('/');
655                if (pathPart.size() > 1) {
656                    name.append('[');
657                }
658               
659                int index = 0;
660                for (String elem : pathPart) {
661                    if (index++ > 0) {
662                        name.append('|');
663                    }
664                    name.append(elem);
665                }
666               
667                if (pathPart.size() > 1) {
668                    name.append(']');
669                }
670            }
671        }
672        else {
673            name.append("common");
674        }
675       
676        return name.toString();
677    }
678
679    /*
680     * (non-Javadoc)
681     *
682     * @see de.ugoe.cs.util.console.Command#help()
683     */
684    @Override
685    public String help() {
686        return "condenseHTMLGUIModel <sequence>";
687    }
688
689    /**
690     * <p>
691     * represents a cluster of similar GUI elements. It consists of a list of similar GUI elements
692     * represented by the cluster somewhere in the GUI element hierarchy. It contains, furthermore,
693     * a list of GUI elements that belong to the root cluster of the cluster hierarchy, which are
694     * usually the similar documents. It also refers to child clusters, and if created, a GUI
695     * element group representing the cluster.
696     * </p>
697     */
698    private static class GUIElementsCluster {
699       
700        /**
701         * <p>
702         * the similar children on the same level in the GUI model represented through the cluster
703         * </p>
704         */
705        private List<SimilarGUIElements> similarChildrenGUIElements =
706            new LinkedList<SimilarGUIElements>();
707       
708        /**
709         * <p>
710         * the similar root GUI elements on the first level of the created hierarchy (usually
711         * the documents on a website) to which the similar GUI elements belong
712         * </p>
713         */
714        private SimilarGUIElements clusteredGUIElements = new SimilarGUIElements();
715       
716        /**
717         * <p>
718         * reference to the child clusters.
719         * </p>
720         */
721        private List<GUIElementsCluster> childClusters = new LinkedList<GUIElementsCluster>();
722       
723        /**
724         * <p>
725         * reference to the GUI element group if one is created for the cluster
726         * </p>
727         */
728        private IGUIElement group = null;
729
730        /* (non-Javadoc)
731         * @see java.lang.Object#toString()
732         */
733        @Override
734        public String toString() {
735            return getName();
736        }
737
738        /**
739         * <p>
740         * checks, if the main cluster parents, i.e., the documents of this and the provided cluster
741         * match
742         * </p>
743         *
744         * @param other the other cluster of which the main cluster parents shall be compared to
745         *              this
746         *             
747         * @return true if they match, false else
748         */
749        private boolean clusterParentsMatch(GUIElementsCluster other) {
750            // cluster parent may already be merged and therefore equals --> use system identity
751            // hash code for uniqueness
752            Set<Integer> mainClusterParents1 = new HashSet<Integer>();
753            for (SimilarGUIElement clusteredElem1 : clusteredGUIElements) {
754                mainClusterParents1.add(System.identityHashCode(clusteredElem1.mainClusterParent));
755            }
756           
757            Set<Integer> mainClusterParents2 = new HashSet<Integer>();
758            for (SimilarGUIElement clusteredElem2 : other.clusteredGUIElements) {
759                mainClusterParents2.add(System.identityHashCode(clusteredElem2.mainClusterParent));
760            }
761           
762            return mainClusterParents1.equals(mainClusterParents2);
763        }
764
765        /**
766         * <p>
767         * returns true, if this cluster is a default cluster
768         * </p>
769         *
770         * @return
771         */
772        public boolean isDefault() {
773            return clusteredGUIElements.size() <= 0;
774        }
775
776        /**
777         * <p>
778         * sets the GUI element group created for this cluster
779         * </p>
780         *
781         * @param group the GUI element group created for this cluster
782         */
783        private void setGroup(IGUIElement group) {
784            this.group = group;
785        }
786
787        /**
788         * <p>
789         * returns the GUI element group created for this cluster
790         * </p>
791         *
792         * @return the GUI element group created for this cluster
793         */
794        private IGUIElement getGroup() {
795            return group;
796        }
797
798        /**
799         * <p>
800         * checks if the given cluster should be a child of this cluster. It should be a child
801         * if its list of clustered documents is a subset of the list of clustered documents of
802         * this cluster.
803         * </p>
804         *
805         * @param potentialChild the cluster to check
806         *
807         * @return true, if the cluster should be a child, false else
808         */
809        private boolean isSubCluster(GUIElementsCluster potentialChild) {
810            return
811                (potentialChild.clusteredGUIElements.size() < clusteredGUIElements.size()) &&
812                (clusteredGUIElements.containsAll(potentialChild.clusteredGUIElements));
813        }
814
815        /**
816         * <p>
817         * sets the list of clustered GUI elements, i.e., documents of which this cluster contains
818         * similar GUI elements
819         * </p>
820         *
821         * @param clusteredGUIElements the new list of clustered GUI elements
822         */
823        private void setClusteredGUIElements(SimilarGUIElements clusteredGUIElements) {
824            this.clusteredGUIElements = clusteredGUIElements;
825        }
826
827        /**
828         * <p>
829         * adds a child cluster to this cluster
830         * </p>
831         *
832         * @param cluster the new child cluster
833         */
834        private void addChildCluster(GUIElementsCluster cluster) {
835            childClusters.add(cluster);
836        }
837
838        /**
839         * <p>
840         * adds similar GUI elements to the list of similar GUI elements
841         * </p>
842         */
843        private void addSimilarGUIElements(SimilarGUIElements similarGuiElements) {
844            similarChildrenGUIElements.add(similarGuiElements);
845        }
846
847        /**
848         * <p>
849         * determines a list of similar GUI elements to which the provided GUI element belongs.
850         * If it finds one, it adds the GUI element to that list. If not, it creates a new one.
851         * </p>
852         *
853         * @param child         the child to to be added to the list of similar GUI elements
854         * @param clusterParent the main parent of the cluster to which the GUI element belongs
855         *                      (usually the document)
856         */
857        private void addSimilarChild(IGUIElement child, IGUIElement clusterParent) {
858            SimilarGUIElements similarGUIElements = null;
859           
860            for (SimilarGUIElements candidate : similarChildrenGUIElements) {
861                if (candidate.elementsMatch(child)) {
862                    similarGUIElements = candidate;
863                }
864            }
865           
866            if (similarGUIElements == null) {
867                similarGUIElements = new SimilarGUIElements();
868                similarChildrenGUIElements.add(similarGUIElements);
869            }
870           
871            similarGUIElements.add(new SimilarGUIElement(child, clusterParent));
872        }
873
874        /**
875         * <p>
876         * checks, if this cluster is a cluster representing the provided main cluster parents
877         * </p>
878         */
879        private boolean isClusterOf(SimilarGUIElements checkedClusterParents) {
880            return (clusteredGUIElements.size() == checkedClusterParents.size()) &&
881                   (clusteredGUIElements.containsAll(checkedClusterParents));
882        }
883
884        /**
885         * <p>
886         * returns a name for this cluster
887         * </p>
888         */
889        private String getName() {
890            StringBuffer ret = new StringBuffer("cluster(");
891           
892            if (clusteredGUIElements.size() > 0) {
893                ret.append(clusteredGUIElements.get(0).similarGUIElement);
894                ret.append(" [");
895               
896                int length = ret.length();
897                for (SimilarGUIElement similarGUIElement : clusteredGUIElements) {
898                    if (ret.length() > length) {
899                        ret.append(", ");
900                    }
901
902                    ret.append(similarGUIElement.mainClusterParent);
903                }
904               
905                ret.append(']');
906            }
907            else {
908                ret.append("default");
909            }
910           
911            ret.append(")");
912           
913            return ret.toString();
914        }
915       
916        /**
917         * <p>
918         * dumps infos of this cluster to the provided stream
919         * </p>
920         */
921//        private void dump(PrintStream out, String indent) {
922//            out.print(indent);
923//            out.print(getName());
924//            out.println(" { ");
925//           
926//            if (clusteredGUIElements.size() > 0) {
927//                out.print(indent);
928//                out.println("  clustered GUIElements {");
929//
930//                for (SimilarGUIElement clusteredGUIElement : clusteredGUIElements) {
931//                    clusteredGUIElement.dump(out, indent + "    ");
932//                }
933//
934//                out.print(indent);
935//                out.println("  }");
936//            }
937//
938//            if (similarChildrenGUIElements.size() > 0) {
939//                out.print(indent);
940//                out.println("  similar children {");
941//
942//                for (SimilarGUIElements similarGuiElements : similarChildrenGUIElements) {
943//                    similarGuiElements.dump(out, indent + "    ");
944//                }
945//
946//                out.print(indent);
947//                out.println("  }");
948//            }
949//
950//            if (childClusters.size() > 0) {
951//                out.print(indent);
952//                out.println("  child clusters {");
953//
954//                for (GUIElementsCluster childCluster : childClusters) {
955//                    childCluster.dump(out, indent + "    ");
956//                }
957//
958//                out.print(indent);
959//                out.println("  }");
960//            }
961//           
962//            out.print(indent);
963//            out.println("}");
964//        }
965
966    }
967
968    /**
969     * <p>
970     * represents a list of similar GUI elements
971     * </p>
972     */
973    private static class SimilarGUIElements extends LinkedList<SimilarGUIElement> {
974
975        /**
976         * <p>
977         * default serial version UID
978         * </p>
979         */
980        private static final long serialVersionUID = 1L;
981
982        /**
983         * <p>
984         * checks if the provided GUI element is similar to at least on of the GUI elements
985         * represented by this list.
986         * </p>
987         */
988        private boolean elementsMatch(IGUIElement otherGUIElement) {
989            // it is sufficient, if one of the elements (if any) matches, as the similarity
990            // check must be transitive
991            if (size() > 0) {
992                return get(0).similarGUIElement.getSpecification().getSimilarity
993                           (otherGUIElement.getSpecification());
994            }
995            else {
996                return true;
997            }
998        }
999   
1000        /**
1001         * <p>
1002         * transforms this list to a list of GUI elements
1003         * </p>
1004         */
1005        private List<IGUIElement> toGUIElementList() {
1006            List<IGUIElement> guiElementList = new LinkedList<IGUIElement>();
1007           
1008            for (SimilarGUIElement similarGUIElement : this) {
1009                guiElementList.add(similarGUIElement.similarGUIElement);
1010            }
1011
1012            return guiElementList;
1013        }
1014
1015        /**
1016         * <p>
1017         * dumps infos of this list to the provided stream
1018         * </p>
1019         */
1020//        private void dump(PrintStream out, String indent) {
1021//            out.print(indent);
1022//            out.print("{ ");
1023//           
1024//            for (int i = 0; i < super.size(); i++) {
1025//                if (i > 0) {
1026//                    out.print(", ");
1027//                }
1028//                out.print(super.get(i));
1029//            }
1030//           
1031//            out.println(" }");
1032//        }
1033    }
1034   
1035    /**
1036     * <p>
1037     * represents a single similar GUI element and the main cluster parent, i.e., the document to
1038     * which it belongs.
1039     * </p>
1040     */
1041    private static class SimilarGUIElement {
1042       
1043        /**
1044         * <p>
1045         * the represented GUI element
1046         * </p>
1047         */
1048        private IGUIElement similarGUIElement;
1049       
1050        /**
1051         * <p>
1052         * the main cluster parent, i.e., usually the document of the represented GUI element
1053         * </p>
1054         */
1055        private IGUIElement mainClusterParent;
1056       
1057        /**
1058         * <p>
1059         * simple constructor to initialize the objects
1060         * </p>
1061         */
1062        private SimilarGUIElement(IGUIElement similarGUIElement, IGUIElement clusterParent) {
1063            this.similarGUIElement = similarGUIElement;
1064            this.mainClusterParent = clusterParent;
1065        }
1066
1067        /**
1068         * <p>
1069         * dumps infos about this object to the provided stream
1070         * </p>
1071         */
1072//        private void dump(PrintStream out, String indent) {
1073//            out.print(indent);
1074//            out.println(this);
1075//        }
1076
1077        /* (non-Javadoc)
1078         * @see java.lang.Object#equals(java.lang.Object)
1079         */
1080        @Override
1081        public boolean equals(Object obj) {
1082            if (obj == this) {
1083                return true;
1084            }
1085            else if (obj instanceof SimilarGUIElement) {
1086                return (similarGUIElement.equals(((SimilarGUIElement) obj).similarGUIElement));
1087            }
1088            else {
1089                return false;
1090            }
1091        }
1092
1093        /* (non-Javadoc)
1094         * @see java.lang.Object#hashCode()
1095         */
1096        @Override
1097        public int hashCode() {
1098            return similarGUIElement.hashCode();
1099        }
1100
1101        /* (non-Javadoc)
1102         * @see java.lang.Object#toString()
1103         */
1104        @Override
1105        public String toString() {
1106            return similarGUIElement + " (" + mainClusterParent + ")";
1107        }
1108
1109    }
1110       
1111}
Note: See TracBrowser for help on using the repository browser.