// Module : $RCSfile: GUIModel.java,v $ // Version : $Revision: 0.0 $ $Author: pharms $ $Date: 14.08.2012 $ // Project : quest-core-events // Creation : 2012 by pharms // Copyright : Patrick Harms, 2012 package de.ugoe.cs.quest.eventcore.guimodel; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** *

* The goal of a GUI model is to correctly l *

* * @version $Revision: $ $Date: 14.08.2012$ * @author 2012, last modified by $Author: pharms$ */ public class GUIModel { /** * */ private TreeNode root = new TreeNode(); /** * */ private List allNodes = new ArrayList(); /** * TODO: comment * * @param currentGUIElementPath * @return * @throws GUIModelException */ public IGUIElement integratePath(List guiElementPath, IGUIElementFactory guiElementFactory) throws GUIModelException { if ((guiElementPath == null) || (guiElementPath.size() <= 0)) { throw new IllegalArgumentException ("GUI element path must contain at least one element"); } List remainingPath = new LinkedList(); for (IGUIElementSpec spec : guiElementPath) { remainingPath.add(spec); } return integratePath(root, remainingPath, guiElementFactory); } /** * TODO: comment * * @param root * @return */ public List getChildren(IGUIElement guiElement) { for (TreeNode node : allNodes) { if (node.guiElement.equals(guiElement)) { List result = new ArrayList(); if (node.children != null) { for (TreeNode child : node.children) { result.add(child.guiElement); } } return result; } } return null; } /** *

* TODO: comment *

* * @param guiElement * @return */ public IGUIElement getParent(IGUIElement guiElement) { for (TreeNode node : allNodes) { for (TreeNode child : node.children) { if (child.guiElement.equals(guiElement)) { return node.guiElement; } } } return null; } /** * TODO: comment * * @return */ public List getRootElements() { List roots = new ArrayList(); if (root.children != null) { for (TreeNode rootChild : root.children) { roots.add(rootChild.guiElement); } } return roots; } /** *

* TODO: comment *

* * @param root2 * @param guiElementPath * @param guiElementFactory * @return * @throws GUIModelException */ private IGUIElement integratePath(TreeNode parentNode, List remainingPath, IGUIElementFactory guiElementFactory) throws GUIModelException { IGUIElementSpec specToIntegrateElementFor = remainingPath.remove(0); List similarNodes = determineSimilarChildNodes(parentNode, specToIntegrateElementFor); if (similarNodes.size() > 1) { // this may happen, if the GUI elements changed over time (e.g. their name is usually // set later in the program execution) and if they now match because of the changes. // So perform a merge of all similar children of the current parent node to reduce the // model and then try the determination of matching children again. mergeSimilarChildren(parentNode); similarNodes = determineSimilarChildNodes(parentNode, specToIntegrateElementFor); if (similarNodes.size() > 1) { // this can happen, because the similarity check is not transitive. The new GUI // element may be similar to two or more existing ones, but the existing ones // may not be similar to each other. As an example, the new GUI element may // not yet provide sufficient information (such as all names it will have during // the execution of the program). Therefore the similarity check with GUI elements // that already contain more information may return true. But the similarity check // of two GUI elements that already carry a lot of information may return false, // although both are similar to the new GUI element. Therefore, we try a selection // based on the children of the existing and new GUI elements. The one for which // the existing children match best is selected to be the right one. similarNodes = considerSubChildren(similarNodes, specToIntegrateElementFor, remainingPath); if (similarNodes.size() > 1) { System.out.println("TODO: implement handling to many similar children: " + specToIntegrateElementFor); for (TreeNode similarNode : similarNodes) { System.out.println(" " + similarNode.guiElement); } System.out.println(); } } } else if (similarNodes.size() == 1) { similarNodes.get(0).guiElement.updateSpecification(specToIntegrateElementFor); } else if (similarNodes.size() == 0) { // if we get here, the corresponding path does not exist yet. So create it IGUIElement newElement = guiElementFactory.instantiateGUIElement (specToIntegrateElementFor, parentNode.guiElement); similarNodes.add(parentNode.addChild(newElement)); } if (remainingPath.size() > 0) { return integratePath(similarNodes.get(0), remainingPath, guiElementFactory); } else { return similarNodes.get(0).guiElement; } } /** *

* TODO: comment *

* * @param parentNode * @param specToIntegrateElementFor * @return */ private List determineSimilarChildNodes(TreeNode parentNode, IGUIElementSpec specToMatch) { List similarChildren = new ArrayList(); if (parentNode.children != null) { for (TreeNode child : parentNode.children) { if (specToMatch.getSimilarity(child.guiElement.getSpecification())) { similarChildren.add(child); } } } return similarChildren; } /** *

* TODO: comment *

* * @param similarChildren * @param remove * @return */ private List considerSubChildren(List similarNodes, IGUIElementSpec specToMatch, List remainingPath) { List reducedList = new ArrayList(); // check, if there are any children to consider and remove any similar node, that has // further children if (remainingPath.size() <= 0) { for (TreeNode similarNode : similarNodes) { if ((similarNode.children == null) || (similarNode.children.size() == 0)) { reducedList.add(similarNode); } } } else { // if there are further children to consider, then check if there is already a child // node that has an appropriate child IGUIElementSpec subChildSpec = remainingPath.get(0); for (TreeNode similarNode : similarNodes) { if (similarNode.children != null) { for (TreeNode subchild : similarNode.children) { if (subchild.guiElement.getSpecification().getSimilarity(subChildSpec)) { reducedList.add(similarNode); break; } } } } } return reducedList; } /** *

* TODO: comment *

* * @param parentNode */ private void mergeSimilarChildren(TreeNode parentNode) { boolean performedMerge; do { performedMerge = false; if (parentNode.children != null) { for (int i = 0; (!performedMerge) && (i < parentNode.children.size()); i++) { for (int j = i + 1; j < parentNode.children.size(); j++) { IGUIElement elem1 = parentNode.children.get(i).guiElement; IGUIElement elem2 = parentNode.children.get(j).guiElement; if (elem1.getSpecification().getSimilarity(elem2.getSpecification())) { TreeNode replacement = mergeTreeNodes(parentNode.children.get(i), parentNode.children.get(j)); parentNode.children.set(i, replacement); parentNode.children.remove(j); performedMerge = true; break; } } } } } while (performedMerge); } /** *

* TODO: comment *

* * @param treeNode * @param treeNode2 * @return */ private TreeNode mergeTreeNodes(TreeNode treeNode1, TreeNode treeNode2) { TreeNode replacement = new TreeNode(); replacement.guiElement = treeNode1.guiElement; // the following two lines are needed to preserve the references to the existing GUI // elements. If two elements are the same, one should be deleted to make the elements // singletons again. However, there may exist references to both objects. To preserve // these, we simply register the equal GUI elements with each other so that an equals // check can return true. replacement.guiElement.addEqualGUIElement(treeNode2.guiElement); treeNode2.guiElement.addEqualGUIElement(replacement.guiElement); if (treeNode1.children != null) { for (TreeNode child : treeNode1.children) { replacement.addChild(child.guiElement); } } if (treeNode2.children != null) { for (TreeNode child : treeNode2.children) { replacement.addChild(child.guiElement); } } mergeSimilarChildren(replacement); replacement.guiElement.updateSpecification(treeNode2.guiElement.getSpecification()); return replacement; } /** *

* TODO comment *

* * @version $Revision: $ $Date: 17.08.2012$ * @author 2012, last modified by $Author: pharms$ */ private class TreeNode { /** */ private IGUIElement guiElement; /** */ private List children; /** */ //private TreeNode parent; /** *

* TODO: comment *

* * @param guiElement * @return */ private TreeNode addChild(IGUIElement guiElement) { if (children == null) { children = new ArrayList(); } TreeNode child = new TreeNode(); child.guiElement = guiElement; //child.parent = this; children.add(child); allNodes.add(child); return child; } } }