// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.plugin.html.commands; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.logging.Level; import de.ugoe.cs.autoquest.CommandHelpers; import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementFactory; import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement; import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLDocument; import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLServer; import de.ugoe.cs.util.console.Command; import de.ugoe.cs.util.console.Console; import de.ugoe.cs.util.console.GlobalDataContainer; /** *

* This command condenses an HTML GUI model. For HTML GUI models, this is a special task, as, e.g., * menu structures resisting on several pages of a website are identified separately. However, for * subsequent analysis, they need to be treated as identical. Therefore, this command identifies * pages (i.e. documents) with equal structures and merges them. For differences in the pages, it * adds groups of GUI elements indicating, which GUI element belongs to which pages and not to * others. *

*

* An example for clarification. Consider the following two pages: *

 * server
 *   |-- document1
 *   |     \-- html
 *   |           \-- body
 *   |                 |-- div (id = menu)
 *   |                 |     \-- ...
 *   |                 |-- div (id = textcontent)
 *   |                 |     \-- ...
 *   |                 \-- div (id = footer)
 *   |                       \-- ...
 *   \-- document2
 *         \-- html
 *               \-- body
 *                     |-- div (id = menu)
 *                     |     \-- ...
 *                     |-- div (id = imagecontent)
 *                     |     \-- ...
 *                     \-- div (id = footer)
 *                           \-- ...
 * 
* They only differ in the central div which has the id textcontent for the first document and * imagecontent for the second. The above GUI model is the result of the monitoring and parsing of * the log files. This command condenses the GUI model to the following structure: *
 * server
 *   \-- document1
 *         \-- html
 *               \-- body
 *                     |-- div (id = menu)
 *                     |     \-- ...
 *                     |-- group (document1)
 *                     |     \-- div (id = textcontent)
 *                     |           \-- ...
 *                     |-- group (document2)
 *                     |     \-- div (id = imagecontent)
 *                     |           \-- ...
 *                     \-- div (id = footer)
 *                           \-- ...
 * 
* This now allows the menu and the footer to be treated as identical over several pages. *

*

* If several but not all pages share similarities, the created groups refer to several documents * at once. *

