source: trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/windows/data/MFCTargetComparator.java

Last change on this file was 413, checked in by sherbold, 13 years ago
  • changed equals method of de.ugoe.cs.eventbench.windows.data.WindowsEvent? to be able to better identify equal widgets throughout multiple sessions. To this aim, the class de.ugoe.cs.eventbench.windows.data.MFCTargetComparator has been introduced to compare the target strings of WindowsEvents?.
  • changed parseXML to set up the MFCTargetComparator
  • Property svn:mime-type set to text/plain
File size: 11.0 KB
Line 
1package de.ugoe.cs.eventbench.windows.data;
2
3import java.io.ByteArrayInputStream;
4import java.util.ArrayList;
5import java.util.HashMap;
6import java.util.HashSet;
7import java.util.LinkedHashSet;
8import java.util.List;
9import java.util.Map;
10import java.util.Set;
11
12import javax.xml.parsers.DocumentBuilder;
13import javax.xml.parsers.DocumentBuilderFactory;
14
15import org.apache.commons.collections15.CollectionUtils;
16import org.w3c.dom.Document;
17import org.w3c.dom.Element;
18import org.w3c.dom.NodeList;
19
20/**
21 * <p>
22 * This class implements a comparator for target strings of MFC GUIs. It
23 * internally maintains a collection of all targets that have been compared to
24 * ensure the transitivity of the equals relation. This memory can always be
25 * deleted by calling {@link #reset()}.
26 * </p>
27 *
28 * @author Steffen Herbold
29 * @version 1.0
30 */
31public class MFCTargetComparator {
32
33        /**
34         * <p>
35         * If mutable is true, new target strings can be added to the internal
36         * memory. This leads to a very expensive {@link #compare(String, String)}
37         * operation.
38         * </p>
39         * <p>
40         * if mutable is set to false, currently possible equal targets are
41         * pre-computed. This pre-computation is expensive and might take a while.
42         * In turn, the {@link #compare(String, String)} operation becomes very
43         * cheap.
44         * </p>
45         */
46        private static boolean mutable = true;
47
48        /**
49         * <p>
50         * Set of all currently known targets.
51         * </p>
52         */
53        private static Set<String> knownTargets = new LinkedHashSet<String>();
54
55        /**
56         * <p>
57         * Map that contains for all known target strings all equal target strings.
58         * Pre-computed when {@link #mutable} is set to false.
59         * </p>
60         */
61        private static Map<String, Set<String>> equalTargets;
62
63        /**
64         * <p>
65         * Changes the mutability of the comparator. If the mutability is changed
66         * from true to false, the map {@link #equalTargets} is computed.
67         * </p>
68         *
69         * @param mutable
70         *            new mutability of the comparator
71         */
72        public static void setMutable(boolean mutable) {
73                if (MFCTargetComparator.mutable == true && mutable == false) {
74                        equalTargets = new HashMap<String, Set<String>>();
75                        for (String target1 : knownTargets) {
76                                Set<String> curEqualTargets = new HashSet<String>();
77                                for (String target2 : knownTargets) {
78                                        if (compare(target1, target2)) {
79                                                curEqualTargets.add(target2);
80                                        }
81                                }
82                                equalTargets.put(target1, curEqualTargets);
83                        }
84                }
85                MFCTargetComparator.mutable = mutable;
86        }
87
88        /**
89         * <p>
90         * Compares to target strings. The strings are equal, if TODO
91         * <ul>
92         * <li>the class, resourceId, and modality of all widgets are equal</li>
93         * <li>either the name or the hashCode of all widgets are equal</li>
94         * <li>either the name or the hashCode has been observed in one equal
95         * instance of a widget, for all widgets.</li>
96         * </ul>
97         * </p>
98         * <p>
99         * All target strings are remembered internally, to be able to test for the
100         * third property.
101         * </p>
102         *
103         * @param target1
104         *            first target string
105         * @param target2
106         *            second target string
107         * @return true, if both targets are equal; false otherwise
108         */
109        public static boolean compare(String target1, String target2) {
110                boolean result = false;
111                if (mutable) {
112                        try {
113                                MFCWidget widget1 = null;
114                                MFCWidget widget2 = null;
115                                if (!"dummy".equals(target1)) {
116                                        instance.addTarget(target1);
117                                        knownTargets.add(target1);
118                                        widget1 = instance.find(target1);
119                                }
120                                if (!"dummy".equals(target2)) {
121                                        instance.addTarget(target2);
122                                        knownTargets.add(target2);
123                                        widget2 = instance.find(target2);
124                                }
125                                if (widget1 == null) {
126                                        return false;
127                                }
128                                result = (widget1 == widget2);
129                        } catch (Exception e) {
130                                e.printStackTrace();
131                        }
132                }
133
134                if (!mutable) {
135                        Set<String> curEquals = equalTargets.get(target1);
136                        if (curEquals != null) {
137                                result = curEquals.contains(target2);
138                        }
139                }
140
141                return result;
142        }
143
144        /**
145         * <p>
146         * Resets the internal memory of targets.
147         * </p>
148         */
149        public static void reset() {
150                instance = new MFCTargetComparator();
151        }
152
153        /**
154         * <p>
155         * Internal handle to the instance of this class (implemented as
156         * Singleton!).
157         * </p>
158         */
159        private static MFCTargetComparator instance = new MFCTargetComparator();
160
161        /**
162         * <p>
163         * Private Constructor. Creates a new instance of the class and prevents
164         * instantiation from outside of this class.
165         * </p>
166         */
167        private MFCTargetComparator() {
168        }
169
170        /**
171         * <p>
172         * List of the root widgets found in the target string.
173         * </p>
174         */
175        private List<MFCWidget> rootWidgets = new ArrayList<MFCTargetComparator.MFCWidget>();
176
177        /**
178         * <p>
179         * Adds a target to the memory.
180         * </p>
181         *
182         * @param target
183         *            target to be added
184         */
185        private void addTarget(String target) throws Exception {
186                if (target != null) {
187                        DocumentBuilder documentBuilder = DocumentBuilderFactory
188                                        .newInstance().newDocumentBuilder();
189                        Document doc = documentBuilder.parse(new ByteArrayInputStream(
190                                        ("<dummy>" + target + "</dummy>").getBytes("UTF-8")));
191                        doc.getDocumentElement().normalize();
192                        NodeList widgets = doc.getElementsByTagName("window");
193
194                        MFCWidget parent = null;
195                        for (int i = 0; i < widgets.getLength(); i++) {
196                                Element currentWidget = (Element) widgets.item(i);
197                                parent = addWidget(currentWidget, parent);
198                        }
199                }
200        }
201
202        /**
203         * <p>
204         * Adds a widget extracted from a target to the memory. The widget is placed
205         * as a child/parent of other widget according to the GUI hierarchy of the
206         * application.
207         * </p>
208         * <p>
209         * In case the widget already exists, the existing widget is returned and
210         * the known targets and hashCodes of the existing widget are updated.
211         * </p>
212         *
213         * @param widgetString
214         *            string identifying the widget
215         * @param parent
216         *            parent widget; if null, it is a root widget and added to
217         *            {@link #rootWidgets}
218         * @return the created widget.
219         */
220        private MFCWidget addWidget(Element widgetElement, MFCWidget parent) {
221                MFCWidget widget = generateWidget(widgetElement);
222
223                if (parent == null) {
224                        int index = rootWidgets.indexOf(widget);
225                        if (index >= 0) {
226                                widget = rootWidgets.get(index);
227                                widget.names.add(widgetElement.getAttribute("name"));
228                                widget.hwnds.add(widgetElement.getAttribute("hwnd"));
229                        } else {
230                                rootWidgets.add(widget);
231                        }
232                } else {
233                        int index = parent.children.indexOf(widget);
234                        if (index >= 0) {
235                                widget = parent.children.get(index);
236                                widget.names.add(widgetElement.getAttribute("name"));
237                                widget.hwnds.add(widgetElement.getAttribute("hwnd"));
238                        } else {
239                                parent.children.add(widget);
240                        }
241                }
242                return widget;
243        }
244
245        /**
246         * <p>
247         * Creates a new {@link MFCWidget} from a widget XML element.
248         * </p>
249         *
250         * @param widgetElement
251         *            XML element containing information about the widget
252         * @return created {@link MFCWidget}
253         */
254        private MFCWidget generateWidget(Element widgetElement) {
255                MFCWidget widget = new MFCWidget();
256                widget.names.add(widgetElement.getAttribute("name"));
257                widget.hwnds.add(widgetElement.getAttribute("hwnd"));
258                widget.widgetClass = widgetElement.getAttribute("class");
259                widget.resourceId = widgetElement.getAttribute("resourceId");
260                widget.modality = widgetElement.getAttribute("isModal");
261                return widget;
262        }
263
264        /**
265         * <p>
266         * Tries to find the {@link MFCWidget} that the target string identifies in
267         * the known GUI hierarchy, by traversing the known widgets starting with
268         * the {@link #rootWidgets}.
269         *
270         * @param target
271         *            target string whose widget is searched for
272         * @return respective {@link MFCWidget} instance if it is found; null
273         *         otherwise
274         */
275        private MFCWidget find(String target) throws Exception {
276                MFCWidget widget = null;
277                if (target != null) {
278                        DocumentBuilder documentBuilder = DocumentBuilderFactory
279                                        .newInstance().newDocumentBuilder();
280                        Document doc = documentBuilder.parse(new ByteArrayInputStream(
281                                        ("<dummy>" + target + "</dummy>").getBytes("UTF-8")));
282                        doc.getDocumentElement().normalize();
283                        NodeList widgets = doc.getElementsByTagName("window");
284
285                        MFCWidget parent = null;
286                        for (int i = 0; i < widgets.getLength(); i++) {
287                                Element currentWidget = (Element) widgets.item(i);
288                                MFCWidget generatedWidget = generateWidget(currentWidget);
289                                if (parent == null) {
290                                        int index = rootWidgets.indexOf(generatedWidget);
291                                        if (index >= 0) {
292                                                parent = rootWidgets.get(index);
293                                        } else {
294                                                return null;
295                                        }
296                                } else {
297                                        int index = parent.children.indexOf(generatedWidget);
298                                        if (index >= 0) {
299                                                parent = parent.children.get(index);
300                                        } else {
301                                                return null;
302                                        }
303                                }
304                        }
305                        widget = parent;
306                }
307                return widget;
308        }
309
310        /**
311         * <p>
312         * Internal class used to store MFCWidget. The implementation is more like a
313         * C-style structure, than an actual class.
314         * </p>
315         *
316         * @author Steffen Herbold
317         * @version 1.0
318         */
319        private static class MFCWidget {
320
321                /**
322                 * <p>
323                 * Set of all known name strings of the widget.
324                 * </p>
325                 */
326                Set<String> names = new LinkedHashSet<String>();
327
328                /**
329                 * <p>
330                 * Set of all known hwnds of the widget.
331                 * </p>
332                 */
333                Set<String> hwnds = new LinkedHashSet<String>();
334
335                /**
336                 * <p>
337                 * Class of the widget.
338                 * </p>
339                 */
340                String widgetClass;
341
342                /**
343                 * <p>
344                 * Resource id of the widget.
345                 * </p>
346                 */
347                String resourceId;
348
349                /**
350                 * <p>
351                 * Modality of the widget.
352                 * </p>
353                 */
354                String modality;
355
356                /**
357                 * <p>
358                 * Pre-computed hash code of the widget.
359                 * </p>
360                 */
361                int hashCode = 0;
362
363                /**
364                 * <p>
365                 * List of children of the widget.
366                 * </p>
367                 */
368                List<MFCWidget> children = new ArrayList<MFCTargetComparator.MFCWidget>();
369
370                /**
371                 * <p>
372                 * Two widgets are equal, if {@link #widgetClass}, {@link #resourceId},
373                 * and {@link #modality} are equal and the intersection of either the
374                 * {@link #hwnds}, the {@link #names}, or both of them is not empty.
375                 * </p>
376                 *
377                 * @see java.lang.Object#equals(java.lang.Object)
378                 */
379                @Override
380                public boolean equals(Object obj) {
381                        if (obj instanceof MFCWidget) {
382                                MFCWidget other = (MFCWidget) obj;
383                                boolean titleEqual = CollectionUtils.containsAny(names,
384                                                other.names);
385                                boolean hashEqual = CollectionUtils.containsAny(hwnds,
386                                                other.hwnds);
387
388                                return widgetClass.equals(other.widgetClass)
389                                                && resourceId.equals(other.resourceId)
390                                                && modality.equals(other.modality)
391                                                && (titleEqual || hashEqual);
392                        }
393                        return false;
394                }
395
396                /*
397                 * (non-Javadoc)
398                 *
399                 * @see java.lang.Object#hashCode()
400                 */
401                @Override
402                public int hashCode() {
403                        if (hashCode == 0) {
404                                int multiplier = 17;
405                                hashCode = multiplier * hashCode + widgetClass.hashCode();
406                                hashCode = multiplier * hashCode + resourceId.hashCode();
407                                hashCode = multiplier * hashCode + modality.hashCode();
408                        }
409                        return hashCode;
410                }
411        }
412}
Note: See TracBrowser for help on using the repository browser.