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

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