Index: trunk/quest-plugin-jfc/src/main/java/de/ugoe/cs/quest/plugin/jfc/JFCReplayIDCalculator.java
===================================================================
--- trunk/quest-plugin-jfc/src/main/java/de/ugoe/cs/quest/plugin/jfc/JFCReplayIDCalculator.java	(revision 675)
+++ trunk/quest-plugin-jfc/src/main/java/de/ugoe/cs/quest/plugin/jfc/JFCReplayIDCalculator.java	(revision 675)
@@ -0,0 +1,217 @@
+package de.ugoe.cs.quest.plugin.jfc;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.ugoe.cs.quest.eventcore.Event;
+import de.ugoe.cs.quest.eventcore.IEventTarget;
+import de.ugoe.cs.quest.plugin.jfc.guimodel.JFCGUIElement;
+import de.ugoe.cs.quest.plugin.jfc.guimodel.JFCGUIElementSpec;
+
+
+/**
+ * <p>
+ * This class provides the functionality to calculate the unique GUITAR replayIDs
+ * for {@link JFCEvent}s. This code is mainly based on guitar source code: edu.umd.cs.guitar.
+ * model.JFCDefaultIDGeneratorSimple
+ *    
+ * </p>
+ * @author fabian.glaser
+ *
+ */
+
+public class JFCReplayIDCalculator {
+	
+	final int prime = 31;
+	
+	/**
+	 * Properties that are used to identify widgets
+	 */
+	private static List<String> ID_PROPERTIES = Arrays.asList(
+			"Class","Title","Icon");
+	
+   /**
+    * Those classes are invisible widgets but cause false-positive when
+    * calculating ID (compare guitar source code: edu.umd.cs.guitar.
+    * model.JFCDefaultIDGeneratorSimple)
+    */
+   private static List<String> IGNORED_CLASSES = Arrays.asList("javax.swing.JPanel",
+         "javax.swing.JTabbedPane", "javax.swing.JScrollPane",
+         "javax.swing.JSplitPane", "javax.swing.Box",
+         "javax.swing.JViewport", "javax.swing.JScrollBar",
+         "javax.swing.JLayeredPane",
+         "javax.swing.JList$AccessibleJList$AccessibleJListChild",
+         "javax.swing.JList$AccessibleJList", "javax.swing.JList",
+         "javax.swing.JScrollPane$ScrollBar",
+         "javax.swing.plaf.metal.MetalScrollButton");
+	
+	
+	/**
+	 * Calculates the replayID needed for compatibility with Guitar suite of a JFCEvent
+	 * @param event for which the ID should be calculated
+	 * @return replayID
+	 */
+	public String calculateReplayID(Event event){
+		String replayID = "";
+		long hashCode = 1;
+		Stack<JFCGUIElement> path = new Stack<JFCGUIElement>();
+		
+		IEventTarget target = event.getTarget();
+		JFCGUIElement jfcTarget = (JFCGUIElement) target;
+		
+		// extract target path
+		JFCGUIElement currentTarget = jfcTarget;
+		while (currentTarget != null){
+			path.push(currentTarget);
+			currentTarget = (JFCGUIElement) currentTarget.getParent();
+		}
+		
+		// calculate window hashcode
+		currentTarget = path.pop();
+		
+		JFCGUIElementSpec currentSpec = (JFCGUIElementSpec) currentTarget.getSpecification();
+		String title = currentSpec.getName();
+		String fuzzyTitle = getFuzzyTitle(title);
+		long windowHashCode = fuzzyTitle.hashCode();
+		windowHashCode = (windowHashCode * 2) & 0xffffffffL;
+		
+		long propagatedHashCode = windowHashCode;
+		
+		// walk through component path and calculate hashcode
+		
+		while(!path.isEmpty()){
+			currentTarget = path.pop(); 
+			currentSpec = (JFCGUIElementSpec) currentTarget.getSpecification();
+			long localHashCode = getLocalHashCode(currentSpec);
+			hashCode = propagatedHashCode * prime + localHashCode;
+	        hashCode = (hashCode * 2) & 0xffffffffL;
+			
+	        if (!path.isEmpty()){
+	        	Integer index = ((JFCGUIElementSpec) path.lastElement().getSpecification()).getIndex();
+				propagatedHashCode = prime * propagatedHashCode
+				+ index.hashCode();
+	        }
+		}
+		
+		replayID = "e" + hashCode;
+		
+		return replayID;
+	}
+	
+	/**
+	 * Calculates the hashcode part of a component.
+	 * @param spec The {@link JFCGUIElementSpec} for which the hashcode should be calculated.
+	 * @return the local hashcode
+	 */
+	
+	private long getLocalHashCode(JFCGUIElementSpec spec){
+		long hashcode = 1;
+		String wClass = spec.getType();
+		if (IGNORED_CLASSES.contains(wClass)) {
+		    hashcode = (prime * hashcode + (wClass.equals("null") ? 0 : (wClass
+		               .hashCode())));
+		    return hashcode;
+		}
+		else{
+			Map<String, String> idProperties = extractIDProperties(spec);
+			for (String property: idProperties.keySet()){
+				String value = idProperties.get(property);
+				if (!value.equals("null")){
+					hashcode = prime * hashcode + property.hashCode();
+					hashcode = prime * hashcode + value.hashCode();
+				}
+			}
+		}
+		
+		hashcode = (hashcode * 2) & 0xffffffffL;
+		
+		return hashcode;
+	}
+	
+	/**
+	 * Extracts the IDProperties from a given {@link JFCGUIElementSpec}.
+	 * @param spec
+	 * @return LinkedHashMap that contains the IDProperties and its values.
+	 */
+	
+	private Map<String, String> extractIDProperties(JFCGUIElementSpec spec){
+		LinkedHashMap<String, String> idProperties = new LinkedHashMap<String, String>();
+		if (ID_PROPERTIES.contains("Class")){
+			idProperties.put("Class", spec.getType());
+		}
+		if (ID_PROPERTIES.contains("Title")){
+			idProperties.put("Title", spec.getName());
+		}
+		if (ID_PROPERTIES.contains("Icon")){
+			idProperties.put("Icon", spec.getIcon());
+		}
+		if (ID_PROPERTIES.contains("Index")){
+			idProperties.put("Index", Integer.toString(spec.getIndex()));
+		}	
+		return idProperties;
+	}
+	
+	/**
+	 * Guitar has a special way to deal with window titles when
+	 * calculating unique widget IDs. This method mimics Guitar's
+	 * behavior (compare guitar source code: edu.umd.cs.guitar.
+	 * model.JFCDefaultIDGeneratorSimple).
+	 * @param title
+	 * @return fuzzyTitle
+	 */
+
+	private String getFuzzyTitle(String title){
+		final List<String> PATTERNS = 
+				Arrays.asList("Rachota .*",
+						"OmegaT-.*",
+						"Buddi.*",
+						"Open:.*",
+						"JabRef.*",
+						"GanttProject.*",
+						".*Pauker.*",
+						".*FreeMind.*",
+						".* - ArgoUML.*",
+						"Save Project .*");
+
+
+		for (String sPattern : PATTERNS) {
+			if (matchRegex(title, sPattern)) {
+				return sPattern;
+			}
+		}
+
+		return title;
+	}
+
+	/**
+	 * Determine if the input string matches the input regex pattern.
+	 * This method mimics Guitars behavior.
+	 * Attempt to match the pattern 'sPattern' with the string 'sInputString'.
+
+	 * @param sInputString    Input string to match with pattern
+	 * @param sPattern        Regex pattern to match with string
+	 * @return                True if match, false otherwise
+	 */
+	
+	private static boolean
+	matchRegex(String sInputString,
+			String sPattern)
+	{
+		Pattern pattern;
+		Matcher matcher;
+
+		pattern = Pattern.compile(sPattern);
+		matcher = pattern.matcher(sInputString);
+		if (matcher.matches()) {
+			return true;
+		}
+
+		return false;
+	}
+	
+};
