source: trunk/autoquest-plugin-jfc/src/main/java/de/ugoe/cs/autoquest/plugin/jfc/JFCSimplifiedLogParser.java @ 965

Last change on this file since 965 was 965, checked in by fglaser, 12 years ago
  • Corrected bug in JFCSimplifiedLogParser and JFCComponentTree, which were using Long instead of Integers as hashCodes
  • Property svn:mime-type set to text/plain
File size: 24.1 KB
Line 
1package de.ugoe.cs.autoquest.plugin.jfc;
2
3import java.awt.event.MouseEvent;
4import java.io.File;
5import java.io.FileInputStream;
6import java.io.FileNotFoundException;
7import java.io.IOException;
8import java.io.InputStreamReader;
9import java.io.UnsupportedEncodingException;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.HashMap;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.logging.Level;
17import java.util.regex.Matcher;
18import java.util.regex.Pattern;
19
20import javax.xml.parsers.ParserConfigurationException;
21import javax.xml.parsers.SAXParser;
22import javax.xml.parsers.SAXParserFactory;
23
24import org.xml.sax.Attributes;
25import org.xml.sax.InputSource;
26import org.xml.sax.SAXException;
27import org.xml.sax.SAXParseException;
28import org.xml.sax.helpers.DefaultHandler;
29
30import de.ugoe.cs.autoquest.eventcore.Event;
31import de.ugoe.cs.autoquest.eventcore.gui.IInteraction;
32import de.ugoe.cs.autoquest.eventcore.gui.KeyPressed;
33import de.ugoe.cs.autoquest.eventcore.gui.KeyReleased;
34import de.ugoe.cs.autoquest.eventcore.gui.KeyboardFocusChange;
35import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonDown;
36import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction;
37import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonUp;
38import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
39import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
40import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
41import de.ugoe.cs.autoquest.keyboardmaps.VirtualKey;
42import de.ugoe.cs.autoquest.plugin.jfc.eventcore.JFCEventId;
43import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCComponentTree;
44import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElement;
45import de.ugoe.cs.autoquest.plugin.jfc.guimodel.JFCGUIElementSpec;
46import de.ugoe.cs.util.console.Console;
47
48/**
49 * <p>
50 * This class provides functionality to parse XML log files generated by the JFCMonitor of
51 * autoquest. The result of parsing a file is a collection of event sequences.
52 * </p>
53 *
54 * @author Fabian Glaser
55 * @author Steffen Herbold
56 * @version 1.0
57 */
58public class JFCSimplifiedLogParser extends DefaultHandler {
59
60    /**
61     * <p>
62     * Collection of event sequences that is contained in the log file, which is parsed.
63     * </p>
64     */
65    private Collection<List<Event>> sequences;
66
67    /**
68     * <p>
69     * Internal handle to the id of the event that is currently being parsed.
70     * </p>
71     */
72    private JFCEventId currentEventId;
73   
74    /**
75     *
76     * <p>
77     * Internal handle to the hashcode of the component, that is currently parsed.
78     * </p>
79     */
80    private Integer currentComponentHash;
81   
82    /**
83     *
84     * <p>
85     * Internal handle to the hashcode of the parent of the component, that is currently parsed.
86     * </p>
87     */
88    private Integer currentParentHash;
89   
90    /**
91     * <p>
92     * Internal handle to the parameters of the event that is currently being parsed.
93     * </p>
94     */
95    private Integer currentEventSource;
96     
97    /**
98     * <p>
99     * Internal handle to the parameters of the event currently being parsed.
100     * </p>
101     */
102    private Map<String, String> currentEventParameters;
103
104    /**
105     * <p>
106     * Internal handle to the event sequence that is currently being parsed.
107     * </p>
108     */
109    private List<Event> currentSequence;
110   
111    /**
112     * <p>
113     * internal handle to the parameters currently parsed for a component
114     * </p>
115     */
116    private JFCGUIElementSpec currentGuiElementSpec;
117
118    /**
119     * <p>
120     * internal handle to the parsed gui structure, stored in a JFCComponentTree
121     * </p>
122     */
123    private JFCComponentTree currentComponentTree;
124
125    /**
126     * <p>
127     * internal handle to the component of the previous event to be potentially reused for the
128     * current
129     * </p>
130     */
131    private IGUIElement lastGUIElement;
132   
133    /**
134     * <p>
135     * internal handle to the class ancestors
136     * </p>
137     */
138    private List<String> currentAncestorList;
139
140    /**
141     * <p>
142     * this is used to check, if for every pressed key, there is a release of it
143     * </p>
144     */
145    private List<VirtualKey> mPressedKeys = new ArrayList<VirtualKey>();
146   
147    /**
148     * <p>
149     * a specification for event ids to be omitted by the parser
150     * </p>
151     */
152    private Collection<JFCEventId> eventFilter;
153
154    /**
155     * <p>
156     * Constructor. Creates a new JFCLogParser with a default event filter. This ignores focus
157     * events, mouse pressed, and mouse released events.
158     * </p>
159     */
160    public JFCSimplifiedLogParser() {
161        sequences = new LinkedList<List<Event>>();
162        currentSequence = null;
163        //setupDefaultEventFilter();
164    }
165
166    /**
167     * <p>
168     * Constructor. Creates a new JFCLogParser with a specific event filter. The events in the
169     * provided collection are ignored by the parser. As events, the constants of the different
170     * event classes must be used. E.g. creating a collection and putting
171     * <code>MouseEvent.MOUSE_PRESSED</code> will cause the parser to ignore all mouse pressed
172     * events. If the provided collection is null, no event is ignored.
173     * </p>
174     *
175     * @param ignoredEvents
176     *            the events to be ignored by the parser, can be null
177     */
178    public JFCSimplifiedLogParser(Collection<JFCEventId> ignoredEvents) {
179        sequences = new LinkedList<List<Event>>();
180        currentSequence = null;
181        eventFilter = ignoredEvents;
182    }
183
184    /**
185     * <p>
186     * Parses a log file written by the JFCMonitor and creates a collection of event sequences.
187     * </p>
188     *
189     * @param filename
190     *            name and path of the log file
191     */
192    public void parseFile(String filename) {
193        if (filename == null) {
194            throw new IllegalArgumentException("filename must not be null");
195        }
196
197        parseFile(new File(filename));
198    }
199
200    /**
201     * <p>
202     * Parses a log file written by the JFCMonitor and creates a collection of event sequences.
203     * </p>
204     *
205     * @param file
206     *            name and path of the log file
207     */
208    public void parseFile(File file) {
209        if (file == null) {
210            throw new IllegalArgumentException("file must not be null");
211        }
212
213        SAXParserFactory spf = SAXParserFactory.newInstance();
214        spf.setValidating(true);
215
216        SAXParser saxParser = null;
217        InputSource inputSource = null;
218        try {
219            saxParser = spf.newSAXParser();
220            inputSource =
221                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
222        }
223        catch (UnsupportedEncodingException e) {
224            Console.printerr("Error parsing file + " + file.getName());
225            Console.logException(e);
226            return;
227        }
228        catch (ParserConfigurationException e) {
229            Console.printerr("Error parsing file + " + file.getName());
230            Console.logException(e);
231            return;
232        }
233        catch (SAXException e) {
234            Console.printerr("Error parsing file + " + file.getName());
235            Console.logException(e);
236            return;
237        }
238        catch (FileNotFoundException e) {
239            Console.printerr("Error parsing file + " + file.getName());
240            Console.logException(e);
241            return;
242        }
243        if (inputSource != null) {
244            inputSource.setSystemId("file://" + file.getAbsolutePath());
245            try {
246                if (saxParser == null) {
247                    throw new RuntimeException("SAXParser creation failed");
248                }
249                saxParser.parse(inputSource, this);
250            }
251            catch (SAXParseException e) {
252                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
253                    ", column " + e.getColumnNumber() + ".");
254                Console.logException(e);
255                return;
256            }
257            catch (SAXException e) {
258                Console.printerr("Error parsing file + " + file.getName());
259                Console.logException(e);
260                return;
261            }
262            catch (IOException e) {
263                Console.printerr("Error parsing file + " + file.getName());
264                Console.logException(e);
265                return;
266            }
267        }
268    }
269
270    /**
271     * <p>
272     * Returns the collection of event sequences that is obtained from parsing log files.
273     * </p>
274     *
275     * @return collection of event sequences
276     */
277    public Collection<List<Event>> getSequences() {
278        return sequences;
279    }
280
281    /**
282     * <p>
283     * Returns the gui model that is obtained from parsing log files.
284     * </p>
285     *
286     * @return GUIModel
287     */
288    public GUIModel getGuiModel() {
289        return currentComponentTree.getGUIModel();
290    }
291
292    /*
293     * (non-Javadoc)
294     *
295     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
296     * java.lang.String, org.xml.sax.Attributes)
297     */
298    public void startElement(String uri, String localName, String qName, Attributes atts)
299        throws SAXException{
300        if (qName.equals("sessions")) {
301            currentSequence = new LinkedList<Event>();
302            if (currentComponentTree == null)
303                currentComponentTree = new JFCComponentTree();
304        }
305        if (qName.equals("newsession")) {
306            Console.traceln(Level.FINE, "start of session");
307            if (currentSequence != null && !currentSequence.isEmpty()) {
308                // create a copy of the list just to have a correctly typed one.
309                sequences.add(currentSequence.subList(0, currentSequence.size() - 1));
310            }
311            currentSequence = new LinkedList<Event>();
312        }
313        else if (qName.equals("component")) {
314                currentComponentHash = (int) Long.parseLong(atts.getValue("hash"), 16);
315                currentGuiElementSpec = new JFCGUIElementSpec();
316                currentGuiElementSpec.setElementHash((int) currentComponentHash.longValue());
317        }
318        else if (qName.equals("event")) {
319            JFCEventId eventId = JFCEventId.parseEventId(atts.getValue("id"));
320            if ((eventFilter == null) || (!eventFilter.contains(eventId))) {
321                currentEventId = eventId;
322                currentEventParameters = new HashMap<String, String>();
323            }
324        }
325        else if (qName.equals("componentNameChange")) {
326                int sourceHash = (int) Long.parseLong(atts.getValue("hash"), 16);
327                String newName = atts.getValue("newName");
328                int titleSource = Integer.parseInt(atts.getValue("titleSource"));
329                JFCGUIElement sourceElement = (JFCGUIElement) currentComponentTree.find(sourceHash);
330                JFCGUIElementSpec sourceSpec = (JFCGUIElementSpec) sourceElement.getSpecification();
331                sourceSpec.setName(newName);
332        }
333        else if (qName.equals("param")) {
334                if (currentEventId != null){
335                        if ("source".equals(atts.getValue("name"))){
336                                currentEventSource = (int) Long.parseLong(atts.getValue("value"), 16);
337                        }
338                currentEventParameters.put(atts.getValue("name"), atts.getValue("value"));
339            } else if(currentComponentHash != null){
340                if ("title".equals(atts.getValue("name"))) {
341                                currentGuiElementSpec.setName(atts.getValue("value"));
342                        }
343                        else if ("class".equals(atts.getValue("name"))) {
344                                currentGuiElementSpec.setType(atts.getValue("value"));
345                        }
346                        else if ("icon".equals(atts.getValue("name"))) {
347                                currentGuiElementSpec.setIcon(atts.getValue("value"));
348                        }
349                        else if ("index".equals(atts.getValue("name"))) {
350                                currentGuiElementSpec.setIndex(Integer.parseInt(atts.getValue("value")));
351                        }
352                        else if ("parent".equals(atts.getValue("name"))) {
353                                currentParentHash = (int) Long.parseLong(atts.getValue("value"), 16);
354                        }
355            }
356        }
357        else if (qName.equals("ancestor")){
358                        currentAncestorList.add(atts.getValue("name"));
359        }
360        else if (qName.equals("ancestors")){
361                        currentAncestorList = new LinkedList<String>();
362        }
363    }
364
365    /*
366     * (non-Javadoc)
367     *
368     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
369     * java.lang.String)
370     */
371    @Override
372    public void endElement(String uri, String localName, String qName) throws SAXException {
373        if (qName.equals("sessions")) {
374            if (currentSequence != null && !currentSequence.isEmpty()) {
375                sequences.add(currentSequence);
376            }
377            currentSequence = null;
378        }
379        else if (qName.equals("component") && currentComponentHash != null) {
380                currentComponentTree.add(currentComponentHash, currentParentHash, currentGuiElementSpec);
381                // do something with the ancestor list here
382               
383                currentComponentHash = null;
384                currentParentHash = null;
385                currentAncestorList = null;
386        }
387        else if (currentEventId != null) {
388            if (qName.equals("event")) {
389               
390                IGUIElement currentGUIElement;
391                currentGUIElement = currentComponentTree.find(currentEventSource);
392               
393                Event event = new Event
394                    (instantiateInteraction(currentEventId, currentEventParameters),
395                     (currentGUIElement == null ? lastGUIElement : currentGUIElement));
396               
397                currentSequence.add(event);
398               
399                currentEventParameters = null;
400                currentEventId = null;
401               
402                if (currentGUIElement != null) {
403                    lastGUIElement = currentGUIElement;
404                }
405               
406                currentGUIElement = null;
407            }
408        }
409    }
410
411    /**
412     * <p>
413     * depending on the event id and the event parameters, this method instantiates the concrete
414     * interaction, that took place, i.e. the event type
415     * </p>
416     *
417     * @param eventId
418     *            the id of the event
419     * @param eventParameters
420     *            the parameters provided for the event
421     *           
422     * @return as described
423     *
424     * @throws SAXException thrown if the provided event id is unknown
425     */
426    private IInteraction instantiateInteraction(JFCEventId          eventId,
427                                                Map<String, String> eventParameters)
428      throws SAXException
429    {
430        switch (eventId)
431        {
432            case FOCUS_GAINED:
433                return handleNewFocus(eventId, eventParameters);
434
435            case KEY_PRESSED:
436            case KEY_RELEASED:
437            case KEY_TYPED:
438                return handleKeyAction(eventId, eventParameters);
439
440            case MOUSE_CLICKED:
441            case MOUSE_PRESSED:
442            case MOUSE_RELEASED:
443            case MOUSE_MOVED:
444            case MOUSE_ENTERED:
445            case MOUSE_EXITED:
446            case MOUSE_DRAGGED:
447            case MOUSE_WHEEL:
448                return handleMouseAction(eventId, eventParameters);
449
450            default:
451                throw new SAXException("unhandled event id " + eventId);
452        }
453    }
454
455    /**
456     * <p>
457     * handles a mouse interaction. The method determines based on the event id and the parameters
458     * which mouse button is pressed, released or clicked.
459     * </p>
460     *
461     * @param eventId
462     *            the id of the event
463     * @param eventParameters
464     *            the parameters provided for the event
465     *           
466     * @return as described
467     *
468     * @throws SAXException thrown if the provided event id or button index is unknown
469     */
470    private IInteraction handleMouseAction(JFCEventId eventId, Map<String, String> eventParameters)
471        throws SAXException
472    {
473        MouseButtonInteraction.Button button = null;
474
475        if (eventParameters.get("Button") != null)
476        {
477            int buttonId = Integer.parseInt(eventParameters.get("Button"));
478            if (buttonId == MouseEvent.BUTTON1)
479            {
480                button = MouseButtonInteraction.Button.LEFT;
481            }
482            else if (buttonId == MouseEvent.BUTTON2)
483            {
484                button = MouseButtonInteraction.Button.MIDDLE;
485            }
486            else if (buttonId == MouseEvent.BUTTON3)
487            {
488                button = MouseButtonInteraction.Button.RIGHT;
489            }
490            else
491            {
492                throw new SAXException("unknown mouse button index " + buttonId);
493            }
494        }
495
496        if (JFCEventId.MOUSE_CLICKED == eventId)
497        {
498            int x = Integer.parseInt(eventParameters.get("X"));
499            int y = Integer.parseInt(eventParameters.get("Y"));
500            return new MouseClick(button, x, y);
501        }
502        else if (JFCEventId.MOUSE_PRESSED == eventId)
503        {
504            int x = Integer.parseInt(eventParameters.get("X"));
505            int y = Integer.parseInt(eventParameters.get("Y"));
506            return new MouseButtonDown(button, x, y);
507        }
508        else if (JFCEventId.MOUSE_RELEASED == eventId)
509        {
510            int x = Integer.parseInt(eventParameters.get("X"));
511            int y = Integer.parseInt(eventParameters.get("Y"));
512            return new MouseButtonUp(button, x, y);
513        }
514        else
515        {
516            throw new SAXException("unknown event id " + eventId);
517        }
518    }
519
520    /**
521     * <p>
522     * handles a keyboard interaction. The method determines based on the event id and the
523     * parameters which key on the keyboard is pressed or released. It further checks, if for
524     * every released key there is also a pressed event
525     * </p>
526     *
527     * @param eventId
528     *            the id of the event
529     * @param eventParameters
530     *            the parameters provided for the event
531     *           
532     * @return as described
533     *
534     * @throws SAXException thrown if the provided event id is unknown or if there is a key
535     *                      release without a preceding press of the same key
536     */
537    private IInteraction handleKeyAction(JFCEventId eventId, Map<String, String> eventParameters)
538      throws SAXException
539    {
540        // TODO handle shortcuts
541        if (JFCEventId.KEY_PRESSED == eventId)
542        {
543            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
544            mPressedKeys.add(key);
545
546            return new KeyPressed(key);
547        }
548        else if (JFCEventId.KEY_RELEASED == eventId)
549        {
550            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
551            if (mPressedKeys.contains(key))
552            {
553                mPressedKeys.remove(key);
554            }
555            else
556            {
557                Console.traceln(Level.SEVERE, "log file has an error, as it contains a key up event on key " +
558                                   key + " for which there is no preceeding key down event");
559            }
560
561            return new KeyReleased(key);
562        }
563
564        throw new SAXException("unknown event id " + eventId);
565    }
566
567    /**
568     * <p>
569     * handles explicit keyboard focus changes.
570     * </p>
571     *
572     * @param eventId
573     *            the id of the event
574     * @param eventParameters
575     *            the parameters provided for the event
576     *           
577     * @return as described
578     *
579     * @throws SAXException thrown if the provided event id is unknown
580     */
581    private IInteraction handleNewFocus(JFCEventId eventId, Map<String, String> eventParameters)
582        throws SAXException
583    {
584        if (JFCEventId.FOCUS_GAINED == eventId)
585        {
586            return new KeyboardFocusChange();
587        }
588        else
589        {
590            throw new SAXException("unknown event id " + eventId);
591        }
592    }
593
594    /**
595     * <p>
596     * for some events in the log file, no component specification is provided. In this case the
597     * GUI element on which the event is executed must be determined based on the
598     * <code>toString</code> parameter of the event. This is achieved through this method. The
599     * <code>toString</code> parameter does not always carry sufficient information for the GUI
600     * elements. For example the title is not necessarily provided. Therefore some of this
601     * information is generated.
602     * </p>
603     *
604     * @param toStringValue
605     *            the <code>toString</code> parameter of the event to be parsed for the GUI element
606     *           
607     * @return the appropriate GUI Element
608     *
609     * @throws SAXException thrown if the provided value of the <code>toString</code> parameter
610     *                      can not be parsed
611     */
612    private void getGUIElementSpecFromToString(String toStringValue)
613        throws SAXException
614    {
615        try
616        {
617            // match the following: <type>[<parameters>]
618            String pattern = "([\\w$\\.]*)\\[(.*)\\]";
619            Matcher matcher = Pattern.compile(pattern).matcher(toStringValue);
620
621            if (!matcher.find())
622            {
623                throw new IllegalArgumentException
624                    ("could not parse target from toString parameter");
625            }
626
627            String type = matcher.group(1);
628           
629            // match the following: <parameter>|,
630            // where <parameter> := <digitValue>|<value>|<key>"="<value>
631            pattern = "([\\w$@=\\.]*)|,";
632
633            matcher = Pattern.compile(pattern).matcher(matcher.group(2));
634           
635            float elementHash = -1;
636           
637            pattern = "(([\\d]*)|([\\w$]*)|(([\\w$@\\.]*)=([\\w$@\\.]*)))\\z";
638            Pattern valuePattern = Pattern.compile(pattern);
639           
640            while (matcher.find()) {
641                Matcher valueMatcher = valuePattern.matcher(matcher.group(1));
642                if (valueMatcher.find()) {
643                    if ((valueMatcher.group(2) != null) && (!"".equals(valueMatcher.group(2)))) {
644                        // found digit value. Those in combination usually denote the position
645                        // of the GUI element. So calculate an element has out of them
646                        elementHash += Integer.parseInt(valueMatcher.group(2));
647                    }
648                    else if ((valueMatcher.group(5) != null) &&
649                             (!"".equals(valueMatcher.group(5))) &&
650                             (valueMatcher.group(6) != null) &&
651                             (!"".equals(valueMatcher.group(6))))
652                    {
653                        // found a key value pair. Get some of them and integrate them into the hash
654                        String key = valueMatcher.group(5);
655                       
656                        if ("alignmentX".equals(key) || "alignmentY".equals(key)) {
657                            elementHash += Float.parseFloat(valueMatcher.group(6));
658                        }
659                    }
660                }
661            }
662
663            currentGuiElementSpec.setName("unknown(" + ((int) elementHash) + ")");
664            currentGuiElementSpec.setType(type);
665            currentGuiElementSpec.setIndex(-1);
666            currentGuiElementSpec.setElementHash((int) elementHash);
667        }
668        catch (Exception e)
669        {
670            throw new SAXException("could not parse target", e);
671        }
672    }
673   
674    /**
675     * <p>
676     * creates a default event filter that ignores focus changes, mouse pressed and mouse released
677     * events.
678     * </p>
679     */
680    /*private void setupDefaultEventFilter() {
681        eventFilter = new HashSet<JFCEventId>();
682        eventFilter.add(JFCEventId.MOUSE_PRESSED);
683        eventFilter.add(JFCEventId.MOUSE_RELEASED);
684        eventFilter.add(JFCEventId.FOCUS_GAINED);
685    }*/
686}
Note: See TracBrowser for help on using the repository browser.