Index: /trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCEvent.java
===================================================================
--- /trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCEvent.java	(revision 379)
+++ /trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCEvent.java	(revision 380)
@@ -173,75 +173,50 @@
 	@Override
 	protected boolean targetEquals(String otherTarget) {
-		if( target==null || otherTarget==null ) {
-			return target==otherTarget; 
-		}
-		String[] targetParts = target.split("\\]\\.\\[");
-		String[] otherParts = otherTarget.split("\\]\\.\\[");
-		if (targetParts.length != otherParts.length) {
-			return false;
-		}
-		
-		boolean retVal;
-		if (targetParts.length == 0) {
-			retVal = compareWidgets(target, otherTarget);
-		} else {
-			retVal = true;
-			for (int i = 0; retVal && i < targetParts.length; i++) {
-				retVal &= compareWidgets(targetParts[i], otherParts[i]);
-			}
-		}
-		return retVal;
-	}
-
-	/**
-	 * <p>
-	 * Compares two widget strings of the form
-	 * {@code ['title','class','index','text','hashCode']}.
-	 * </p>
-	 * 
-	 * @param widget1
-	 * @param widget2
-	 * @return
-	 */
-	private boolean compareWidgets(String widget1, String widget2) {
-		String[] widgetInfo1 = widget1.split("','");
-		String[] widgetInfo2 = widget2.split("','");
-		// ensure size is equal
-		if (widgetInfo1.length != 5 || widgetInfo2.length != 5) {
-			return false;
-		}
-		// ensure that class [1], index [2], and text [3] are equal
-		// and title [0] or hashCode [4] are equal
-		return (widgetInfo1[1].equals(widgetInfo2[1])
-				&& widgetInfo1[2].equals(widgetInfo2[2]) && widgetInfo1[3]
-					.equals(widgetInfo2[3]))
-				&& (widgetInfo1[0].equals(widgetInfo2[0]) || widgetInfo1[4]
-						.equals(widgetInfo2[4]));
-
-	}
-	
+		return JFCTargetComparator.compare(target, otherTarget);
+	}
+
+	/**
+	 * <p>
+	 * The targetHashCode ignores the parts of the target that describe the
+	 * title and hash of a widget, to ensure that the equals/hashCode contract
+	 * is fulfilled.
+	 * </p>
+	 * 
+	 * @see de.ugoe.cs.eventbench.data.Event#targetHashCode()
+	 */
 	@Override
 	protected int targetHashCode() {
 		int hashCode = 0;
 		int multiplier = 29;
-		if( target!=null ) {
-			String[] targetParts = target.split("\\[\\.\\[");
-			if( targetParts.length==0 ) {
+		if (target != null) {
+			String[] targetParts = target.split("\\]\\.\\[");
+			if (targetParts.length == 0) {
 				hashCode = widgetHashCode(target);
 			} else {
-				for( String widgetString : targetParts ) {
-					hashCode = hashCode * multiplier + widgetHashCode(widgetString);
+				for (String widgetString : targetParts) {
+					hashCode = hashCode * multiplier
+							+ widgetHashCode(widgetString);
 				}
 			}
 		}
-		
+
 		return hashCode;
 	}
-	
+
+	/**
+	 * <p>
+	 * This method calculates the hashCode for a a widget. If is used by
+	 * {@link #targetHashCode()} to build the complete hashCode.
+	 * </p>
+	 * 
+	 * @param widget
+	 *            string describing the widget
+	 * @return hashCode of the widget
+	 */
 	private int widgetHashCode(String widget) {
 		int hashCode = 0;
 		int multiplier = 37;
 		String[] widgetInfo = widget.split("','");
-		if( widgetInfo.length==5 ) {
+		if (widgetInfo.length == 5) {
 			hashCode = hashCode * multiplier + widgetInfo[1].hashCode();
 			hashCode = hashCode * multiplier + widgetInfo[2].hashCode();
Index: /trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCTargetComparator.java
===================================================================
--- /trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCTargetComparator.java	(revision 380)
+++ /trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/jfc/data/JFCTargetComparator.java	(revision 380)
@@ -0,0 +1,335 @@
+package de.ugoe.cs.eventbench.jfc.data;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * This class implements a comparator for target string for JFC GUIs. It
+ * internally maintains a collection of all targets that have been compared, to
+ * ensure transitivity of the equals relation. This memory can always be deleted
+ * by calling {@link #reset()}.
+ * </p>
+ * 
+ * @author Steffen Herbold
+ * @version 1.0
+ */
+public class JFCTargetComparator {
+
+	/**
+	 * <p>
+	 * Compares to target strings. The strings are equal, if
+	 * <ul>
+	 * <li>the class, index, and text of all widgets are equal</li>
+	 * <li>either the title or the hashCode of all widgets are equal</li>
+	 * <li>either the title or the hashCode has been observed in one equal
+	 * instance of a widget, for all widgets.</li>
+	 * </ul>
+	 * </p>
+	 * <p>
+	 * All target strings are remembered internally, to be able to test for the
+	 * third property.
+	 * </p>
+	 * 
+	 * @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) {
+		instance.addTarget(target1);
+		instance.addTarget(target2);
+
+		JFCWidget widget1 = instance.find(target1);
+		JFCWidget widget2 = instance.find(target2);
+
+		return widget1 == widget2;
+	}
+
+	/**
+	 * <p>
+	 * Resets the internal memory of targets.
+	 * </p>
+	 */
+	public static void reset() {
+		instance = new JFCTargetComparator();
+	}
+
+	/**
+	 * <p>
+	 * Internal handle to the instance of this class (implemented as
+	 * Singleton!).
+	 * </p>
+	 */
+	private static JFCTargetComparator instance = new JFCTargetComparator();
+
+	/**
+	 * <p>
+	 * Private Constructor. Creates a new instance of the class and prevents
+	 * instantiation from outside of this class.
+	 * </p>
+	 */
+	private JFCTargetComparator() {
+	}
+
+	/**
+	 * <p>
+	 * List of the root widgets found in the target string.
+	 * </p>
+	 */
+	private List<JFCWidget> rootWidgets = new ArrayList<JFCTargetComparator.JFCWidget>();
+
+	/**
+	 * <p>
+	 * Adds a target to the memory.
+	 * </p>
+	 * 
+	 * @param target
+	 *            target to be added
+	 */
+	private void addTarget(String target) {
+		if (target != null) {
+			String[] targetParts = target.split("\\]\\.\\[");
+			if (targetParts.length == 1) {
+				addWidget(target.substring(1, target.length() - 1), null);
+			} else {
+				JFCWidget parent = null;
+				for (int i = 0; i < targetParts.length; i++) {
+					if (i == 0) {
+						parent = addWidget(targetParts[i].substring(1), parent);
+					} else if (i == targetParts.length - 1) {
+						parent = addWidget(
+								targetParts[i].substring(0,
+										targetParts[i].length() - 1), parent);
+					} else {
+						parent = addWidget(targetParts[i], parent);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * <p>
+	 * 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.
+	 * </p>
+	 * <p>
+	 * In case the widget already exists, the existing widget is returned and
+	 * the known targets and hashCodes of the existing widget are updated.
+	 * </p>
+	 * 
+	 * @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 JFCWidget addWidget(String widgetString, JFCWidget parent) {
+		String[] widgetInfo = widgetString.split("','");
+		JFCWidget widget = generateWidget(widgetString);
+
+		if (parent == null) {
+			int index = rootWidgets.indexOf(widget);
+			if (index >= 0) {
+				widget = rootWidgets.get(index);
+				widget.titles.add(widgetInfo[0]);
+				widget.hashCodes.add(widgetInfo[4]);
+			} else {
+				rootWidgets.add(widget);
+			}
+		} else {
+			int index = parent.children.indexOf(widget);
+			if (index >= 0) {
+				widget = parent.children.get(index);
+				widget.titles.add(widgetInfo[0]);
+				widget.hashCodes.add(widgetInfo[4]);
+			} else {
+				parent.children.add(widget);
+			}
+		}
+
+		return widget;
+	}
+
+	/**
+	 * <p>
+	 * Creates a new {@link JFCWidget} from a widget string.
+	 * </p>
+	 * 
+	 * @param widgetString
+	 *            string describing the widget
+	 * @return created {@link JFCWidget}
+	 */
+	private JFCWidget generateWidget(String widgetString) {
+		String[] widgetInfo = widgetString.split("','");
+		JFCWidget widget = new JFCWidget();
+		widget.titles.add(widgetInfo[0]);
+		widget.widgetClass = widgetInfo[1];
+		widget.index = widgetInfo[2];
+		widget.text = widgetInfo[3];
+		widget.hashCodes.add(widgetInfo[4]);
+		return widget;
+	}
+
+	/**
+	 * <p>
+	 * Tries to find the {@link JFCWidget} 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 JFCWidget} instance if it is found; null
+	 *         otherwise
+	 */
+	private JFCWidget find(String target) {
+		JFCWidget widget = null;
+		if (target != null) {
+			String[] targetParts = target.split("\\]\\.\\[");
+			if (targetParts.length == 1) {
+				JFCWidget generatedWidget = generateWidget(target.substring(1,
+						target.length() - 1));
+				int index = rootWidgets.indexOf(generatedWidget);
+				if (index >= 0) {
+					widget = rootWidgets.get(index);
+				} else {
+					return null;
+				}
+			} else {
+				JFCWidget parent = null;
+				for (int i = 0; i < targetParts.length; i++) {
+					if (i == 0) {
+						JFCWidget generatedWidget = generateWidget(targetParts[i]
+								.substring(1));
+						int index = rootWidgets.indexOf(generatedWidget);
+						if (index >= 0) {
+							parent = rootWidgets.get(index);
+						} else {
+							return null;
+						}
+					} else if (i == targetParts.length - 1) {
+						JFCWidget generatedWidget = generateWidget(targetParts[i]
+								.substring(0, targetParts[i].length() - 1));
+						int index = parent.children.indexOf(generatedWidget);
+						if (index >= 0) {
+							widget = parent.children.get(index);
+						} else {
+							return null;
+						}
+					} else {
+						JFCWidget generatedWidget = generateWidget(targetParts[i]);
+						int index = parent.children.indexOf(generatedWidget);
+						if (index >= 0) {
+							parent = parent.children.get(index);
+						} else {
+							return null;
+						}
+					}
+				}
+			}
+		}
+		return widget;
+	}
+
+	/**
+	 * <p>
+	 * Internal class used to store JFCWidgets. The implementation is more like
+	 * a C-style structure, than a actual class.
+	 * </p>
+	 * 
+	 * @author Steffen Herbold
+	 * @version 1.0
+	 */
+	private static class JFCWidget {
+
+		/**
+		 * <p>
+		 * Set of all known title strings of the widget.
+		 * </p>
+		 */
+		Set<String> titles = new LinkedHashSet<String>();
+
+		/**
+		 * <p>
+		 * Set of all known hashCodes of the widget.
+		 * </p>
+		 */
+		Set<String> hashCodes = new LinkedHashSet<String>();
+
+		/**
+		 * <p>
+		 * Class of the widget.
+		 * </p>
+		 */
+		String widgetClass;
+
+		/**
+		 * <p>
+		 * Index of the widget.
+		 * </p>
+		 */
+		String index;
+
+		/**
+		 * <p>
+		 * Text of the widget.
+		 * </p>
+		 */
+		String text;
+
+		/**
+		 * <p>
+		 * List of children of the widget.
+		 * </p>
+		 */
+		List<JFCWidget> children = new ArrayList<JFCTargetComparator.JFCWidget>();
+
+		/**
+		 * <p>
+		 * Two widgets are equal, if {@link #widgetClass}, {@link #index}, and
+		 * {@link #text} are equal and the intersection of either the
+		 * {@link #hashCodes}, the {@link #titles}, or both of them is not
+		 * empty.
+		 * </p>
+		 * 
+		 * @see java.lang.Object#equals(java.lang.Object)
+		 */
+		@Override
+		public boolean equals(Object obj) {
+			if (obj instanceof JFCWidget) {
+				JFCWidget other = (JFCWidget) obj;
+				Set<String> titlesCopy = new LinkedHashSet<String>(titles);
+				Set<String> hashCodesCopy = new LinkedHashSet<String>(hashCodes);
+
+				titlesCopy.retainAll(other.titles);
+				hashCodesCopy.retainAll(other.hashCodes);
+
+				return (widgetClass.equals(other.widgetClass)
+						&& index.equals(other.index) && text.equals(other.text) && (!titlesCopy
+						.isEmpty() || !hashCodesCopy.isEmpty()));
+			}
+			return false;
+		}
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see java.lang.Object#hashCode()
+		 */
+		@Override
+		public int hashCode() {
+			int multiplier = 7;
+			int hashCode = 0;
+			hashCode = multiplier * hashCode + widgetClass.hashCode();
+			hashCode = multiplier * hashCode + index.hashCode();
+			hashCode = multiplier * hashCode + text.hashCode();
+			return hashCode;
+		}
+	}
+}
