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

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