[538] | 1 | package de.ugoe.cs.quest.plugin.jfc; |
---|
| 2 | |
---|
| 3 | import java.util.Arrays; |
---|
| 4 | import java.util.Collections; |
---|
| 5 | import java.util.HashMap; |
---|
| 6 | import java.util.List; |
---|
| 7 | import java.util.Map; |
---|
[539] | 8 | import java.util.regex.Matcher; |
---|
| 9 | import java.util.regex.Pattern; |
---|
[538] | 10 | |
---|
| 11 | import de.ugoe.cs.quest.plugin.jfc.eventcore.JFCEvent; |
---|
| 12 | |
---|
| 13 | /** |
---|
| 14 | * <p> |
---|
| 15 | * This class provides the functionality to calculate the unique GUITAR replayIDs |
---|
| 16 | * for {@link JFCEvent}s. This code is mainly based on guitar source code: edu.umd.cs.guitar. |
---|
| 17 | * model.JFCDefaultIDGeneratorSimple |
---|
| 18 | * |
---|
| 19 | * </p> |
---|
| 20 | * @author fabian.glaser |
---|
| 21 | * |
---|
| 22 | */ |
---|
| 23 | |
---|
| 24 | public class JFCReplayIDCalculator { |
---|
| 25 | |
---|
| 26 | final int prime = 31; |
---|
| 27 | |
---|
| 28 | /** |
---|
| 29 | * Properties that are used to identify widgets |
---|
| 30 | */ |
---|
| 31 | private static List<String> ID_PROPERTIES = Arrays.asList( |
---|
| 32 | "Class","Title","Icon"); |
---|
| 33 | |
---|
| 34 | private static Map<String, Integer> ID_PROPERTIES_MAP; |
---|
| 35 | static{ |
---|
| 36 | Map<String, Integer> idmap = new HashMap<String, Integer>(4); |
---|
| 37 | idmap.put("Class", 1); |
---|
| 38 | idmap.put("Title", 0); |
---|
| 39 | idmap.put("Icon", 2); |
---|
| 40 | idmap.put("Index", 3); |
---|
| 41 | ID_PROPERTIES_MAP = Collections.unmodifiableMap(idmap); |
---|
| 42 | } |
---|
| 43 | |
---|
| 44 | |
---|
| 45 | /** |
---|
| 46 | * Those classes are invisible widgets but cause false-positive when |
---|
| 47 | * calculating ID (compare guitar source code: edu.umd.cs.guitar. |
---|
| 48 | * model.JFCDefaultIDGeneratorSimple) |
---|
| 49 | */ |
---|
| 50 | private static List<String> IGNORED_CLASSES = Arrays.asList("javax.swing.JPanel", |
---|
| 51 | "javax.swing.JTabbedPane", "javax.swing.JScrollPane", |
---|
| 52 | "javax.swing.JSplitPane", "javax.swing.Box", |
---|
| 53 | "javax.swing.JViewport", "javax.swing.JScrollBar", |
---|
| 54 | "javax.swing.JLayeredPane", |
---|
| 55 | "javax.swing.JList$AccessibleJList$AccessibleJListChild", |
---|
| 56 | "javax.swing.JList$AccessibleJList", "javax.swing.JList", |
---|
| 57 | "javax.swing.JScrollPane$ScrollBar", |
---|
| 58 | "javax.swing.plaf.metal.MetalScrollButton"); |
---|
| 59 | |
---|
| 60 | |
---|
| 61 | /** |
---|
| 62 | * Calculates the replayID needed for compatibility with Guitar suite of a JFCEvent |
---|
| 63 | * @param event for which the ID should be calculated |
---|
| 64 | * @return replayID |
---|
| 65 | */ |
---|
| 66 | public String calculateReplayID(JFCEvent event){ |
---|
| 67 | String replayID = ""; |
---|
| 68 | long hashCode = 1; |
---|
| 69 | |
---|
| 70 | // extract target string information |
---|
| 71 | String target = event.getTarget(); |
---|
| 72 | String[] targetParts = target.split("\\]\\.\\["); |
---|
| 73 | |
---|
| 74 | // we assume that first component corresponds to the notion of a |
---|
| 75 | // window in GUITAR suite |
---|
| 76 | |
---|
| 77 | // calculate window hashcode |
---|
| 78 | String[] windowInfo = targetParts[0].split("','"); |
---|
| 79 | String title = windowInfo[0]; |
---|
| 80 | String fuzzyTitle = getFuzzyTitle(title); |
---|
| 81 | long windowHashCode = fuzzyTitle.hashCode(); |
---|
| 82 | windowHashCode = (windowHashCode * 2) & 0xffffffffL; |
---|
| 83 | |
---|
| 84 | long propagatedHashCode = windowHashCode; |
---|
| 85 | |
---|
| 86 | for (int i = 1; i < targetParts.length; i++){ |
---|
| 87 | long localHashCode = getLocalHashCode(targetParts[i]); |
---|
| 88 | hashCode = propagatedHashCode * prime + localHashCode; |
---|
| 89 | hashCode = (hashCode * 2) & 0xffffffffL; |
---|
| 90 | |
---|
| 91 | if (i < targetParts.length - 1){ |
---|
| 92 | Integer index = getIndex(targetParts[i+1]); |
---|
| 93 | propagatedHashCode = prime * propagatedHashCode |
---|
| 94 | + index.hashCode(); |
---|
| 95 | } |
---|
| 96 | } |
---|
| 97 | |
---|
| 98 | replayID = "e" + hashCode; |
---|
| 99 | |
---|
| 100 | return replayID; |
---|
| 101 | } |
---|
| 102 | |
---|
| 103 | /** |
---|
| 104 | * Calculates the hashcode part of one component. |
---|
| 105 | * @param targetPart A string that represents a compontent |
---|
| 106 | * @return the local hashcode |
---|
| 107 | */ |
---|
| 108 | |
---|
| 109 | private long getLocalHashCode(String targetPart){ |
---|
| 110 | long hashcode = 1; |
---|
| 111 | String[] widgetInfo = targetPart.split("','"); |
---|
| 112 | widgetInfo[0] = widgetInfo[0].substring(1); |
---|
| 113 | int length = widgetInfo.length - 1; |
---|
| 114 | widgetInfo[length] = widgetInfo[length].substring(0,widgetInfo[length].length()-1); |
---|
| 115 | String wClass = widgetInfo[ID_PROPERTIES_MAP.get("Class")]; |
---|
| 116 | if (IGNORED_CLASSES.contains(wClass)) { |
---|
| 117 | hashcode = (prime * hashcode + (wClass.equals("null") ? 0 : (wClass |
---|
| 118 | .hashCode()))); |
---|
| 119 | return hashcode; |
---|
| 120 | } |
---|
| 121 | else{ |
---|
| 122 | for (String property: ID_PROPERTIES){ |
---|
| 123 | String value = widgetInfo[ID_PROPERTIES_MAP.get(property)]; |
---|
| 124 | if (!value.equals("null")){ |
---|
| 125 | hashcode = prime * hashcode + property.hashCode(); |
---|
| 126 | hashcode = prime * hashcode + value.hashCode(); |
---|
| 127 | } |
---|
| 128 | } |
---|
| 129 | } |
---|
| 130 | |
---|
| 131 | hashcode = (hashcode * 2) & 0xffffffffL; |
---|
| 132 | |
---|
| 133 | return hashcode; |
---|
| 134 | } |
---|
| 135 | |
---|
| 136 | /** |
---|
| 137 | * Guitar has a special way to deal with window titles when |
---|
| 138 | * calculating unique widget IDs. This method mimics Guitar's |
---|
[539] | 139 | * behavior (compare guitar source code: edu.umd.cs.guitar. |
---|
[538] | 140 | * model.JFCDefaultIDGeneratorSimple). |
---|
| 141 | * @param title |
---|
| 142 | * @return |
---|
| 143 | */ |
---|
[539] | 144 | |
---|
[538] | 145 | private String getFuzzyTitle(String title){ |
---|
[539] | 146 | final List<String> PATTERNS = |
---|
| 147 | Arrays.asList("Rachota .*", |
---|
| 148 | "OmegaT-.*", |
---|
| 149 | "Buddi.*", |
---|
| 150 | "Open:.*", |
---|
| 151 | "JabRef.*", |
---|
| 152 | "GanttProject.*", |
---|
| 153 | ".*Pauker.*", |
---|
| 154 | ".*FreeMind.*", |
---|
| 155 | ".* - ArgoUML.*", |
---|
| 156 | "Save Project .*"); |
---|
| 157 | |
---|
| 158 | |
---|
| 159 | for (String sPattern : PATTERNS) { |
---|
| 160 | if (matchRegex(title, sPattern)) { |
---|
| 161 | return sPattern; |
---|
| 162 | } |
---|
| 163 | } |
---|
| 164 | |
---|
| 165 | return title; |
---|
[538] | 166 | } |
---|
[539] | 167 | |
---|
| 168 | /** |
---|
| 169 | * Determine if the input string matches the input regex pattern. |
---|
| 170 | * This method mimics Guitars behavior. |
---|
| 171 | * Attempt to match the pattern 'sPattern' with the string 'sInputString'. |
---|
| 172 | |
---|
| 173 | * @param sInputString Input string to match with pattern |
---|
| 174 | * @param sPattern Regex pattern to match with string |
---|
| 175 | * @return True if match, false otherwise |
---|
| 176 | */ |
---|
| 177 | private static boolean |
---|
| 178 | matchRegex(String sInputString, |
---|
| 179 | String sPattern) |
---|
| 180 | { |
---|
| 181 | Pattern pattern; |
---|
| 182 | Matcher matcher; |
---|
| 183 | |
---|
| 184 | pattern = Pattern.compile(sPattern); |
---|
| 185 | matcher = pattern.matcher(sInputString); |
---|
| 186 | if (matcher.matches()) { |
---|
| 187 | return true; |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | return false; |
---|
| 191 | } |
---|
[538] | 192 | |
---|
[539] | 193 | /** |
---|
| 194 | * Extracts the index from a component string |
---|
| 195 | * @param targetPart String that represents the component |
---|
| 196 | * @return Index of the component |
---|
| 197 | */ |
---|
| 198 | |
---|
[538] | 199 | private Integer getIndex(String targetPart){ |
---|
| 200 | String[] widgetInfo = targetPart.split("','"); |
---|
| 201 | String index = widgetInfo[ID_PROPERTIES_MAP.get("Index")]; |
---|
| 202 | if (index.equals("-1")) |
---|
| 203 | throw new AssertionError("Index should only be -1 for components" + |
---|
[539] | 204 | "that have no parent."); |
---|
[538] | 205 | |
---|
| 206 | return Integer.parseInt(index); |
---|
| 207 | } |
---|
| 208 | }; |
---|