package de.ugoe.cs.eventbench.windows.data; import java.io.ByteArrayInputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.apache.commons.collections15.CollectionUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** *
* This class implements a comparator for target strings of MFC GUIs. It * internally maintains a collection of all targets that have been compared to * ensure the transitivity of the equals relation. This memory can always be * deleted by calling {@link #reset()}. *
* * @author Steffen Herbold * @version 1.0 */ public class MFCTargetComparator { /** ** If mutable is true, new target strings can be added to the internal * memory. This leads to a very expensive {@link #compare(String, String)} * operation. *
** if mutable is set to false, currently possible equal targets are * pre-computed. This pre-computation is expensive and might take a while. * In turn, the {@link #compare(String, String)} operation becomes very * cheap. *
*/ private static boolean mutable = true; /** ** Set of all currently known targets. *
*/ private static Set* Map that contains for all known target strings all equal target strings. * Pre-computed when {@link #mutable} is set to false. *
*/ private static Map* Changes the mutability of the comparator. If the mutability is changed * from true to false, the map {@link #equalTargets} is computed. *
* * @param mutable * new mutability of the comparator */ public static void setMutable(boolean mutable) { if (MFCTargetComparator.mutable == true && mutable == false) { equalTargets = new HashMap* Compares to target strings. The strings are equal, if TODO *
* All target strings are remembered internally, to be able to test for the * third property. *
* * @param target1 * first target string * @param target2 * second target string * @return true, if both targets are equal; false otherwise */ public static boolean compare(String target1, String target2) { boolean result = false; if (mutable) { try { MFCWidget widget1 = null; MFCWidget widget2 = null; if (!"dummy".equals(target1)) { instance.addTarget(target1); knownTargets.add(target1); widget1 = instance.find(target1); } if (!"dummy".equals(target2)) { instance.addTarget(target2); knownTargets.add(target2); widget2 = instance.find(target2); } if (widget1 == null) { return false; } result = (widget1 == widget2); } catch (Exception e) { e.printStackTrace(); } } if (!mutable) { Set* Resets the internal memory of targets. *
*/ public static void reset() { instance = new MFCTargetComparator(); } /** ** Internal handle to the instance of this class (implemented as * Singleton!). *
*/ private static MFCTargetComparator instance = new MFCTargetComparator(); /** ** Private Constructor. Creates a new instance of the class and prevents * instantiation from outside of this class. *
*/ private MFCTargetComparator() { } /** ** List of the root widgets found in the target string. *
*/ private List* Adds a target to the memory. *
* * @param target * target to be added */ private void addTarget(String target) throws Exception { if (target != null) { DocumentBuilder documentBuilder = DocumentBuilderFactory .newInstance().newDocumentBuilder(); Document doc = documentBuilder.parse(new ByteArrayInputStream( ("* Adds a widget extracted from a target to the memory. The widget is placed * as a child/parent of other widget according to the GUI hierarchy of the * application. *
** In case the widget already exists, the existing widget is returned and * the known targets and hashCodes of the existing widget are updated. *
* * @param widgetString * string identifying the widget * @param parent * parent widget; if null, it is a root widget and added to * {@link #rootWidgets} * @return the created widget. */ private MFCWidget addWidget(Element widgetElement, MFCWidget parent) { MFCWidget widget = generateWidget(widgetElement); if (parent == null) { int index = rootWidgets.indexOf(widget); if (index >= 0) { widget = rootWidgets.get(index); widget.names.add(widgetElement.getAttribute("name")); widget.hwnds.add(widgetElement.getAttribute("hwnd")); } else { rootWidgets.add(widget); } } else { int index = parent.children.indexOf(widget); if (index >= 0) { widget = parent.children.get(index); widget.names.add(widgetElement.getAttribute("name")); widget.hwnds.add(widgetElement.getAttribute("hwnd")); } else { parent.children.add(widget); } } return widget; } /** ** Creates a new {@link MFCWidget} from a widget XML element. *
* * @param widgetElement * XML element containing information about the widget * @return created {@link MFCWidget} */ private MFCWidget generateWidget(Element widgetElement) { MFCWidget widget = new MFCWidget(); widget.names.add(widgetElement.getAttribute("name")); widget.hwnds.add(widgetElement.getAttribute("hwnd")); widget.widgetClass = widgetElement.getAttribute("class"); widget.resourceId = widgetElement.getAttribute("resourceId"); widget.modality = widgetElement.getAttribute("isModal"); return widget; } /** *
* Tries to find the {@link MFCWidget} that the target string identifies in
* the known GUI hierarchy, by traversing the known widgets starting with
* the {@link #rootWidgets}.
*
* @param target
* target string whose widget is searched for
* @return respective {@link MFCWidget} instance if it is found; null
* otherwise
*/
private MFCWidget find(String target) throws Exception {
MFCWidget widget = null;
if (target != null) {
DocumentBuilder documentBuilder = DocumentBuilderFactory
.newInstance().newDocumentBuilder();
Document doc = documentBuilder.parse(new ByteArrayInputStream(
("
* Internal class used to store MFCWidget. The implementation is more like a * C-style structure, than an actual class. *
* * @author Steffen Herbold * @version 1.0 */ private static class MFCWidget { /** ** Set of all known name strings of the widget. *
*/ Set* Set of all known hwnds of the widget. *
*/ Set* Class of the widget. *
*/ String widgetClass; /** ** Resource id of the widget. *
*/ String resourceId; /** ** Modality of the widget. *
*/ String modality; /** ** Pre-computed hash code of the widget. *
*/ int hashCode = 0; /** ** List of children of the widget. *
*/ List* Two widgets are equal, if {@link #widgetClass}, {@link #resourceId}, * and {@link #modality} are equal and the intersection of either the * {@link #hwnds}, the {@link #names}, or both of them is not empty. *
* * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (obj instanceof MFCWidget) { MFCWidget other = (MFCWidget) obj; boolean titleEqual = CollectionUtils.containsAny(names, other.names); boolean hashEqual = CollectionUtils.containsAny(hwnds, other.hwnds); return widgetClass.equals(other.widgetClass) && resourceId.equals(other.resourceId) && modality.equals(other.modality) && (titleEqual || hashEqual); } return false; } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { if (hashCode == 0) { int multiplier = 17; hashCode = multiplier * hashCode + widgetClass.hashCode(); hashCode = multiplier * hashCode + resourceId.hashCode(); hashCode = multiplier * hashCode + modality.hashCode(); } return hashCode; } } }