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

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