* * @author Patrick Harms * @version 1.0 */ public class CMDcondenseHTMLGUIModel implements Command { /* * (non-Javadoc) * * @see de.ugoe.cs.util.console.Command#run(java.util.List) */ @Override public void run(List parameters) { String sequencesName; try { sequencesName = (String) parameters.get(0); } catch (Exception e) { throw new IllegalArgumentException("illegal parameters provided: " + e); } Object dataObject = GlobalDataContainer.getInstance().getData(sequencesName + "_targets"); if (dataObject == null) { CommandHelpers.objectNotFoundMessage(sequencesName + "_targets"); return; } if (!(dataObject instanceof GUIModel)) { CommandHelpers.objectNotType(sequencesName, "GUIModel"); return; } GUIModel model = (GUIModel) dataObject; for (IGUIElement root : model.getRootElements()) { if (root instanceof HTMLServer) { try { mergeServer((HTMLServer) root, model); } catch (Exception e) { Console.printerrln("problems while condensing model of server " + root); Console.logException(e); break; } } } } /** *

* Condenses all documents resisting on the same server. For this, the method first identifies * a hierarchy of clusters of similar GUI elements. This is then used to merge similar GUI * elements and to create subgroups for indicating differences between the documents. *

* * @param server the server of which all documents shall be condensed * @param model the GUI model in which the server is referenced */ private void mergeServer(HTMLServer server, GUIModel model) { Console.traceln(Level.INFO, "condensing documents of " + server); Console.traceln(Level.FINE, "creating cluster hierarchy of GUI elements for " + server); GUIElementsCluster rootCluster = getSimilarElementClusterHierarchy(server, model); //rootCluster.dump(System.out, ""); Console.traceln(Level.FINE, "merging GUI elements in same clusters and creating groups"); mergeGUIElementsAccordingToClusters(rootCluster, model, ""); //model.dump(System.out, "UTF-8"); Console.traceln(Level.INFO, "condensed documents of " + server); } /** *

* determines clusters of similar GUI elements being children of the provided server. For this, * the method creates a cluster containing all children of the provided server as similar * GUI elements. Usually, these are all documents on the server. It then initiates creating * the cluster hierarchy by calling the recursive method * {@link #addChildClusters(GUIElementsCluster, GUIModel, String)}. *

* * @param server the server for which the clusters of similar children shall be determined * @param model the GUI model required for identifying children and sub children * * @return a GUI element cluster representing the server. */ private GUIElementsCluster getSimilarElementClusterHierarchy(HTMLServer server, GUIModel model) { GUIElementsCluster cluster = new GUIElementsCluster(); List children = model.getChildren(server); SimilarGUIElements similarGuiElements = new SimilarGUIElements(); for (IGUIElement child : children) { // when starting with the root, the cluster parent is the child itself, i.e. the // document. We expect all documents to be similar. similarGuiElements.add(new SimilarGUIElement(child, child)); } cluster.addSimilarGUIElements(similarGuiElements); // create the cluster structure addChildClusters(cluster, model, ""); return cluster; } /** *

* Determines child clusters of a cluster of given GUI elements. This method calls itself * recursively for each directly identified child cluster. Furthermore, it moves child clusters * to children of other child clusters if they are a subset of the clustered GUI elements. *

* * @param cluster the cluster for which child clusters shall be identified * @param model the model required to be able to determine child GUI elements */ private void addChildClusters(GUIElementsCluster cluster, GUIModel model, String indent) { for (SimilarGUIElements similarGuiElements : cluster.similarChildrenGUIElements) { createSubClustersForChildren(similarGuiElements, cluster, model, indent + " "); } for (GUIElementsCluster childCluster : cluster.childClusters) { addChildClusters(childCluster, model, indent + " "); } createClusterHierachies(cluster); } /** *

* for a set of similar GUI elements, it identifies similar children and clusters them. For * each identified cluster, it adds a new child cluster to the provided parent cluster. GUI * elements having no further children are ignored. *

* * @param similarGuiElements the similar GUI elements of which the children shall be clustered * @param parentCluster the parent cluster to which newly identified cluster are added * as children * @param model the model required to be able to determine child GUI elements */ private void createSubClustersForChildren(SimilarGUIElements similarGuiElements, GUIElementsCluster parentCluster, GUIModel model, String indent) { for (SimilarGUIElement similarGuiElement : similarGuiElements) { List children = model.getChildren(similarGuiElement.similarGUIElement); if (children.size() > 0) { for (IGUIElement child : children) { addToClusterOfSimilarElements (child, similarGuiElement.mainClusterParent, parentCluster, similarGuiElements, model); } } // else { // // search for a default cluster to add all elements to, which have no children // GUIElementsCluster defaultCluster = null; // // for (GUIElementsCluster candidate : parentCluster.childClusters) { // if (candidate.similarChildrenGUIElements.size() == 0) { // defaultCluster = candidate; // break; // } // } // // if (defaultCluster == null) { // defaultCluster = new GUIElementsCluster(); // parentCluster.addChildCluster(defaultCluster); // } // // defaultCluster.clusteredGUIElements.add(similarGuiElement); // } } } /** *

* for a given GUI element, searches the list of child clusters of the parent cluster and adds * the GUI element to the cluster, if the cluster already contains a similar GUI element. If * not, a new cluster is created and added to the list of known clusters. *

* * @param child the child for which the cluster is to be determined * @param clusterParent the root GUI element of the parent cluster * @param parentCluster the parent cluster whose child clusters shall be filled * @param similarGuiElements the similar GUI elements currently matched to each other * @param model the GUI model required to determine children of GUI elements */ private void addToClusterOfSimilarElements(IGUIElement child, IGUIElement clusterParent, GUIElementsCluster parentCluster, SimilarGUIElements similarGuiElements, GUIModel model) { SimilarGUIElements matchingParents = new SimilarGUIElements(); // determine matching parents for (SimilarGUIElement similarGuiElement : similarGuiElements) { for (IGUIElement candidate : model.getChildren(similarGuiElement.similarGUIElement)) { IGUIElementSpec similarityCandidate = candidate.getSpecification(); if (similarityCandidate.getSimilarity(child.getSpecification())) { matchingParents.add(similarGuiElement); break; } } } // check if an appropriate cluster exists GUIElementsCluster cluster = null; for (GUIElementsCluster clusterCandidate : parentCluster.childClusters) { if (clusterCandidate.isClusterOf(matchingParents)) { cluster = clusterCandidate; break; } } if (cluster == null) { cluster = new GUIElementsCluster(); parentCluster.addChildCluster(cluster); cluster.setClusteredGUIElements(matchingParents); } cluster.addSimilarChild(child, clusterParent); } /** *

* clusters of similar children identified for a GUI element may be logical subclusters of * each other. This is, e.g., the case if one cluster contains all elements being part of * document1 and document2 and a further cluster contains all elements of document 1 only. In * this case, the cluster of document1 is added as a child to the other cluster. In this case * it is furthermore required, that a common cluster as child of the document1/document2 cluster * is created carrying the common GUI elements for both clusters. *

* * @param parentCluster the parent cluster for whose children the child hierarchies shall be * created (in-out parameter, as it is changed) */ private void createClusterHierachies(GUIElementsCluster parentCluster) { GUIElementsCluster[] clustersCopy = parentCluster.childClusters.toArray (new GUIElementsCluster[parentCluster.childClusters.size()]); // sort the array starting with the shortest cluster and ending with the longest Arrays.sort(clustersCopy, new Comparator() { @Override public int compare(GUIElementsCluster c1, GUIElementsCluster c2) { return c1.clusteredGUIElements.size() - c2.clusteredGUIElements.size(); } }); Set subClustersToHandle = new HashSet(); // now add smaller clusters to larger ones, if they are parents for (int i = 0; i < (clustersCopy.length - 1); i++) { GUIElementsCluster potentialChild = clustersCopy[i]; // search for the next cluster and add the child for (int j = i + 1; j < clustersCopy.length; j++) { if (clustersCopy[j].isSubCluster(potentialChild)) { clustersCopy[j].addChildCluster(potentialChild); subClustersToHandle.add(clustersCopy[j]); for (int k = 0; k < parentCluster.childClusters.size(); k++) { if (parentCluster.childClusters.get(k) == potentialChild) { parentCluster.childClusters.remove(k); break; } } break; } } } if (subClustersToHandle.size() > 0) { // finally, for all subclusters that were changed, ensure the creation of their internal // hierarchy as well for (GUIElementsCluster subClusterToHandle : subClustersToHandle) { // we need a dedicated common cluster --> add it createClusterHierachies(subClusterToHandle); /*GUIElementsCluster commonCluster = new GUIElementsCluster(); //commonCluster.setClusteredGUIElements(subClusterToHandle.clusteredGUIElements); commonCluster.similarChildrenGUIElements.addAll (subClusterToHandle.similarChildrenGUIElements); subClusterToHandle.similarChildrenGUIElements.clear(); subClusterToHandle.childClusters.add(0, commonCluster);*/ } } } /** *

* called for each cluster to merge similar GUI elements depending on the clusters and to * create GUI element groups if required. Calls itself recursively to be also applied on * child clusters. *

* * @param cluster the cluster of which the similar children shall be merged * @param model the model to be adapted through the merge */ private void mergeGUIElementsAccordingToClusters(GUIElementsCluster cluster, GUIModel model, String indent) { //System.out.println(indent + "handling " + cluster); for (SimilarGUIElements similarGUIElements : cluster.similarChildrenGUIElements) { mergeGUIElements(similarGUIElements, model, indent + " "); } if (cluster.childClusters.size() > 0) { //System.out.println(indent + " handling child clusters"); for (GUIElementsCluster childCluster : cluster.childClusters) { if (cluster.isDefault() || cluster.clusterParentsMatch(childCluster)) { // for default cluster or children not creating subgroups, just traverse the // cluster hierarchy mergeGUIElementsAccordingToClusters(childCluster, model, indent + " "); } else { createClusterGroup(childCluster, model, indent + " "); } } } } /** *

* merges similar GUI elements using the provided model *

* * @param similarGUIElements the GUI elements to merge * @param model the model to be used for merging the GUI elements */ private void mergeGUIElements(SimilarGUIElements similarGUIElements, GUIModel model, String indent) { if (similarGUIElements.size() > 0) { IGUIElement mergeResult = similarGUIElements.get(0).similarGUIElement; while (similarGUIElements.size() > 1) { //System.out.println(indent + "merging " + mergeResult + " and " + // similarGUIElements.get(1).similarGUIElement); mergeResult = model.mergeEventTargets (mergeResult, similarGUIElements.remove(1).similarGUIElement, false); } } } /** *

* creates a group of GUI elements to represent a cluster. Uses the provided model for group * creation. *

* * @param cluster the cluster for which the group shall be created * @param model the model to be used for creating the groups */ private void createClusterGroup(GUIElementsCluster cluster, GUIModel model, String indent) { //System.out.println(indent + "creating group for " + cluster); List guiElementsToGroup = new LinkedList(); for (SimilarGUIElements similarGUIElements : cluster.similarChildrenGUIElements) { mergeGUIElements(similarGUIElements, model, indent); guiElementsToGroup.addAll(similarGUIElements.toGUIElementList()); } //System.out.println(indent + " iterating child clusters of " + cluster); for (GUIElementsCluster childCluster : cluster.childClusters) { if (cluster.isDefault() || cluster.clusterParentsMatch(childCluster)) { // for default cluster or children not creating subgroups, just traverse the // cluster hierarchy mergeGUIElementsAccordingToClusters(childCluster, model, indent + " "); } else { createClusterGroup(childCluster, model, indent + " "); } if (childCluster.getGroup() != null) { if (cluster.isSubCluster(childCluster)) { guiElementsToGroup.add(childCluster.getGroup()); } else { guiElementsToGroup.add(childCluster.getGroup().getParent()); } } } //System.out.println(indent + "grouping: " + guiElementsToGroup); IGUIElement group = model.groupEventTargets (guiElementsToGroup, getGroupName(cluster), GUIElementFactory.getInstance()); //System.out.println(indent + " created group for " + cluster + ": " + group); cluster.setGroup(group); } /** *

* determines a name for a group to be created for the provided cluster *

* * @param cluster the cluster for which a group name shall be determined * * @return an appropriate name */ private String getGroupName(GUIElementsCluster cluster) { StringBuffer name = new StringBuffer(); name.append("group_"); if (cluster.clusteredGUIElements.size() > 0) { List> pathParts = new ArrayList>(); for (SimilarGUIElement guiElement : cluster.clusteredGUIElements) { if (guiElement.mainClusterParent instanceof HTMLDocument) { String path = ((HTMLDocument) guiElement.mainClusterParent).getPath(); if (path.startsWith("/")) { path = path.substring(1); } if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } String[] pathElements = path.split("/"); for (int i = 0; i < pathElements.length; i++) { if (pathParts.size() <= i) { pathParts.add(new LinkedList()); } if (!pathParts.get(i).contains(pathElements[i])) { pathParts.get(i).add(pathElements[i]); } } } else { if (pathParts.size() < 1) { pathParts.add(new LinkedList()); } if (!pathParts.get(0).contains(guiElement.mainClusterParent.getStringIdentifier())) { pathParts.get(0).add(guiElement.mainClusterParent.getStringIdentifier()); } } } for (List pathPart : pathParts) { name.append('/'); if (pathPart.size() > 1) { name.append('['); } int index = 0; for (String elem : pathPart) { if (index++ > 0) { name.append('|'); } name.append(elem); } if (pathPart.size() > 1) { name.append(']'); } } } else { name.append("common"); } return name.toString(); } /* * (non-Javadoc) * * @see de.ugoe.cs.util.console.Command#help() */ @Override public String help() { return "condenseHTMLGUIModel "; } /** *

* represents a cluster of similar GUI elements. It consists of a list of similar GUI elements * represented by the cluster somewhere in the GUI element hierarchy. It contains, furthermore, * a list of GUI elements that belong to the root cluster of the cluster hierarchy, which are * usually the similar documents. It also refers to child clusters, and if created, a GUI * element group representing the cluster. *

*/ private static class GUIElementsCluster { /** *

* the similar children on the same level in the GUI model represented through the cluster *

*/ private List similarChildrenGUIElements = new LinkedList(); /** *

* the similar root GUI elements on the first level of the created hierarchy (usually * the documents on a website) to which the similar GUI elements belong *

*/ private SimilarGUIElements clusteredGUIElements = new SimilarGUIElements(); /** *

* reference to the child clusters. *

*/ private List childClusters = new LinkedList(); /** *

* reference to the GUI element group if one is created for the cluster *

*/ private IGUIElement group = null; /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return getName(); } /** *

* checks, if the main cluster parents, i.e., the documents of this and the provided cluster * match *

* * @param other the other cluster of which the main cluster parents shall be compared to * this * * @return true if they match, false else */ private boolean clusterParentsMatch(GUIElementsCluster other) { // cluster parent may already be merged and therefore equals --> use system identity // hash code for uniqueness Set mainClusterParents1 = new HashSet(); for (SimilarGUIElement clusteredElem1 : clusteredGUIElements) { mainClusterParents1.add(System.identityHashCode(clusteredElem1.mainClusterParent)); } Set mainClusterParents2 = new HashSet(); for (SimilarGUIElement clusteredElem2 : other.clusteredGUIElements) { mainClusterParents2.add(System.identityHashCode(clusteredElem2.mainClusterParent)); } return mainClusterParents1.equals(mainClusterParents2); } /** *

* returns true, if this cluster is a default cluster *

* * @return */ public boolean isDefault() { return clusteredGUIElements.size() <= 0; } /** *

* sets the GUI element group created for this cluster *

* * @param group the GUI element group created for this cluster */ private void setGroup(IGUIElement group) { this.group = group; } /** *

* returns the GUI element group created for this cluster *

* * @return the GUI element group created for this cluster */ private IGUIElement getGroup() { return group; } /** *

* checks if the given cluster should be a child of this cluster. It should be a child * if its list of clustered documents is a subset of the list of clustered documents of * this cluster. *

* * @param potentialChild the cluster to check * * @return true, if the cluster should be a child, false else */ private boolean isSubCluster(GUIElementsCluster potentialChild) { return (potentialChild.clusteredGUIElements.size() < clusteredGUIElements.size()) && (clusteredGUIElements.containsAll(potentialChild.clusteredGUIElements)); } /** *

* sets the list of clustered GUI elements, i.e., documents of which this cluster contains * similar GUI elements *

* * @param clusteredGUIElements the new list of clustered GUI elements */ private void setClusteredGUIElements(SimilarGUIElements clusteredGUIElements) { this.clusteredGUIElements = clusteredGUIElements; } /** *

* adds a child cluster to this cluster *

* * @param cluster the new child cluster */ private void addChildCluster(GUIElementsCluster cluster) { childClusters.add(cluster); } /** *

* adds similar GUI elements to the list of similar GUI elements *

*/ private void addSimilarGUIElements(SimilarGUIElements similarGuiElements) { similarChildrenGUIElements.add(similarGuiElements); } /** *

* determines a list of similar GUI elements to which the provided GUI element belongs. * If it finds one, it adds the GUI element to that list. If not, it creates a new one. *

* * @param child the child to to be added to the list of similar GUI elements * @param clusterParent the main parent of the cluster to which the GUI element belongs * (usually the document) */ private void addSimilarChild(IGUIElement child, IGUIElement clusterParent) { SimilarGUIElements similarGUIElements = null; for (SimilarGUIElements candidate : similarChildrenGUIElements) { if (candidate.elementsMatch(child)) { similarGUIElements = candidate; } } if (similarGUIElements == null) { similarGUIElements = new SimilarGUIElements(); similarChildrenGUIElements.add(similarGUIElements); } similarGUIElements.add(new SimilarGUIElement(child, clusterParent)); } /** *

* checks, if this cluster is a cluster representing the provided main cluster parents *

*/ private boolean isClusterOf(SimilarGUIElements checkedClusterParents) { return (clusteredGUIElements.size() == checkedClusterParents.size()) && (clusteredGUIElements.containsAll(checkedClusterParents)); } /** *

* returns a name for this cluster *

*/ private String getName() { StringBuffer ret = new StringBuffer("cluster("); if (clusteredGUIElements.size() > 0) { ret.append(clusteredGUIElements.get(0).similarGUIElement); ret.append(" ["); int length = ret.length(); for (SimilarGUIElement similarGUIElement : clusteredGUIElements) { if (ret.length() > length) { ret.append(", "); } ret.append(similarGUIElement.mainClusterParent); } ret.append(']'); } else { ret.append("default"); } ret.append(")"); return ret.toString(); } /** *

* dumps infos of this cluster to the provided stream *

*/ // private void dump(PrintStream out, String indent) { // out.print(indent); // out.print(getName()); // out.println(" { "); // // if (clusteredGUIElements.size() > 0) { // out.print(indent); // out.println(" clustered GUIElements {"); // // for (SimilarGUIElement clusteredGUIElement : clusteredGUIElements) { // clusteredGUIElement.dump(out, indent + " "); // } // // out.print(indent); // out.println(" }"); // } // // if (similarChildrenGUIElements.size() > 0) { // out.print(indent); // out.println(" similar children {"); // // for (SimilarGUIElements similarGuiElements : similarChildrenGUIElements) { // similarGuiElements.dump(out, indent + " "); // } // // out.print(indent); // out.println(" }"); // } // // if (childClusters.size() > 0) { // out.print(indent); // out.println(" child clusters {"); // // for (GUIElementsCluster childCluster : childClusters) { // childCluster.dump(out, indent + " "); // } // // out.print(indent); // out.println(" }"); // } // // out.print(indent); // out.println("}"); // } } /** *

* represents a list of similar GUI elements *

*/ private static class SimilarGUIElements extends LinkedList { /** *

* default serial version UID *

*/ private static final long serialVersionUID = 1L; /** *

* checks if the provided GUI element is similar to at least on of the GUI elements * represented by this list. *

*/ private boolean elementsMatch(IGUIElement otherGUIElement) { // it is sufficient, if one of the elements (if any) matches, as the similarity // check must be transitive if (size() > 0) { return get(0).similarGUIElement.getSpecification().getSimilarity (otherGUIElement.getSpecification()); } else { return true; } } /** *

* transforms this list to a list of GUI elements *

*/ private List toGUIElementList() { List guiElementList = new LinkedList(); for (SimilarGUIElement similarGUIElement : this) { guiElementList.add(similarGUIElement.similarGUIElement); } return guiElementList; } /** *

* dumps infos of this list to the provided stream *

*/ // private void dump(PrintStream out, String indent) { // out.print(indent); // out.print("{ "); // // for (int i = 0; i < super.size(); i++) { // if (i > 0) { // out.print(", "); // } // out.print(super.get(i)); // } // // out.println(" }"); // } } /** *

* represents a single similar GUI element and the main cluster parent, i.e., the document to * which it belongs. *

*/ private static class SimilarGUIElement { /** *

* the represented GUI element *

*/ private IGUIElement similarGUIElement; /** *

* the main cluster parent, i.e., usually the document of the represented GUI element *

*/ private IGUIElement mainClusterParent; /** *

* simple constructor to initialize the objects *

*/ private SimilarGUIElement(IGUIElement similarGUIElement, IGUIElement clusterParent) { this.similarGUIElement = similarGUIElement; this.mainClusterParent = clusterParent; } /** *

* dumps infos about this object to the provided stream *

*/ // private void dump(PrintStream out, String indent) { // out.print(indent); // out.println(this); // } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj == this) { return true; } else if (obj instanceof SimilarGUIElement) { return (similarGUIElement.equals(((SimilarGUIElement) obj).similarGUIElement)); } else { return false; } } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return similarGUIElement.hashCode(); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return similarGUIElement + " (" + mainClusterParent + ")"; } } }