package de.ugoe.cs.eventbench.windows;
import java.io.IOException;
import java.security.InvalidParameterException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.NoSuchElementException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import de.ugoe.cs.eventbench.data.Event;
import de.ugoe.cs.eventbench.data.ReplayableEvent;
import de.ugoe.cs.eventbench.windows.data.WindowTree;
import de.ugoe.cs.eventbench.windows.data.WindowTreeNode;
import de.ugoe.cs.eventbench.windows.data.WindowsMessage;
import de.ugoe.cs.util.console.Console;
/**
*
* Translates sequences of windows messages into events that can be used by the
* Logalyzer core libraries for usage analysis.
*
*
* @author Steffen Herbold
*
*/
public class EventGenerator {
/**
*
* Helper method that fetches the document node of an XML file.
*
*
* @param filename
* name of the XML file
* @return the document node
*/
private static Document getDocument(String filename) {
SAXBuilder builder = new SAXBuilder();
Document doc = null;
try {
doc = builder.build(filename);
rulesNamespace = Namespace.getNamespace("ul:rules");
} catch (JDOMException e) {
System.err.println("Invalid rules file.");
e.printStackTrace();
} catch (IOException e) {
System.err.println("Invalid rules file.");
e.printStackTrace();
}
return doc;
}
/**
*
* Name and path of the XML files containing the rules.
*
*/
private String rulesFile;
/**
*
* Iterator used for the current sequence.
*
*/
private ListIterator sequenceIterator;
/**
*
* Token that is currently being generated.
*
*/
private ReplayableEvent currentToken;
/**
*
* Reference to the ul:rules namespace.
*
*/
private static Namespace rulesNamespace;
/**
*
* The name of the rule that is currently being evaluated.
*
*/
private String currentRuleName;
/**
*
* Internal message storage. Used to implement the
* {@literal }
and {@literal }
* tags.
*
*/
private Map messageStorage;
/**
*
* Creates a new EventGenerator. Sets "rules/rules.xml" as default file for
* the rules.
*
*/
public EventGenerator() {
rulesFile = "rules/rules.xml";
}
/**
*
* Tries to match the rules to the given sequence to generate an
* {@link Event}.
*
*
* The rules are matched the order, in which they are defined in the XML
* file. Therefore, the order of the rules in the file defines priorities,
* when multiple rules could be matched to the same sequence.
*
*
* @param sequence
* sequence of message for which an event will be generated
* @return event that matches the messages; null, if no rule can be matched
*/
@SuppressWarnings("unchecked")
public Event generateEvent(List sequence) {
Document rulesDoc = getDocument(rulesFile);
Element rulesRoot = rulesDoc.getRootElement();
List ruleElements = rulesRoot.getChildren("rule",
rulesNamespace);
boolean isMatch = false;
for (int ruleIndex = 0; ruleIndex < ruleElements.size() && !isMatch; ruleIndex++) {
Element currentRule = ruleElements.get(ruleIndex);
currentRuleName = currentRule.getAttributeValue("name");
currentToken = new ReplayableEvent(currentRuleName);
isMatch = true;
messageStorage = new HashMap();
sequenceIterator = sequence.listIterator();
List ruleChildrenMsg = currentRule.getChildren("msg",
rulesNamespace);
int i = 0;
while (isMatch && i < ruleChildrenMsg.size()) {
Element messageElement = ruleChildrenMsg.get(i);
if ("true".equals(messageElement.getAttributeValue("multiple"))) {
Element nextMessageElement = null;
if (i + 1 < ruleChildrenMsg.size()) {
nextMessageElement = ruleChildrenMsg.get(i + 1);
}
try {
isMatch = matchMultipleMessages(messageElement,
nextMessageElement);
} catch (InvalidParameterException e) {
Console.printerrln(e.getMessage());
}
} else {
try {
isMatch = matchSingleMessage(messageElement);
} catch (InvalidParameterException e) {
Console.printerrln(e.getMessage());
}
}
i++;
}
if (isMatch) {
List ruleChildren = currentRule.getChildren();
for (Element genMsgElement : ruleChildren) {
if (genMsgElement.getName().equals("genMsg")) {
try {
generateReplayMessage(genMsgElement);
} catch (InvalidParameterException e) {
Console.printerrln(e.getMessage());
currentToken.invalidateReplay();
}
} else if (genMsgElement.getName().equals("genMsgSeq")) {
try {
generateReplaySequence(genMsgElement);
currentToken.invalidateReplay();
} catch (InvalidParameterException e) {
Console.printerrln(e.getMessage());
currentToken.invalidateReplay();
}
}
}
Element idinfoElement = currentRule.getChild("idinfo",
rulesNamespace);
if (idinfoElement != null) {
// cannot be empty if document is valid
List valueElements = idinfoElement.getChildren();
currentToken.setIdInfo(getTermValue(null,
valueElements.get(0)));
}
Console.traceln(currentRule.getAttributeValue("name")
+ currentToken.getIdInfo() + " matched");
} else {
currentToken = null;
}
}
if (!isMatch) {
Console.traceln("no match found for sequence: "
+ sequence.toString());
}
return currentToken;
}
private boolean createSequenceLParam(
List generatedMessageSeq, boolean msgsGenerated,
int constMsgType, Element termElement)
throws NoSuchElementException {
Iterator seqIterator = generatedMessageSeq.iterator();
if (termElement.getName().equals("seqValue")) {
String obj = termElement.getAttributeValue("seqObj");
List seqVar = getStoredSeqVariable(obj);
if (msgsGenerated && seqVar.size() != generatedMessageSeq.size()) {
throw new InvalidParameterException(
"Failure generating replay sequence for rule "
+ currentRuleName
+ ": One or more of the sequence variables used to generate a sequence have different lenghts.");
}
for (WindowsMessage msg : seqVar) {
WindowsMessage currentSeqMsg = getCurrentSeqMsg(
generatedMessageSeq, msgsGenerated, constMsgType,
seqIterator);
String paramValueStr = msg.getParameter(termElement
.getAttributeValue("param"));
int paramValue = 0;
try {
paramValue = Integer.parseInt(paramValueStr);
currentSeqMsg.setLPARAM(paramValue);
} catch (NumberFormatException e) {
currentSeqMsg.setLPARAMasWindowDesc(paramValueStr);
}
}
if (seqIterator.hasNext()) {
// the first seq-var has a different number of elements than the
// current one
throw new NoSuchElementException();
}
msgsGenerated = true;
} else { // const value
int paramValue = Integer.parseInt(getTermValue(null, termElement));
while (seqIterator.hasNext()) {
seqIterator.next().setLPARAM(paramValue);
}
}
return msgsGenerated;
}
private boolean createSequenceTarget(
List generatedMessageSeq, boolean msgsGenerated,
int constMsgType, Element termElement)
throws NoSuchElementException {
Iterator seqIterator = generatedMessageSeq.iterator();
if (termElement.getName().equals("seqValue")) {
String obj = termElement.getAttributeValue("seqObj");
List seqVar = getStoredSeqVariable(obj);
if (msgsGenerated && seqVar.size() != generatedMessageSeq.size()) {
throw new InvalidParameterException(
"Failure generating replay sequence for rule "
+ currentRuleName
+ ": One or more of the sequence variables used to generate a sequence have different lenghts.");
}
for (WindowsMessage msg : seqVar) {
WindowsMessage currentSeqMsg = getCurrentSeqMsg(
generatedMessageSeq, msgsGenerated, constMsgType,
seqIterator);
String targetString = msg.getParameter(termElement
.getAttributeValue("param"));
currentSeqMsg.setXmlWindowDescription(targetString);
}
msgsGenerated = true;
} else { // const value
throw new AssertionError("target must be a sequence variable!");
/*
* If target would not be a variable, the message-elements could not
* yet be created and the whole sequence might be broken. If this is
* to be changed, createSequenceLParam and createSequenceWParam need
* to be addepted, too.
*/
}
return msgsGenerated;
}
private boolean createSequenceWParam(
List generatedMessageSeq, boolean msgsGenerated,
int constMsgType, Element termElement)
throws NoSuchElementException {
Iterator seqIterator = generatedMessageSeq.iterator();
if (termElement.getName().equals("seqValue")) {
String obj = termElement.getAttributeValue("seqObj");
List seqVar = getStoredSeqVariable(obj);
if (msgsGenerated && seqVar.size() != generatedMessageSeq.size()) {
throw new InvalidParameterException(
"Failure generating replay sequence for rule "
+ currentRuleName
+ ": One or more of the sequence variables used to generate a sequence have different lenghts.");
}
for (WindowsMessage msg : seqVar) {
WindowsMessage currentSeqMsg = getCurrentSeqMsg(
generatedMessageSeq, msgsGenerated, constMsgType,
seqIterator);
String paramValueStr = msg.getParameter(termElement
.getAttributeValue("param"));
int paramValue = 0;
try {
paramValue = Integer.parseInt(paramValueStr);
currentSeqMsg.setWPARAM(paramValue);
} catch (NumberFormatException e) {
currentSeqMsg.setWPARAMasWindowDesc(paramValueStr);
}
}
if (seqIterator.hasNext()) {
// the first seq-var has a different number of elements than the
// current one
throw new NoSuchElementException();
}
msgsGenerated = true;
} else { // const value
int paramValue = Integer.parseInt(getTermValue(null, termElement));
while (seqIterator.hasNext()) {
seqIterator.next().setWPARAM(paramValue);
}
}
return msgsGenerated;
}
@SuppressWarnings("unchecked")
private boolean evalEqualRestrictions(WindowsMessage currentMessage,
Element messageElement) {
boolean isMatch = true;
for (Element childElement : (List) messageElement.getChildren(
"equals", rulesNamespace)) {
List termElements = childElement.getChildren();
// the size 2 of termElements is guaranteed by the XML schema
String value1 = getTermValue(currentMessage, termElements.get(0));
String value2 = getTermValue(currentMessage, termElements.get(1));
if (value1 == null || value2 == null) {
isMatch = false;
} else {
isMatch = isMatch && value1.equals(value2);
}
}
for (Element childElement : (List) messageElement.getChildren(
"equalsSeq", rulesNamespace)) {
List termElements = childElement.getChildren();
List values1 = getTermValueSeq(currentMessage,
termElements.get(0));
List values2 = getTermValueSeq(currentMessage,
termElements.get(0));
if (values1 == null || values2 == null) {
isMatch = false;
} else {
isMatch = isMatch && values1.equals(values2);
}
}
return isMatch;
}
@SuppressWarnings("unchecked")
private void generateReplayMessage(Element genMsgElement) {
List genMsgChildren = genMsgElement.getChildren();
WindowsMessage generatedMessage = null;
if (genMsgChildren.size() == 1) { // replay stored message without
// change
String obj = genMsgChildren.get(0).getAttributeValue("obj");
generatedMessage = getStoredMessageVariable(null, obj);
} else { // generate message according to the rule
for (Element genMsgChild : genMsgChildren) {
Element termElement = (Element) genMsgChild.getChildren()
.get(0);
if (genMsgChild.getName().equals("type")) {
try {
int msgType = Integer.parseInt(getTermValue(null,
termElement));
generatedMessage = new WindowsMessage(msgType);
} catch (NumberFormatException e) {
throw new InvalidParameterException(
"Failure generating replay sequence for rule "
+ currentRuleName
+ ": Defined type is not an integer.");
}
} else if (genMsgChild.getName().equals("target")) {
String targetString = getTermValue(null, termElement);
generatedMessage.setXmlWindowDescription(targetString);
} else if (genMsgChild.getName().equals("LPARAM")) {
String paramValueStr = getTermValue(null, termElement);
long paramValue = 0;
Element loword = genMsgChild.getChild("LOWORD", rulesNamespace);
if( loword!=null ) {
paramValue = loHiWord(genMsgChild);
generatedMessage.setLPARAM(paramValue);
} else {
try {
paramValue = Integer.parseInt(paramValueStr);
generatedMessage.setLPARAM(paramValue);
} catch (NumberFormatException e) {
generatedMessage.setLPARAMasWindowDesc(paramValueStr);
}
}
} else if (genMsgChild.getName().equals("WPARAM")) {
String paramValueStr = getTermValue(null, termElement);
long paramValue = 0;
Element loword = genMsgChild.getChild("LOWORD", rulesNamespace);
if( loword!=null ) {
paramValue = loHiWord(genMsgChild);
generatedMessage.setWPARAM(paramValue);
} else {
try {
paramValue = Integer.parseInt(paramValueStr);
generatedMessage.setWPARAM(paramValue);
} catch (NumberFormatException e) {
generatedMessage.setWPARAMasWindowDesc(paramValueStr);
}
}
}
}
}
if (generatedMessage != null) {
int delay = Integer.parseInt(genMsgElement
.getAttributeValue("delay"));
generatedMessage.setDelay(delay);
} else {
currentToken.invalidateReplay();
}
currentToken.addReplayEvent(generatedMessage);
}
@SuppressWarnings("unchecked")
private void generateReplaySequence(Element genMsgElement) {
List genMsgSeqChildren = genMsgElement.getChildren();
List generatedMessageSeq = new LinkedList();
if (genMsgSeqChildren.size() == 1) {
String obj = genMsgSeqChildren.get(0).getAttributeValue("seqObj");
generatedMessageSeq = getStoredSeqVariable(obj);
} else {
boolean msgsGenerated = false;
int constMsgType = 0;
for (Element genMsgSeqChild : genMsgSeqChildren) {
Element termElement = (Element) genMsgSeqChild.getChildren()
.get(0);
if (genMsgSeqChild.getName().equals("type")) {
// note: cannot easily be extracted because of mulitple
// return values
if (termElement.getName().equals("seqValue")) {
String obj = termElement.getAttributeValue("seqObj");
List seqVar = getStoredSeqVariable(obj);
for (WindowsMessage msg : seqVar) {
generatedMessageSeq.add(new WindowsMessage(msg
.getType()));
}
msgsGenerated = true;
} else { // constValue type
constMsgType = Integer.parseInt(getTermValue(null,
termElement));
}
} else if (genMsgSeqChild.getName().equals("target")) {
msgsGenerated = createSequenceTarget(generatedMessageSeq,
msgsGenerated, constMsgType, termElement);
} else if (genMsgSeqChild.getName().equals("LPARAM")) {
msgsGenerated = createSequenceLParam(generatedMessageSeq,
msgsGenerated, constMsgType, termElement);
} else if (genMsgSeqChild.getName().equals("WPARAM")) {
msgsGenerated = createSequenceWParam(generatedMessageSeq,
msgsGenerated, constMsgType, termElement);
}
}
}
currentToken.addReplaySequence(generatedMessageSeq);
}
private WindowsMessage getCurrentSeqMsg(
List generatedMessageSeq, boolean msgsGenerated,
int constMsgType, Iterator seqIterator) {
WindowsMessage currentSeqMsg = null;
if (msgsGenerated) {
currentSeqMsg = seqIterator.next();
} else {
currentSeqMsg = new WindowsMessage(constMsgType);
generatedMessageSeq.add(currentSeqMsg);
}
return currentSeqMsg;
}
private WindowsMessage getStoredMessageVariable(
WindowsMessage currentMessage, String obj)
throws InvalidParameterException {
WindowsMessage varMessage = null;
if (obj.equals("this")) {
if (currentMessage == null) {
throw new InvalidParameterException(
"Failure obtaining term value for rule "
+ currentRuleName
+ ": \"this\" is not a valid name for generating runtime messages.");
}
varMessage = currentMessage;
} else {
Object tmp = messageStorage.get(obj);
if (tmp instanceof WindowsMessage) {
varMessage = (WindowsMessage) tmp;
} else {
throw new InvalidParameterException(
"Failure obtaining term value for rule "
+ currentRuleName + ": No message \"" + obj
+ "\" stored.");
}
}
return varMessage;
}
@SuppressWarnings("unchecked")
private List getStoredSeqVariable(String obj)
throws InvalidParameterException {
List varMsgSeq = null;
Object tmp = messageStorage.get(obj);
if (tmp instanceof List>) {
varMsgSeq = (List) tmp;
} else {
throw new InvalidParameterException(
"Failure obtaining term value for rule " + currentRuleName
+ ": No sequence \"" + obj + "\" store.");
}
return varMsgSeq;
}
private String getTermValue(WindowsMessage currentMessage,
Element termElement) {
String value = null;
WindowsMessage varMessage = null;
if (termElement.getName().equals("constValue")) {
value = termElement.getAttributeValue("value");
} else if (termElement.getName().equals("paramValue")) {
String objectName = termElement.getAttributeValue("obj");
varMessage = getStoredMessageVariable(currentMessage, objectName);
if (varMessage != null) {
String param = termElement.getAttributeValue("param");
value = varMessage.getParameter(param);
}
} else if (termElement.getName().equals("winInfoValue")) {
String objectName = termElement.getAttributeValue("obj");
varMessage = getStoredMessageVariable(currentMessage, objectName);
if (varMessage != null) {
String paramString = termElement.getAttributeValue("winParam");
if (paramString.equals("class")) {
value = varMessage.getWindowClass();
} else if (paramString.equals("resourceId")) {
value = "" + varMessage.getWindowResourceId();
} else if (paramString.equals("hwnd")) {
value = "" + varMessage.getHwnd();
} else if (paramString.equals("parentTarget")) {
String target = varMessage.getXmlWindowDescription();
int index = target.lastIndexOf("<");
if( index==0 ) {
Console.println("Trying to adress parent of top-level window! Replay probably invalid!");
}
value = target.substring(0, index);
}
}
} else if (termElement.getName().equals("msgInfoValue")) {
String objectName = termElement.getAttributeValue("obj");
varMessage = getStoredMessageVariable(currentMessage, objectName);
if (varMessage != null) {
String paramString = termElement.getAttributeValue("msgParam");
if (paramString.equals("type")) {
value = "" + varMessage.getType();
} else if (paramString.equals("target")) {
value = varMessage.getXmlWindowDescription();
}
}
}
return value;
}
private List getTermValueSeq(WindowsMessage currentMessage,
Element termElement) {
List values = new LinkedList();
if (termElement.getName().equals("seqValue")) {
String obj = termElement.getAttributeValue("seqObj");
String param = termElement.getAttributeValue("param");
List seqVar = getStoredSeqVariable(obj);
for (WindowsMessage msg : seqVar) {
// msg.getParameter returns null, if parameter is not found,
// therefore the List can contain null-values
values.add(msg.getParameter(param));
}
}
return values;
}
@SuppressWarnings("unchecked")
private void handleStorage(Element messageElement,
WindowsMessage currentMessage) {
for (Element childElement : (List) messageElement.getChildren(
"store", rulesNamespace)) {
String identifier = childElement.getAttributeValue("var");
messageStorage.put(identifier, currentMessage);
resolveHwnd(currentMessage, childElement);
}
for (Element childElement : (List) messageElement.getChildren(
"storeSeq", rulesNamespace)) {
String identifier = childElement.getAttributeValue("varSeq");
Object tmp = messageStorage.get(identifier);
List storedSequence;
if (tmp == null || tmp instanceof WindowsMessage) {
storedSequence = new LinkedList();
storedSequence.add(currentMessage);
messageStorage.put(identifier, storedSequence);
} else if (tmp instanceof List>) {
storedSequence = (List) tmp;
storedSequence.add(currentMessage);
messageStorage.put(identifier, storedSequence);
}
resolveHwnd(currentMessage, childElement);
}
}
private boolean matchMultipleMessages(Element messageElement,
Element nextMessageElement) {
boolean isMatch = false;
boolean isCurrentMatch = false;
boolean nextMatchFound = false;
WindowsMessage currentMessage = null;
WindowsMessage nextMessage = null;
int type = Integer.parseInt(messageElement.getAttributeValue("type"));
int nextType = -1;
if (nextMessageElement != null) {
nextType = Integer.parseInt(nextMessageElement
.getAttributeValue("type"));
}
while (!nextMatchFound && sequenceIterator.hasNext()) {
currentMessage = sequenceIterator.next();
if (type == currentMessage.getType()) {
isCurrentMatch = evalEqualRestrictions(currentMessage,
messageElement);
isMatch = isMatch || isCurrentMatch;
if (isCurrentMatch) {
handleStorage(messageElement, currentMessage);
currentToken.setTarget(currentMessage
.getXmlWindowDescription());
currentToken
.setTargetShort(currentMessage.getParentNames());
}
}
if (nextMessageElement != null && isMatch) {
// peek next message to check if the sequence ends and the next
// match is found
if (!sequenceIterator.hasNext()) {
return false; // sequence is over, but not all messages are
// found
}
nextMessage = sequenceIterator.next();
sequenceIterator.previous();
if (nextType == nextMessage.getType()) {
nextMatchFound = evalEqualRestrictions(nextMessage,
nextMessageElement);
}
}
}
return isMatch;
}
private boolean matchSingleMessage(Element messageElement) {
boolean isMatch = false;
WindowsMessage currentMessage = null;
int type = Integer.parseInt(messageElement.getAttributeValue("type"));
while (!isMatch && sequenceIterator.hasNext()) {
// traverses the messages from the current position forward till a
// message with the correct type is found
currentMessage = sequenceIterator.next();
if (type == currentMessage.getType()) {
// message with the correct type found
// eval child nodes for further matching/storing
isMatch = evalEqualRestrictions(currentMessage, messageElement);
// in case the message is a match, eval storage children
if (isMatch) {
handleStorage(messageElement, currentMessage);
currentToken.setTarget(currentMessage
.getXmlWindowDescription());
currentToken
.setTargetShort(currentMessage.getParentNames());
}
}
}
return isMatch;
}
@SuppressWarnings("unchecked")
private void resolveHwnd(WindowsMessage currentMessage, Element childElement) {
List resolveElements = childElement.getChildren("resolveHwnd",
rulesNamespace);
for (Element resolveElement : resolveElements) {
String param = resolveElement.getAttributeValue("param");
String storeParam = resolveElement.getAttributeValue("storeParam");
int paramHwnd = Integer
.parseInt(currentMessage.getParameter(param));
WindowTreeNode node = WindowTree.getInstance().find(paramHwnd);
if (node != null) {
currentMessage.addParameter(storeParam,
node.xmlRepresentation());
}
}
}
private long loHiWord(Element param) {
Element loword = param.getChild("LOWORD", rulesNamespace);
Element hiword = param.getChild("HIWORD", rulesNamespace);
String lowordStr = getTermValue(null, (Element) loword.getChildren().get(0));
String hiwordStr = getTermValue(null, (Element) hiword.getChildren().get(0));
return MAKEPARAM(Short.parseShort(lowordStr), Short.parseShort(hiwordStr));
}
private static int MAKEPARAM(short loword, short hiword) {
return loword| ((int) hiword) << Short.SIZE;
}
}