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.windows.data.WindowTree; import de.ugoe.cs.eventbench.windows.data.WindowTreeNode; import de.ugoe.cs.eventbench.windows.data.WindowsEvent; import de.ugoe.cs.eventbench.windows.data.WindowsMessage; import de.ugoe.cs.util.console.Console; /** *
* Translates sequences of windows messages into {@link WindowsEvent}s that can * be used by the EventBench core libraries. *
* * @author Steffen Herbold * @version 1.0 */ 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) { Console.printerrln("Invalid rules file."); e.printStackTrace(); } catch (IOException e) { Console.printerrln("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* Token that is currently being generated. *
*/ private WindowsEvent 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.
*
* 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 WindowsEvent}. *
** 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 WindowsEvent generateEvent(List* Handles msg-nodes where multiple is not true, i.e., not a sequences. *
* * @param messageElement * {@link Element} representing the msg-node * @return true, if a match is found; false otherwise */ 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; } /** ** Handles msg-nodes where multiple is true, i.e., sequences. Requires * knowledge about the next msg-node to determine the end of the sequence. *
* * @param messageElement * {@link Element} representing the msg-node * @param nextMessageElement * {@link Element} representing the next msg-node; {@code null} * if the current node is the last one * @return true, if a sequence is matched; false otherwise */ 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; } /** ** Handles equals-nodes. *
* * @param currentMessage * {@link Element} representing the msg-node the equals-node * belongs to * @param messageElement * {@link Element} representing the equals-node to be evaluated * @return true, if constraint is fulfilled; false otherwise */ @SuppressWarnings("unchecked") private boolean evalEqualRestrictions(WindowsMessage currentMessage, Element messageElement) { boolean isMatch = true; for (Element childElement : (List* Handles store-nodes and storeSeq-nodes. *
* * @param messageElement * {@link Element} representing the msg-node that is currently * being evaluated * @param currentMessage * current message in the message sequence that is matched; this * is the message that is stored */ @SuppressWarnings("unchecked") private void handleStorage(Element messageElement, WindowsMessage currentMessage) { for (Element childElement : (List* Resolves a parameter that contains a HWND of a message to the target * string of the HWND and stores it. *
* * @param currentMessage * message whose HWND is resolved * @param childElement * child element of the store node that represents the resolve */ @SuppressWarnings("unchecked") private void resolveHwnd(WindowsMessage currentMessage, Element childElement) { List* Handles genMsg-nodes and adds the replay to the {@link Event} that is * generated. *
* * @param genMsgElement * {@link Element} representing the genMsg-node */ @SuppressWarnings("unchecked") private void generateReplayMessage(Element genMsgElement) { List* Creates the targets for replay sequences generated with genMsgSeq-nodes. *
* * @param generatedMessageSeq * list of the messages that is being generated * @param msgsGenerated * boolean stating if the list of messages is already generated * or if the generation has to be handles by this method * @param constMsgType * a constant message type that is used for message generation, * in case the list of message is generated by this method * @param termElement * {@link Element} representing the term-node describing the * target * @return true, if the list of message is generated after calling this * method; false otherwise * @throws NoSuchElementException * thrown if the seqVar referred to in the termElement contains * a different number of messages than is contained in * messageSeq */ private boolean createSequenceTarget( List* Creates the LPARAMs for replay sequences generated with genMsgSeq-nodes. *
* * @param generatedMessageSeq * list of the messages that is being generated * @param msgsGenerated * boolean stating if the list of messages is already generated * or if the generation has to be handles by this method * @param constMsgType * a constant message type that is used for message generation, * in case the list of message is generated by this method * @param termElement * {@link Element} representing the term-node describing the * LPARAM * @return true, if the list of message is generated after calling this * method; false otherwise * @throws NoSuchElementException * thrown if the seqVar referred to in the termElement contains * a different number of messages than is contained in * messageSeq */ private boolean createSequenceLParam( List* Creates the WPARAMs for replay sequences generated with genMsgSeq-nodes. *
* * @param generatedMessageSeq * list of the messages that is being generated * @param msgsGenerated * boolean stating if the list of messages is already generated * or if the generation has to be handles by this method * @param constMsgType * a constant message type that is used for message generation, * in case the list of message is generated by this method * @param termElement * {@link Element} representing the term-node describing the * WPARAM * @return true, if the list of message is generated after calling this * method; false otherwise * @throws NoSuchElementException * thrown if the seqVar referred to in the termElement contains * a different number of messages than is contained in * messageSeq */ private boolean createSequenceWParam( List* If a message sequence is already generated, i.e., msgsGenerated is true, * the seqIterator is used to iterate through these messages and return the * current one. If the message sequence is not yet generated, i.e., * msgsGenerated is false, the message sequence is generated on the fly * during each call of this message and the newly generated messages are * returned. *
* * @param generatedMessageSeq * message sequence * @param msgsGenerated * indicates if generatedMessageSeq is already generated or has * to be generated on the fly by this method * @param constMsgType * type of the message to be used for message generation * @param seqIterator * iterates through an already generated message sequence; must * not be {@code null}, if msgsGenerated is true * @return current message */ private WindowsMessage getCurrentSeqMsg( List* Retrieves a message from the storage for, e.g., comparison or replay. * "this" is used to refer to the current message. *
* * @param currentMessage * current message during the parsing; passed to handle "this" * @param obj * object identifier in the storage * @return message retrieved from the storage * @throws InvalidParameterException * thrown in case of invalid uses of "this" or if no message * with the identifier obj is found in the storage */ 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; } /** ** Retrieves a stored message sequence from the storage. *
* * @param obj * object identifier in the storage * @return message sequence retrieved from the storage * @throws InvalidParameterException * thrown if no message sequences with the identifier obj is * found in the storage */ @SuppressWarnings("unchecked") private List* Handles term-nodes and returns the value of the described term. *
* * @param currentMessage * current message during the parsing; required to resolve * references to "this" in a term * @param termElement * {@link Element} representing the term node * @return value of the term or {@code null} of the term node could not be * evaluated */ 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.traceln("Trying to adress parent of top-level window! Replay probably invalid!"); } value = target.substring(0, index); } else if (paramString.equals("parentClass")) { value = varMessage.getParentClass(); } } } 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; } /** ** Handles term-nodes contained by equalSeq nodes. *
* * @param termElement * {@link Element} representing the term-node * @return list of values of the term */ private List* Handles LOWORD and HIWORD child nodes of LPARAM and WPARAM nodes. The * returned value is the LPARAM/WPARAM value based on the LOWORD and HIWORD. *
* * @param param * {@link Element} representing the LPARAM/WPARAM node * @return value of the LPARAM/WPARAM */ 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)); } /** ** Takes to short integers and combines them into the high and low order * bits of an integer. *
* * @param loword * low word * @param hiword * high word * @return combined integer */ private static int MAKEPARAM(short loword, short hiword) { return loword | ((int) hiword) << Short.SIZE; } }