// Copyright 2012 Georg-August-Universität Göttingen, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package de.ugoe.cs.autoquest.plugin.jfc; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import de.ugoe.cs.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.IEventTarget; import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement; import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElementSpec; /** *

* This class provides the functionality to calculate the unique GUITAR replayIDs * for JFC events. This code is mainly based on guitar source code: edu.umd.cs.guitar. * model.JFCDefaultIDGeneratorSimple * *

* @author fabian.glaser * */ public class JFCReplayIDCalculator { static final int prime = 31; private JFCReplayIDValidator validator; public JFCReplayIDCalculator() { this.validator = null; } public JFCReplayIDCalculator(JFCReplayIDValidator validator){ this.validator = validator; } /** * Properties that are used to identify widgets */ private static List 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 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 of a JFCEvent needed for compatibility with guitar suite * @param guiElementPath List of {@link JFCGUIElementSpec}s that represent the path of an event target * for which the replayID should be calculated. * @return replayID */ public String calculateReplayID(List guiElementPath){ String replayID = ""; long hashCode = 1; ListIterator iterator = guiElementPath.listIterator(); JFCGUIElementSpec currentSpec = iterator.next(); String title = currentSpec.getName(); String fuzzyTitle = getFuzzyTitle(title); long topLevelHashCode = fuzzyTitle.hashCode(); topLevelHashCode = (topLevelHashCode * 2) & 0xffffffffL; long propagatedHashCode = topLevelHashCode; // added validator to check if generated GUI element ids are known if (validator != null){ if (validator.validateReplayID("w" + topLevelHashCode)){ System.out.println("ID w" + topLevelHashCode + " is valid."); } else{ System.err.println(currentSpec + " describes an unknown GUI element."); System.err.println("ID w" + topLevelHashCode + " is unknown." ); System.err.println(); } } // construct looks complicated but avoids going back and forth through path if (iterator.hasNext()) currentSpec = iterator.next(); else{ currentSpec = null; // there are no children of current GUI element, so we use topLevelHashCode as hashCode hashCode = topLevelHashCode; } // walk through GUI element path and calculate hashcode while(currentSpec != null){ long localHashCode = getLocalHashCode(currentSpec); hashCode = propagatedHashCode * prime + localHashCode; hashCode = (hashCode * 2) & 0xffffffffL; // added validator to check if generated GUI element ids are known if (validator != null){ if (validator.validateReplayID("w" + hashCode)){ System.out.println("ID w" + hashCode + " is valid."); System.out.println("==> " + currentSpec + " describes a known GUI element."); } else{ System.err.println("ID w" + hashCode + " is unknown." ); System.err.println("==> " + currentSpec + " describes an unknown GUI element."); System.err.println(); } } if (iterator.hasNext()){ currentSpec = iterator.next(); Integer index = currentSpec.getIndex(); propagatedHashCode = prime * propagatedHashCode + index.hashCode(); } else currentSpec = null; } replayID = "e" + hashCode; return replayID; } /** * Calculates the replayID of a JFCEvent needed for compatibility with guitar suite * @param event for which the ID should be calculated * @return replayID */ public String calculateReplayID(Event event){ List guiElementPath = new ArrayList(); IEventTarget target = event.getTarget(); if (!"JFC".equals(target.getPlatform())) { throw new IllegalArgumentException("Event target must be of type JFC."); } JFCGUIElement currentTarget = (JFCGUIElement) target; // extract element path while (currentTarget != null){ JFCGUIElementSpec currentSpec = (JFCGUIElementSpec) currentTarget.getSpecification(); // new specification must be inserted at the beginning of the list guiElementPath.add(0, currentSpec); currentTarget = (JFCGUIElement) currentTarget.getParent(); } // calculation is delegated to other calculateReplayID method return this.calculateReplayID(guiElementPath); } /** * Calculates the hashcode part of a GUI element. * @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 idProperties = extractIDProperties(spec); for (Map.Entry property : idProperties.entrySet()) { String value = property.getValue(); if (!value.equals("null")){ hashcode = prime * hashcode + property.getKey().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 extractIDProperties(JFCGUIElementSpec spec){ LinkedHashMap idProperties = new LinkedHashMap(); if (ID_PROPERTIES.contains("Class")){ idProperties.put("Class", spec.getType()); } if (ID_PROPERTIES.contains("Title")){ String name = spec.getName(); // spec returns extra "" that need to be removed idProperties.put("Title", name.substring(1, name.length() - 1)); } 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 top level GUI elements (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 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; } };