source: trunk/autoquest-plugin-mfc/src/main/java/de/ugoe/cs/autoquest/plugin/mfc/EventGenerator.java @ 927

Last change on this file since 927 was 927, checked in by sherbold, 12 years ago
  • added copyright under the Apache License, Version 2.0
File size: 44.8 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.plugin.mfc;
16
17import java.io.IOException;
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.Iterator;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.ListIterator;
24import java.util.Map;
25import java.util.NoSuchElementException;
26import java.util.logging.Level;
27
28import org.jdom.Document;
29import org.jdom.Element;
30import org.jdom.JDOMException;
31import org.jdom.Namespace;
32import org.jdom.input.SAXBuilder;
33
34import de.ugoe.cs.autoquest.eventcore.Event;
35import de.ugoe.cs.autoquest.eventcore.IEventType;
36import de.ugoe.cs.autoquest.plugin.mfc.EventGenerationRule.Term;
37import de.ugoe.cs.autoquest.plugin.mfc.eventcore.MFCEventTypeFactory;
38import de.ugoe.cs.autoquest.plugin.mfc.eventcore.ReplayWindowsMessage;
39import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessage;
40import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessageType;
41import de.ugoe.cs.autoquest.plugin.mfc.guimodel.MFCGUIElement;
42import de.ugoe.cs.autoquest.plugin.mfc.guimodel.WindowTree;
43import de.ugoe.cs.util.console.Console;
44
45/**
46 * <p>
47 * Translates sequences of windows messages into {@link Event}s that can be used by the
48 * QUEST core libraries.
49 * </p>
50 *
51 * @version 1.0
52 * @author Steffen Herbold, Patrick Harms
53 */
54public class EventGenerator {
55
56    /**
57     * <p>
58     * the list of all event generation rules available
59     * </p>
60     */
61    private List<EventGenerationRule> generationRules;
62   
63    /**
64     * <p>
65     * Name and path of the XML files containing the rules.
66     * </p>
67     */
68    private String rulesFile;
69
70    /**
71     * <p>
72     * Iterator used for the current sequence.
73     * </p>
74     */
75    private ListIterator<WindowsMessage> sequenceIterator;
76
77    /**
78     * <p>
79     * Token that is currently being generated.
80     * </p>
81     */
82    private Event currentEvent;
83
84    /**
85     * <p>
86     * Event type of the current token. Stored as a member to be able to update it during the
87     * parsing of the idinfo tag.
88     * </p>
89     */
90    private IEventType currentType;
91
92    /**
93     * <p>
94     *
95     * </p>
96     */
97    private MFCGUIElement currentTarget;
98
99    /**
100     * <p>
101     * Reference to the ul:rules namespace.
102     * </p>
103     */
104    private static Namespace rulesNamespace;
105
106    /**
107     * <p>
108     * The name of the rule that is currently being evaluated.
109     * </p>
110     */
111    private String currentRuleName;
112
113    /**
114     * <p>
115     * Internal message storage. Used to implement the <code>{@literal <store>}</code> and
116     * <code>{@literal <storeSeq>}</code> tags.
117     * </p>
118     */
119    private Map<String, Object> messageStorage;
120
121    /**
122     * <p>
123     * reference to the window tree created during parsing
124     * </p>
125     */
126    private WindowTree windowTree;
127
128    /**
129     * <p>
130     * Creates a new EventGenerator. Sets "data/rules.xml" as default file for the rules.
131     * </p>
132     */
133    public EventGenerator(WindowTree windowTree) {
134        rulesFile = "data/rules.xml";
135        this.windowTree = windowTree;
136    }
137
138    /**
139     * <p>
140     * Tries to match the rules to the given sequence to generate an {@link Event}.
141     * </p>
142     * <p>
143     * The rules are matched the order, in which they are defined in the XML file. Therefore, the
144     * order of the rules in the file defines priorities, when multiple rules could be matched to
145     * the same sequence.
146     * </p>
147     *
148     * @param sequence
149     *            sequence of message for which an event will be generated
150     * @return event that matches the messages; null, if no rule can be matched
151     */
152    public Event generateEvent(List<WindowsMessage> sequence) {
153        if (generationRules == null) {
154            parseGenerationRules();
155        }
156       
157       
158        /*System.out.println("generating event for ");
159        for (WindowsMessage msg : sequence) {
160            System.out.println("    " + msg + "  " + msg.getParameter("scrollBarHandle") + "  " + msg.getTargetXML());
161        }*/
162
163        boolean isMatch = false;
164
165        for (int ruleIndex = 0; ruleIndex < generationRules.size() && !isMatch; ruleIndex++) {
166            EventGenerationRule currentRule = generationRules.get(ruleIndex);
167            currentRuleName = currentRule.getName();
168           
169            //System.out.println("checking rule " + currentRuleName);
170           
171            messageStorage = new HashMap<String, Object>();
172            sequenceIterator = sequence.listIterator();
173           
174            isMatch = evaluateMessageConditions(currentRule);
175
176            if (isMatch) {
177                currentType = MFCEventTypeFactory.getInstance().getEventType
178                    (currentRuleName, resolveParameters(currentRule.getEventParameters()));
179               
180                currentEvent = new Event(currentType, currentTarget);
181               
182                for (EventGenerationRule.ReplayMessageSpec replayMessageSpec :
183                      currentRule.getReplayMessageSpecifications())
184                {
185                    if (replayMessageSpec.generateSingleMessage()) {
186                        try {
187                            generateReplayMessage(replayMessageSpec);
188                        }
189                        catch (IllegalArgumentException e) {
190                            Console.printerrln(e.getMessage());
191                            // TODO currentToken.invalidateReplay();
192                        }
193                    }
194                    else {
195                        try {
196                            generateReplaySequence(replayMessageSpec);
197                            // TODO currentToken.invalidateReplay();
198                        }
199                        catch (IllegalArgumentException e) {
200                            Console.printerrln(e.getMessage());
201                            // TODO currentToken.invalidateReplay();
202                        }
203                    }
204                }
205
206                Console.traceln(Level.FINE, currentEvent.getType().toString() + " matched");
207            }
208            else {
209                currentEvent = null;
210            }
211        }
212        if (!isMatch) {
213            Console.traceln(Level.WARNING, "no match found for sequence: " + sequence.toString());
214        }
215       
216       
217        /*if (currentEvent != null && currentEvent.getReplayables() != null) {
218            System.out.println("replay messages are ");
219            for (de.ugoe.cs.autoquest.eventcore.IReplayable msg : currentEvent.getReplayables()) {
220                System.out.println("    " + msg + "  " + msg.getReplay());
221            }
222        }
223
224        System.out.println();*/
225
226
227        return currentEvent;
228    }
229
230    // ////////////////////////////////////////////////////////////
231    // Helper functions for matching of events, i.e., msg-nodes //
232    // ////////////////////////////////////////////////////////////
233
234    /**
235     * <p>
236     * Evaluates whether the current message sequence matches a given rule.
237     * </p>
238     *
239     * @param currentRule rule that is matched
240     * @return true if the message sequence matches the rule; false otherwise
241     */
242    private boolean evaluateMessageConditions(EventGenerationRule currentRule) {
243        boolean isMatch = true;
244        List<EventGenerationRule.MessageCondition> messageConditions =
245            currentRule.getMessageConditions();
246
247        int i = 0;
248        while (isMatch && i < messageConditions.size()) {
249            EventGenerationRule.MessageCondition messageCondition = messageConditions.get(i);
250            if (messageCondition.matchMultiple()) {
251                EventGenerationRule.MessageCondition nextMessageCondition = null;
252                if (i + 1 < messageConditions.size()) {
253                    nextMessageCondition = messageConditions.get(i + 1);
254                }
255                try {
256                    isMatch = matchMultipleConditions(messageCondition, nextMessageCondition);
257                }
258                catch (IllegalArgumentException e) {
259                    Console.printerrln(e.getMessage());
260                }
261            }
262            else {
263                try {
264                    isMatch = matchSingleMessage(messageCondition);
265                }
266                catch (IllegalArgumentException e) {
267                    Console.printerrln(e.getMessage());
268                }
269            }
270            i++;
271        }
272       
273        return isMatch;
274    }
275
276    /**
277     * <p>
278     * Handles msg-nodes where multiple is not true, i.e., not a sequences.
279     * </p>
280     *
281     * @param messageElement
282     *            {@link Element} representing the msg-node
283     * @return true, if a match is found; false otherwise
284     */
285    private boolean matchSingleMessage(EventGenerationRule.MessageCondition condition) {
286        boolean isMatch = false;
287        WindowsMessage currentMessage = null;
288
289        WindowsMessageType type = condition.getMessageType();
290
291        while (!isMatch && sequenceIterator.hasNext()) {
292            /*
293             * traverses the messages from the current position forward till a message with the
294             * correct type is found
295             */
296            currentMessage = sequenceIterator.next();
297            if (type == currentMessage.getType()) {
298                // message with the correct type found
299                // eval child nodes for further matching/storing
300                isMatch = evaluateMessageCondition(currentMessage, condition);
301
302                // in case the message is a match, eval storage children
303                if (isMatch) {
304                    handleStorage(condition, currentMessage);
305                    currentTarget = currentMessage.getTarget();
306                    // TODO currentToken.setTargetShort(currentMessage.getParentNames());
307                }
308            }
309        }
310
311        return isMatch;
312    }
313
314    /**
315     * <p>
316     * Handles msg-nodes where multiple is true, i.e., sequences. Requires knowledge about the next
317     * msg-node to determine the end of the sequence.
318     * </p>
319     *
320     * @param messageElement
321     *            {@link Element} representing the msg-node
322     * @param nextMessageElement
323     *            {@link Element} representing the next msg-node; {@code null} if the current node
324     *            is the last one
325     * @return true, if a sequence is matched; false otherwise
326     */
327    private boolean matchMultipleConditions(EventGenerationRule.MessageCondition condition,
328                                            EventGenerationRule.MessageCondition nextCondition)
329    {
330        boolean isMatch = false;
331        boolean isCurrentMatch = false;
332        boolean nextMatchFound = false;
333        WindowsMessage currentMessage = null;
334        WindowsMessage nextMessage = null;
335
336        WindowsMessageType type = condition.getMessageType();
337
338        WindowsMessageType nextType = null;
339        if (nextCondition != null) {
340            nextType = nextCondition.getMessageType();
341        }
342
343        while (!nextMatchFound && sequenceIterator.hasNext()) {
344            currentMessage = sequenceIterator.next();
345            if (type == currentMessage.getType()) {
346                isCurrentMatch = evaluateMessageCondition(currentMessage, condition);
347                isMatch = isMatch || isCurrentMatch;
348
349                if (isCurrentMatch) {
350                    handleStorage(condition, currentMessage);
351                    currentTarget = currentMessage.getTarget();
352                    // TODO currentToken.setTargetShort(currentMessage.getParentNames());
353                }
354            }
355            if (nextCondition != null && isMatch) {
356                // peek next message to check if the sequence ends and the next
357                // match is found
358                if (!sequenceIterator.hasNext()) {
359                    return false; // sequence is over, but not all messages are
360                                  // found
361                }
362                nextMessage = sequenceIterator.next();
363                sequenceIterator.previous();
364
365                if (nextType == nextMessage.getType()) {
366                    nextMatchFound = evaluateMessageCondition(nextMessage, nextCondition);
367                }
368
369            }
370        }
371
372        return isMatch;
373    }
374
375    /**
376     * <p>
377     * Handles equals-nodes.
378     * </p>
379     *
380     * @param currentMessage
381     *            {@link Element} representing the msg-node the equals-node belongs to
382     * @param messageElement
383     *            {@link Element} representing the equals-node to be evaluated
384     * @return true, if constraint is fulfilled; false otherwise
385     */
386    private boolean evaluateMessageCondition(WindowsMessage                       currentMessage,
387                                             EventGenerationRule.MessageCondition condition)
388    {
389        boolean isMatch = true;
390        for (int i = 0; isMatch && (i < condition.getAttributeConditions().size()); i++)
391        {
392            EventGenerationRule.AttributeCondition attrCond =
393                condition.getAttributeConditions().get(i);
394           
395            // the size 2 of termElements is guaranteed by the XML schema
396            Object value1 = getTermValue(currentMessage, attrCond.getLeftHandSide(), Object.class);
397            Object value2 = getTermValue(currentMessage, attrCond.getRightHandSide(), Object.class);
398            if (value1 == null || value2 == null) {
399                isMatch = false;
400            }
401            else {
402                isMatch = isMatch && value1.equals(value2);
403            }
404        }
405//        for (Element childElement : (List<Element>) messageElement.getChildren("equalsSeq",
406//                                                                               rulesNamespace))
407//        {
408//            List<Element> termElements = childElement.getChildren();
409//            List<String> values1 = getTermValueSeq(termElements.get(0));
410//            List<String> values2 = getTermValueSeq(termElements.get(0));
411//            if (values1 == null || values2 == null) {
412//                isMatch = false;
413//            }
414//            else {
415//                isMatch = isMatch && values1.equals(values2);
416//            }
417//        }
418        return isMatch;
419    }
420
421    /**
422     * <p>
423     * Handles store-nodes and storeSeq-nodes.
424     * </p>
425     *
426     * @param messageElement
427     *            {@link Element} representing the msg-node that is currently being evaluated
428     * @param currentMessage
429     *            current message in the message sequence that is matched; this is the message that
430     *            is stored
431     */
432    @SuppressWarnings("unchecked")
433    private void handleStorage(EventGenerationRule.MessageCondition messageCondition,
434                               WindowsMessage                       currentMessage)
435    {
436        for (Term valueToStore : messageCondition.getMessagesToStore())
437        {
438            if (valueToStore.getMessageId() != null) {
439              ReplayWindowsMessage replayMessage = new ReplayWindowsMessage(currentMessage);
440              messageStorage.put(valueToStore.getMessageId(), replayMessage);
441              resolveHwnd(replayMessage, valueToStore.getResolveHandles());
442            }
443            else if (valueToStore.getSequenceId() != null) {
444                Object tmp = messageStorage.get(valueToStore.getSequenceId());
445                ReplayWindowsMessage replayMessage = new ReplayWindowsMessage(currentMessage);
446                List<ReplayWindowsMessage> storedSequence;
447                if (tmp == null || tmp instanceof ReplayWindowsMessage) {
448                    storedSequence = new LinkedList<ReplayWindowsMessage>();
449                    storedSequence.add(replayMessage);
450                    messageStorage.put(valueToStore.getSequenceId(), storedSequence);
451                }
452                else if (tmp instanceof List<?>) {
453                    storedSequence = (List<ReplayWindowsMessage>) tmp;
454                    storedSequence.add(replayMessage);
455                    messageStorage.put(valueToStore.getSequenceId(), storedSequence);
456                }
457                resolveHwnd(replayMessage, valueToStore.getResolveHandles());
458            }
459        }
460    }
461
462    /**
463     * <p>
464     * Resolves a parameter that contains a HWND of a message to the target string of the HWND and
465     * stores it.
466     * </p>
467     *
468     * @param currentMessage
469     *            message whose HWND is resolved
470     * @param list
471     *            child element of the store node that represents the resolve
472     */
473    private void resolveHwnd(ReplayWindowsMessage currentMessage, List<Term> resolveHandles) {
474        if (resolveHandles != null) {
475            for (Term resolveElement : resolveHandles) {
476                String param = resolveElement.getMessageParameterName();
477                String storeParam = resolveElement.getStoreParameterName();
478                long paramHwnd = (Long) currentMessage.getParameter(param);
479                MFCGUIElement guiElement = windowTree.find(paramHwnd);
480                if (guiElement != null) {
481                    currentMessage.addParameter(storeParam, "" + guiElement.toXML());
482                }
483            }
484        }
485    }
486
487    // /////////////////////////////////////////////////////
488    // Helper functions for generating the replay, i.e.,
489    // parsing of genMsg und genMsgSeq-nodes
490    // /////////////////////////////////////////////////////
491
492    /**
493     * <p>
494     * Handles genMsg-nodes and adds the replay to the {@link Event} that is generated.
495     * </p>
496     *
497     * @param genMsgElement
498     *            {@link Element} representing the genMsg-node
499     */
500    private void generateReplayMessage(EventGenerationRule.ReplayMessageSpec messageSpec) {
501        ReplayWindowsMessage generatedMessage = null;
502        if (messageSpec.getReplayObjectId() != null) { // replay stored message without change
503            generatedMessage = getStoredMessageVariable(null, messageSpec.getReplayObjectId());
504        }
505        else { // generate message according to the rule
506            generatedMessage = new ReplayWindowsMessage
507                (getTermValue(messageSpec.getType(), WindowsMessageType.class));
508            generatedMessage.setTargetXML(getTermValue(messageSpec.getTarget(), String.class));
509
510            if ((messageSpec.getLparamHiWord() != null) ||
511                (messageSpec.getLparamLoWord() != null))
512            {
513                generatedMessage.setLPARAM
514                    (loHiWord(messageSpec.getLparamLoWord(), messageSpec.getLparamHiWord()));
515            }
516            else if (messageSpec.getLparam() != null) {
517                try {
518                    generatedMessage.setLPARAM(getTermValue(messageSpec.getLparam(), Long.class));
519                }
520                catch (IllegalArgumentException e) {
521                    generatedMessage.setLPARAMasWindowDesc
522                        (getTermValue(messageSpec.getLparam(), String.class));
523                }
524            }
525
526            if ((messageSpec.getWparamHiWord() != null) ||
527                (messageSpec.getWparamLoWord() != null))
528            {
529                generatedMessage.setWPARAM
530                    (loHiWord(messageSpec.getWparamLoWord(), messageSpec.getWparamHiWord()));
531            }
532            else if (messageSpec.getWparam() != null) {
533                try {
534                    generatedMessage.setWPARAM(getTermValue(messageSpec.getWparam(), Long.class));
535                }
536                catch (IllegalArgumentException e) {
537                    generatedMessage.setWPARAMasWindowDesc
538                        (getTermValue(messageSpec.getWparam(), String.class));
539                }
540            }
541           
542           
543        }
544
545        generatedMessage.setDelay(messageSpec.getDelay());
546
547        currentEvent.addReplayable(generatedMessage);
548    }
549
550    /**
551     * Handles genMsgSeq-nodes and adds the replay to the {@link Event} that is generated.</p>
552     *
553     * @param genMsgElement
554     *            {@link Element} representing the genMsgSeq-node.
555     */
556    private void generateReplaySequence(EventGenerationRule.ReplayMessageSpec messageSpec)
557    {
558        List<ReplayWindowsMessage> generatedMessageSeq = new LinkedList<ReplayWindowsMessage>();
559        if (messageSpec.getReplayObjectId() != null) { // replay stored sequence without changes
560            generatedMessageSeq = getStoredSeqVariable(messageSpec.getReplayObjectId());
561        }
562        else {
563            List<ReplayWindowsMessage> seqVar =
564                getStoredSeqVariable(messageSpec.getReplayObjectId());
565           
566            Term typeTerm = messageSpec.getType();
567            if (typeTerm.getSequenceId() != null) {
568                seqVar = getStoredSeqVariable(typeTerm.getSequenceId());
569                for (WindowsMessage msg : seqVar) {
570                    ReplayWindowsMessage replayMessage = new ReplayWindowsMessage(msg.getType());
571                    replayMessage.setDelay(messageSpec.getDelay());
572                    generatedMessageSeq.add(replayMessage);
573                }
574            }
575            else { // constValue type
576                WindowsMessageType constMsgType = getTermValue(typeTerm, WindowsMessageType.class);
577                for (int i = 0; i < seqVar.size(); i++) {
578                    ReplayWindowsMessage replayMessage = new ReplayWindowsMessage(constMsgType);
579                    replayMessage.setDelay(messageSpec.getDelay());
580                    generatedMessageSeq.add(replayMessage);
581                }
582            }
583
584            createSequenceTarget(generatedMessageSeq, messageSpec);
585            createSequenceLParam(generatedMessageSeq, messageSpec);
586            createSequenceWParam(generatedMessageSeq, messageSpec);
587        }
588       
589        currentEvent.addReplayableSequence(generatedMessageSeq);
590    }
591
592    /**
593     * <p>
594     * Creates the targets for replay sequences generated with genMsgSeq-nodes.
595     * </p>
596     *
597     * @param generatedMessageSeq
598     *            list of the messages that is being generated
599     * @param msgsGenerated
600     *            boolean stating if the list of messages is already generated or if the generation
601     *            has to be handles by this method
602     * @param constMsgType
603     *            a constant message type that is used for message generation, in case the list of
604     *            message is generated by this method
605     * @param termElement
606     *            {@link Element} representing the term-node describing the target
607     * @return true, if the list of message is generated after calling this method; false otherwise
608     * @throws NoSuchElementException
609     *             thrown if the seqVar referred to in the termElement contains a different number
610     *             of messages than is contained in messageSeq
611     */
612    private void createSequenceTarget(List<ReplayWindowsMessage>            generatedMessageSeq,
613                                      EventGenerationRule.ReplayMessageSpec messageSpec)
614        throws NoSuchElementException
615    {
616        Iterator<ReplayWindowsMessage> seqIterator = generatedMessageSeq.iterator();
617        if (messageSpec.getTarget().getSequenceId() != null) {
618            List<ReplayWindowsMessage> seqVar =
619                getStoredSeqVariable(messageSpec.getTarget().getSequenceId());
620
621            if (seqVar.size() != generatedMessageSeq.size()) {
622                throw new IllegalArgumentException
623                    ("Failure generating replay sequence for rule " + currentRuleName +
624                     ": One or more of the sequence variables used to generate a sequence have " +
625                     "different lenghts.");
626            }
627           
628            for (WindowsMessage msg : seqVar) {
629                seqIterator.next().setTarget(msg.getTarget());
630            }
631        }
632        else { // const value
633            throw new AssertionError("target must be a sequence variable!");
634            /*
635             * If target would not be a variable, the message-elements could not yet be created and
636             * the whole sequence might be broken. If this is to be changed, createSequenceLParam
637             * and createSequenceWParam need to be addepted, too.
638             */
639        }
640    }
641
642    /**
643     * <p>
644     * Creates the LPARAMs for replay sequences generated with genMsgSeq-nodes.
645     * </p>
646     *
647     * @param generatedMessageSeq
648     *            list of the messages that is being generated
649     * @param msgsGenerated
650     *            boolean stating if the list of messages is already generated or if the generation
651     *            has to be handles by this method
652     * @param constMsgType
653     *            a constant message type that is used for message generation, in case the list of
654     *            message is generated by this method
655     * @param termElement
656     *            {@link Element} representing the term-node describing the LPARAM
657     * @return true, if the list of message is generated after calling this method; false otherwise
658     * @throws NoSuchElementException
659     *             thrown if the seqVar referred to in the termElement contains a different number
660     *             of messages than is contained in messageSeq
661     */
662    private void createSequenceLParam(List<ReplayWindowsMessage>            generatedMessageSeq,
663                                      EventGenerationRule.ReplayMessageSpec messageSpec)
664        throws NoSuchElementException
665    {
666        Iterator<ReplayWindowsMessage> seqIterator = generatedMessageSeq.iterator();
667        if (messageSpec.getLparam().getSequenceId() != null) {
668            List<ReplayWindowsMessage> seqVar =
669                getStoredSeqVariable(messageSpec.getLparam().getSequenceId());
670           
671            if (seqVar.size() != generatedMessageSeq.size()) {
672                throw new IllegalArgumentException
673                    ("Failure generating replay sequence for rule " + currentRuleName +
674                     ": One or more of the sequence variables used to generate a sequence have " +
675                     "different lengths.");
676            }
677            for (WindowsMessage msg : seqVar) {
678                ReplayWindowsMessage currentSeqMsg = seqIterator.next();
679                Object paramValue =
680                    msg.getParameter(messageSpec.getLparam().getSequenceParameterName());
681                if (paramValue instanceof Long) {
682                    currentSeqMsg.setLPARAM((Long) paramValue);
683                }
684                else {
685                    currentSeqMsg.setLPARAMasWindowDesc((String) paramValue);
686                }
687            }
688        }
689        else { // const value
690            int paramValue = getTermValue(messageSpec.getLparam(), int.class);
691            while (seqIterator.hasNext()) {
692                seqIterator.next().setLPARAM(paramValue);
693            }
694        }
695
696    }
697
698    /**
699     * <p>
700     * Creates the WPARAMs for replay sequences generated with genMsgSeq-nodes.
701     * </p>
702     *
703     * @param generatedMessageSeq
704     *            list of the messages that is being generated
705     * @param msgsGenerated
706     *            boolean stating if the list of messages is already generated or if the generation
707     *            has to be handles by this method
708     * @param constMsgType
709     *            a constant message type that is used for message generation, in case the list of
710     *            message is generated by this method
711     * @param termElement
712     *            {@link Element} representing the term-node describing the WPARAM
713     * @return true, if the list of message is generated after calling this method; false otherwise
714     * @throws NoSuchElementException
715     *             thrown if the seqVar referred to in the termElement contains a different number
716     *             of messages than is contained in messageSeq
717     */
718    private void createSequenceWParam(List<ReplayWindowsMessage>            generatedMessageSeq,
719                                      EventGenerationRule.ReplayMessageSpec messageSpec)
720        throws NoSuchElementException
721    {
722        Iterator<ReplayWindowsMessage> seqIterator = generatedMessageSeq.iterator();
723        if (messageSpec.getWparam().getSequenceId() != null) {
724            List<ReplayWindowsMessage> seqVar =
725                getStoredSeqVariable(messageSpec.getWparam().getSequenceId());
726           
727            if (seqVar.size() != generatedMessageSeq.size()) {
728                throw new IllegalArgumentException
729                    ("Failure generating replay sequence for rule " + currentRuleName +
730                     ": One or more of the sequence variables used to generate a sequence have " +
731                     "different lengths.");
732            }
733            for (WindowsMessage msg : seqVar) {
734                ReplayWindowsMessage currentSeqMsg = seqIterator.next();
735                Object paramValue =
736                    msg.getParameter(messageSpec.getWparam().getSequenceParameterName());
737                if (paramValue instanceof Long) {
738                    currentSeqMsg.setWPARAM((Long) paramValue);
739                }
740                else {
741                    currentSeqMsg.setWPARAMasWindowDesc((String) paramValue);
742                }
743            }
744        }
745        else { // const value
746            long paramValue = getTermValue(messageSpec.getWparam(), Long.class);
747            while (seqIterator.hasNext()) {
748                seqIterator.next().setWPARAM(paramValue);
749            }
750        }
751    }
752
753    // ////////////////////////////
754    // General helper functions //
755    // ////////////////////////////
756
757    /**
758     * <p>
759     * Resolves the parameters described by {@link Term}s.
760     * </p>
761     *
762     * @param eventParameters terms whose parameters are resolved
763     * @return resolved parameters
764     */
765    private Map<String, String> resolveParameters(List<Term> eventParameters) {
766        Map<String, String> resultParameters = null;
767       
768        if ((eventParameters != null) && (eventParameters.size() > 0)) {
769            resultParameters = new HashMap<String, String>();
770           
771            for (Term term : eventParameters) {
772                if ("seqValue".equals(term.getName())) {
773                    List<String> values = getTermValueAsList(term, String.class);
774                                       
775                    resultParameters.put
776                        (term.getSequenceParameterName(), (String) values.get(values.size() - 1));
777                }
778                else {
779                    resultParameters.put
780                        (term.getMessageParameterName(), getTermValue(term, String.class));
781                }
782            }
783        }
784       
785        return resultParameters;
786    }
787
788    /**
789     * <p>
790     * Retrieves a message from the storage for, e.g., comparison or replay. "this" is used to refer
791     * to the current message.
792     * </p>
793     *
794     * @param currentMessage
795     *            current message during the parsing; passed to handle "this"
796     * @param obj
797     *            object identifier in the storage
798     * @return message retrieved from the storage
799     * @throws IllegalArgumentException
800     *             thrown in case of invalid uses of "this" or if no message with the identifier obj
801     *             is found in the storage
802     */
803    private ReplayWindowsMessage getStoredMessageVariable(WindowsMessage currentMessage, String obj)
804        throws IllegalArgumentException
805    {
806        ReplayWindowsMessage varMessage = null;
807        if (obj.equals("this")) {
808            if (currentMessage == null) {
809                throw new IllegalArgumentException("Failure obtaining term value for rule " +
810                    currentRuleName +
811                    ": \"this\" is not a valid name for generating runtime messages.");
812            }
813            varMessage = new ReplayWindowsMessage(currentMessage);
814        }
815        else {
816            Object tmp = messageStorage.get(obj);
817            if (tmp instanceof ReplayWindowsMessage) {
818                varMessage = (ReplayWindowsMessage) tmp;
819            }
820            else {
821                throw new IllegalArgumentException("Failure obtaining term value for rule " +
822                    currentRuleName + ": No message \"" + obj + "\" stored.");
823            }
824        }
825        return varMessage;
826    }
827
828    /**
829     * <p>
830     * Retrieves a stored message sequence from the storage.
831     * </p>
832     *
833     * @param obj
834     *            object identifier in the storage
835     * @return message sequence retrieved from the storage
836     * @throws IllegalArgumentException
837     *             thrown if no message sequences with the identifier obj is found in the storage
838     */
839    @SuppressWarnings("unchecked")
840    private List<ReplayWindowsMessage> getStoredSeqVariable(String obj)
841        throws IllegalArgumentException
842    {
843        List<ReplayWindowsMessage> varMsgSeq = null;
844        Object tmp = messageStorage.get(obj);
845        if (tmp instanceof List<?>) {
846            varMsgSeq = (List<ReplayWindowsMessage>) tmp;
847        }
848        else {
849            throw new IllegalArgumentException("Failure obtaining term value for rule " +
850                                               currentRuleName + ": No sequence \"" + obj +
851                                               "\" store.");
852        }
853        return varMsgSeq;
854    }
855
856    /**
857     * <p>
858     * convenience method for {@link #getTermValue(WindowsMessage, Term)} with current message is
859     * null.
860     * </p>
861     *
862     * @param termElement
863     *            {@link Element} representing the term node
864     * @return value of the term or {@code null} of the term node could not be evaluated
865     */
866    private <T> T getTermValue(EventGenerationRule.Term term, Class<T> expectedType) {
867        return getTermValue(null, term, expectedType);
868    }
869
870    /**
871     * <p>
872     * Handles term-nodes and returns the value of the described term.
873     * </p>
874     *
875     * @param currentMessage
876     *            current message during the parsing; required to resolve references to "this" in a
877     *            term
878     * @param termElement
879     *            {@link Element} representing the term node
880     * @return value of the term or {@code null} of the term node could not be evaluated
881     */
882    private <T> T getTermValue(WindowsMessage           currentMessage,
883                               EventGenerationRule.Term term,
884                               Class<T>                 expectedType)
885    {
886        T value = null;
887       
888        if ("constValue".equals(term.getName())) {
889            value = getValueAsType(term.getValue(), expectedType);
890        }
891        else if ("paramValue".equals(term.getName())) {
892            String objectName = term.getMessageId();
893            WindowsMessage varMessage = getStoredMessageVariable(currentMessage, objectName);
894            if (varMessage != null) {
895                String param = term.getMessageParameterName();
896                value = getValueAsType(varMessage.getParameter(param), expectedType);
897            }
898        }
899        else if ("winInfoValue".equals(term.getName())) {
900            String objectName = term.getMessageId();
901            WindowsMessage varMessage = getStoredMessageVariable(currentMessage, objectName);
902            if (varMessage != null) {
903                String paramString = term.getWindowParameterName();
904                if (paramString.equals("class")) {
905                    value = getValueAsType
906                        (((MFCGUIElement) varMessage.getTarget()).getType(), expectedType);
907                }
908                else if (paramString.equals("resourceId")) {
909                    value = getValueAsType
910                        (((MFCGUIElement) varMessage.getTarget()).getResourceId(), expectedType);
911                }
912                else if (paramString.equals("hwnd")) {
913                    value = getValueAsType
914                        (((MFCGUIElement) varMessage.getTarget()).getId(), expectedType);
915                }
916                else if (paramString.equals("parentTarget")) {
917                    String target = varMessage.getTargetXML();
918                    int index = target.lastIndexOf("<");
919                    if (index == 0) {
920                        Console.traceln(Level.WARNING, "Trying to adress parent of top-level " +
921                                        "window! Replay probably invalid!");
922                    }
923                    value =  getValueAsType(target.substring(0, index), expectedType);
924                }
925                else if (paramString.equals("parentClass")) {
926                    value =  getValueAsType
927                        (((MFCGUIElement) varMessage.getTarget())
928                        .getParent().getSpecification().getType(), expectedType);
929                }
930            }
931        }
932        else if ("msgInfoValue".equals(term.getName())) {
933            String objectName = term.getMessageId();
934            WindowsMessage varMessage = getStoredMessageVariable(currentMessage, objectName);
935            if (varMessage != null) {
936                String paramString = term.getMessageInfoName();
937                if (paramString.equals("type")) {
938                    value = getValueAsType(varMessage.getType(), expectedType);
939                }
940                else if (paramString.equals("target")) {
941                    value = getValueAsType(varMessage.getTargetXML(), expectedType);
942                }
943            }
944        }
945        else if ("msgInfoValue".equals(term.getName())) {
946            String objectName = term.getMessageId();
947            WindowsMessage varMessage = getStoredMessageVariable(currentMessage, objectName);
948            if (varMessage != null) {
949                String paramString = term.getMessageInfoName();
950                if (paramString.equals("type")) {
951                    value = getValueAsType(varMessage.getType(), expectedType);
952                }
953                else if (paramString.equals("target")) {
954                    value = getValueAsType(varMessage.getTargetXML(), expectedType);
955                }
956            }
957        }
958       
959        return value;
960    }
961   
962    /**
963     * <p>
964     * Convenience method for {@link #getTermValueAsList(WindowsMessage, Term)} with current
965     * message is null.
966     * </p>
967     *
968     * @param termElement
969     *            {@link Element} representing the term node
970     * @return value of the term or {@code null} of the term node could not be evaluated
971     */
972    private <T> List<T> getTermValueAsList(EventGenerationRule.Term term, Class<T> expectedType) {
973        return getTermValueAsList(null, term, expectedType);
974    }
975
976    /**
977     * <p>
978     * Handles term-nodes and returns the value of the described term.
979     * </p>
980     *
981     * @param currentMessage
982     *            current message during the parsing; required to resolve references to "this" in a
983     *            term
984     * @param termElement
985     *            {@link Element} representing the term node
986     * @return value of the term or {@code null} of the term node could not be evaluated
987     */
988    private <T> List<T> getTermValueAsList(WindowsMessage           currentMessage,
989                                           EventGenerationRule.Term term,
990                                           Class<T>                 expectedElementType)
991    {
992        List<T> values = new ArrayList<T>();
993        if ("seqValue".equals(term.getName())) {
994            List<ReplayWindowsMessage> seqVar = getStoredSeqVariable(term.getSequenceId());
995            Object value;
996           
997            for (ReplayWindowsMessage msg : seqVar) {
998                // msg.getParameter returns null, if parameter is not found,
999                // therefore the List can contain null-values
1000                value = msg.getParameter(term.getSequenceParameterName());
1001                values.add(getValueAsType(value, expectedElementType));
1002            }
1003        }
1004        else {
1005            values.add(getTermValue(currentMessage, term, expectedElementType));
1006        }
1007       
1008        return values;
1009    }
1010   
1011    /**
1012     * <p>
1013     * Resolves term values.
1014     * </p>
1015     *
1016     * @param value value to be resolved
1017     * @param expectedType class defining the expected type
1018     * @return resolved value
1019     */
1020    @SuppressWarnings("unchecked")
1021    private <T> T getValueAsType(Object value, Class<T> expectedType) {
1022        if (expectedType.isInstance(value)) {
1023            return (T) value;
1024        }
1025        else if (value instanceof String) {
1026            try {
1027                if (WindowsMessageType.class.equals(expectedType)) {
1028                    return (T) WindowsMessageType.parseMessageType((String) value);
1029                }
1030                else if (Short.class.equals(expectedType)) {
1031                    return (T) (Short) Short.parseShort((String) value);
1032                }
1033                else if (Long.class.equals(expectedType)) {
1034                    return (T) (Long) Long.parseLong((String) value);
1035                }
1036            }
1037            catch (Exception e) {
1038                // in this case, the value can not be transformed to the expected value. So ignore
1039                // the exception and fall through to the exception thrown anyway
1040            }
1041        }
1042        else if (value instanceof Long) {
1043            try {
1044                if (Short.class.equals(expectedType)) {
1045                    return (T) (Short) ((Long) value).shortValue();
1046                }
1047                else if (String.class.equals(expectedType)) {
1048                    return (T) ((Long) value).toString();
1049                }
1050            }
1051            catch (Exception e) {
1052                // in this case, the value can not be transformed to the expected value. So ignore
1053                // the exception and fall through to the exception thrown anyway
1054            }
1055        }
1056       
1057        throw new IllegalArgumentException("the term value is not of the expected type " +
1058                                           expectedType + " but a " +
1059                                           (value != null ? value.getClass() : "null"));
1060    }
1061
1062    /**
1063     * <p>
1064     * Handles LOWORD and HIWORD child nodes of LPARAM and WPARAM nodes. The returned value is the
1065     * LPARAM/WPARAM value based on the LOWORD and HIWORD.
1066     * </p>
1067     *
1068     * @param param
1069     *            {@link Element} representing the LPARAM/WPARAM node
1070     * @return value of the LPARAM/WPARAM
1071     */
1072    private long loHiWord(EventGenerationRule.Term lword, EventGenerationRule.Term hword) {
1073        return MAKEPARAM(getTermValue(lword, Short.class), getTermValue(hword, Short.class));
1074    }
1075
1076    /**
1077     * <p>
1078     * Takes to short integers and combines them into the high and low order bits of an integer.
1079     * </p>
1080     *
1081     * @param loword
1082     *            low word
1083     * @param hiword
1084     *            high word
1085     * @return combined integer
1086     */
1087    private static int MAKEPARAM(short loword, short hiword) {
1088        return loword | ((int) hiword) << Short.SIZE;
1089    }
1090
1091    /**
1092     * <p>
1093     * Parses the rules.
1094     * </p>
1095     *
1096     */
1097    @SuppressWarnings("unchecked")
1098    private void parseGenerationRules() {
1099        SAXBuilder builder = new SAXBuilder();
1100        Document doc = null;
1101
1102        try {
1103            doc = builder.build(rulesFile);
1104            rulesNamespace = Namespace.getNamespace("ul:rules");
1105        }
1106        catch (JDOMException e) {
1107            Console.printerrln("Invalid rules file.");
1108            Console.logException(e);
1109            return;
1110        }
1111        catch (IOException e) {
1112            Console.printerrln("Invalid rules file.");
1113            Console.logException(e);
1114            return;
1115        }
1116
1117        Element rulesRoot = doc.getRootElement();
1118       
1119        List<Element> ruleElements = rulesRoot.getChildren("rule", rulesNamespace);
1120
1121        generationRules = new ArrayList<EventGenerationRule>();
1122
1123        for (Element ruleElement : ruleElements) {
1124            generationRules.add(new EventGenerationRule(ruleElement, rulesNamespace));
1125        }
1126    }
1127
1128}
Note: See TracBrowser for help on using the repository browser.