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); } };