// 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 {@link JFCEvent}s. 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 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 (!target.getPlatform().equals("JFC")){
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;
}
};