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

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