package de.ugoe.cs.autoquest.plugin.mfc; import java.io.IOException; import java.util.ArrayList; 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 java.util.logging.Level; 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.autoquest.eventcore.Event; import de.ugoe.cs.autoquest.eventcore.IEventType; import de.ugoe.cs.autoquest.plugin.mfc.EventGenerationRule.Term; import de.ugoe.cs.autoquest.plugin.mfc.eventcore.MFCEventTypeFactory; import de.ugoe.cs.autoquest.plugin.mfc.eventcore.ReplayWindowsMessage; import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessage; import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessageType; import de.ugoe.cs.autoquest.plugin.mfc.guimodel.MFCGUIElement; import de.ugoe.cs.autoquest.plugin.mfc.guimodel.WindowTree; import de.ugoe.cs.util.console.Console; /** *
* Translates sequences of windows messages into {@link Event}s that can be used by the * QUEST core libraries. *
* * @version 1.0 * @author Steffen Herbold, Patrick Harms */ public class EventGenerator { /** ** the list of all event generation rules available *
*/ private List* 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 Event currentEvent; /** ** Event type of the current token. Stored as a member to be able to update it during the * parsing of the idinfo tag. *
*/ private IEventType currentType; /** ** *
*/ private MFCGUIElement currentTarget; /** ** 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.
*
* reference to the window tree created during parsing *
*/ private WindowTree windowTree; /** ** Creates a new EventGenerator. Sets "data/rules.xml" as default file for the rules. *
*/ public EventGenerator(WindowTree windowTree) { rulesFile = "data/rules.xml"; this.windowTree = windowTree; } /** ** 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 */ public Event generateEvent(List* Evaluates whether the current message sequence matches a given rule. *
* * @param currentRule rule that is matched * @return true if the message sequence matches the rule; false otherwise */ private boolean evaluateMessageConditions(EventGenerationRule currentRule) { boolean isMatch = true; 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(EventGenerationRule.MessageCondition condition) { boolean isMatch = false; WindowsMessage currentMessage = null; WindowsMessageType type = condition.getMessageType(); 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 = evaluateMessageCondition(currentMessage, condition); // in case the message is a match, eval storage children if (isMatch) { handleStorage(condition, currentMessage); currentTarget = currentMessage.getTarget(); // TODO 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 matchMultipleConditions(EventGenerationRule.MessageCondition condition, EventGenerationRule.MessageCondition nextCondition) { boolean isMatch = false; boolean isCurrentMatch = false; boolean nextMatchFound = false; WindowsMessage currentMessage = null; WindowsMessage nextMessage = null; WindowsMessageType type = condition.getMessageType(); WindowsMessageType nextType = null; if (nextCondition != null) { nextType = nextCondition.getMessageType(); } while (!nextMatchFound && sequenceIterator.hasNext()) { currentMessage = sequenceIterator.next(); if (type == currentMessage.getType()) { isCurrentMatch = evaluateMessageCondition(currentMessage, condition); isMatch = isMatch || isCurrentMatch; if (isCurrentMatch) { handleStorage(condition, currentMessage); currentTarget = currentMessage.getTarget(); // TODO currentToken.setTargetShort(currentMessage.getParentNames()); } } if (nextCondition != 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 = evaluateMessageCondition(nextMessage, nextCondition); } } } 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 */ private boolean evaluateMessageCondition(WindowsMessage currentMessage, EventGenerationRule.MessageCondition condition) { boolean isMatch = true; for (int i = 0; isMatch && (i < condition.getAttributeConditions().size()); i++) { EventGenerationRule.AttributeCondition attrCond = condition.getAttributeConditions().get(i); // the size 2 of termElements is guaranteed by the XML schema Object value1 = getTermValue(currentMessage, attrCond.getLeftHandSide(), Object.class); Object value2 = getTermValue(currentMessage, attrCond.getRightHandSide(), Object.class); if (value1 == null || value2 == null) { isMatch = false; } else { isMatch = isMatch && value1.equals(value2); } } // 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(EventGenerationRule.MessageCondition messageCondition, WindowsMessage currentMessage) { for (Term valueToStore : messageCondition.getMessagesToStore()) { if (valueToStore.getMessageId() != null) { ReplayWindowsMessage replayMessage = new ReplayWindowsMessage(currentMessage); messageStorage.put(valueToStore.getMessageId(), replayMessage); resolveHwnd(replayMessage, valueToStore.getResolveHandles()); } else if (valueToStore.getSequenceId() != null) { Object tmp = messageStorage.get(valueToStore.getSequenceId()); ReplayWindowsMessage replayMessage = new ReplayWindowsMessage(currentMessage); 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 list * child element of the store node that represents the resolve */ private void resolveHwnd(ReplayWindowsMessage currentMessage, List* Handles genMsg-nodes and adds the replay to the {@link Event} that is generated. *
* * @param genMsgElement * {@link Element} representing the genMsg-node */ private void generateReplayMessage(EventGenerationRule.ReplayMessageSpec messageSpec) { ReplayWindowsMessage generatedMessage = null; if (messageSpec.getReplayObjectId() != null) { // replay stored message without change generatedMessage = getStoredMessageVariable(null, messageSpec.getReplayObjectId()); } else { // generate message according to the rule generatedMessage = new ReplayWindowsMessage (getTermValue(messageSpec.getType(), WindowsMessageType.class)); generatedMessage.setTargetXML(getTermValue(messageSpec.getTarget(), String.class)); if ((messageSpec.getLparamHiWord() != null) || (messageSpec.getLparamLoWord() != null)) { generatedMessage.setLPARAM (loHiWord(messageSpec.getLparamLoWord(), messageSpec.getLparamHiWord())); } else if (messageSpec.getLparam() != null) { try { generatedMessage.setLPARAM(getTermValue(messageSpec.getLparam(), Long.class)); } catch (IllegalArgumentException e) { generatedMessage.setLPARAMasWindowDesc (getTermValue(messageSpec.getLparam(), String.class)); } } if ((messageSpec.getWparamHiWord() != null) || (messageSpec.getWparamLoWord() != null)) { generatedMessage.setWPARAM (loHiWord(messageSpec.getWparamLoWord(), messageSpec.getWparamHiWord())); } else if (messageSpec.getWparam() != null) { try { generatedMessage.setWPARAM(getTermValue(messageSpec.getWparam(), Long.class)); } catch (IllegalArgumentException e) { generatedMessage.setWPARAMasWindowDesc (getTermValue(messageSpec.getWparam(), String.class)); } } } generatedMessage.setDelay(messageSpec.getDelay()); currentEvent.addReplayable(generatedMessage); } /** * Handles genMsgSeq-nodes and adds the replay to the {@link Event} that is generated. * * @param genMsgElement * {@link Element} representing the genMsgSeq-node. */ private void generateReplaySequence(EventGenerationRule.ReplayMessageSpec messageSpec) { 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 void 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 void 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 void createSequenceWParam(List* Resolves the parameters described by {@link Term}s. *
* * @param eventParameters terms whose parameters are resolved * @return resolved parameters */ private Map* 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 IllegalArgumentException * thrown in case of invalid uses of "this" or if no message with the identifier obj * is found in the storage */ private ReplayWindowsMessage getStoredMessageVariable(WindowsMessage currentMessage, String obj) throws IllegalArgumentException { ReplayWindowsMessage varMessage = null; if (obj.equals("this")) { if (currentMessage == null) { throw new IllegalArgumentException("Failure obtaining term value for rule " + currentRuleName + ": \"this\" is not a valid name for generating runtime messages."); } varMessage = new ReplayWindowsMessage(currentMessage); } else { Object tmp = messageStorage.get(obj); if (tmp instanceof ReplayWindowsMessage) { varMessage = (ReplayWindowsMessage) tmp; } else { throw new IllegalArgumentException("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 IllegalArgumentException * thrown if no message sequences with the identifier obj is found in the storage */ @SuppressWarnings("unchecked") private List* convenience method for {@link #getTermValue(WindowsMessage, Term)} with current message is * null. *
* * @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* 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* Convenience method for {@link #getTermValueAsList(WindowsMessage, Term)} with current * message is null. *
* * @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* 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* Resolves term values. *
* * @param value value to be resolved * @param expectedType class defining the expected type * @return resolved value */ @SuppressWarnings("unchecked") private* 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(EventGenerationRule.Term lword, EventGenerationRule.Term hword) { return MAKEPARAM(getTermValue(lword, Short.class), getTermValue(hword, Short.class)); } /** ** 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; } /** ** Parses the rules. *
* */ @SuppressWarnings("unchecked") private void parseGenerationRules() { SAXBuilder builder = new SAXBuilder(); Document doc = null; try { doc = builder.build(rulesFile); rulesNamespace = Namespace.getNamespace("ul:rules"); } catch (JDOMException e) { Console.printerrln("Invalid rules file."); Console.logException(e); return; } catch (IOException e) { Console.printerrln("Invalid rules file."); Console.logException(e); return; } Element rulesRoot = doc.getRootElement(); List