source: trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCTargetComparator.java @ 417

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