package de.ugoe.cs.quest.plugin.jfc;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.ugoe.cs.quest.plugin.jfc.eventcore.JFCEvent;
/**
*
* 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
*
*
* @author fabian.glaser
*
*/
public class JFCReplayIDCalculator {
final int prime = 31;
/**
* Properties that are used to identify widgets
*/
private static List ID_PROPERTIES = Arrays.asList(
"Class","Title","Icon");
private static Map ID_PROPERTIES_MAP;
static{
Map idmap = new HashMap(4);
idmap.put("Class", 1);
idmap.put("Title", 0);
idmap.put("Icon", 2);
idmap.put("Index", 3);
ID_PROPERTIES_MAP = Collections.unmodifiableMap(idmap);
}
/**
* 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 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(JFCEvent event){
String replayID = "";
long hashCode = 1;
// extract target string information
String target = event.getTarget();
String[] targetParts = target.split("\\]\\.\\[");
// we assume that first component corresponds to the notion of a
// window in GUITAR suite
// calculate window hashcode
String[] windowInfo = targetParts[0].split("','");
String title = windowInfo[0];
String fuzzyTitle = getFuzzyTitle(title);
long windowHashCode = fuzzyTitle.hashCode();
windowHashCode = (windowHashCode * 2) & 0xffffffffL;
long propagatedHashCode = windowHashCode;
for (int i = 1; i < targetParts.length; i++){
long localHashCode = getLocalHashCode(targetParts[i]);
hashCode = propagatedHashCode * prime + localHashCode;
hashCode = (hashCode * 2) & 0xffffffffL;
if (i < targetParts.length - 1){
Integer index = getIndex(targetParts[i+1]);
propagatedHashCode = prime * propagatedHashCode
+ index.hashCode();
}
}
replayID = "e" + hashCode;
return replayID;
}
/**
* Calculates the hashcode part of one component.
* @param targetPart A string that represents a compontent
* @return the local hashcode
*/
private long getLocalHashCode(String targetPart){
long hashcode = 1;
String[] widgetInfo = targetPart.split("','");
widgetInfo[0] = widgetInfo[0].substring(1);
int length = widgetInfo.length - 1;
widgetInfo[length] = widgetInfo[length].substring(0,widgetInfo[length].length()-1);
String wClass = widgetInfo[ID_PROPERTIES_MAP.get("Class")];
if (IGNORED_CLASSES.contains(wClass)) {
hashcode = (prime * hashcode + (wClass.equals("null") ? 0 : (wClass
.hashCode())));
return hashcode;
}
else{
for (String property: ID_PROPERTIES){
String value = widgetInfo[ID_PROPERTIES_MAP.get(property)];
if (!value.equals("null")){
hashcode = prime * hashcode + property.hashCode();
hashcode = prime * hashcode + value.hashCode();
}
}
}
hashcode = (hashcode * 2) & 0xffffffffL;
return hashcode;
}
/**
* 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
*/
private String getFuzzyTitle(String title){
final List 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;
}
/**
* Extracts the index from a component string
* @param targetPart String that represents the component
* @return Index of the component
*/
private Integer getIndex(String targetPart){
String[] widgetInfo = targetPart.split("','");
String index = widgetInfo[ID_PROPERTIES_MAP.get("Index")];
if (index.equals("-1"))
throw new AssertionError("Index should only be -1 for components" +
"that have no parent.");
return Integer.parseInt(index);
}
};