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
RevLine 
[1049]1
[963]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;
[1003]38import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
[963]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>
[1049]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>
[963]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;
[1027]80
[963]81    /**
82     *
83     * <p>
[1009]84     * Internal handle to the hashcode of the GUI element, that is currently parsed.
[963]85     * </p>
86     */
[1009]87    private Long currentGUIElementHash;
[1027]88
[963]89    /**
90     *
91     * <p>
[1049]92     * Internal handle to the hashcode of the parent of the GUI element, that is currently parsed.
[963]93     * </p>
94     */
[1004]95    private Long currentParentHash;
[1027]96
[963]97    /**
98     * <p>
[1001]99     * Internal handle to the source of the event that is currently being parsed.
[963]100     * </p>
101     */
[1004]102    private Long currentEventSource;
[1027]103
[1001]104    /**
105     * <p>
106     * Internal handle to the timestamp of the event that is currently being parsed.
107     */
108    private Long currentEventTimestamp = -1l;
[1027]109
[963]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;
[1027]123
[963]124    /**
125     * <p>
[1009]126     * internal handle to the specification currently parsed for a GUI element
[963]127     * </p>
128     */
129    private JFCGUIElementSpec currentGuiElementSpec;
130
131    /**
132     * <p>
[1009]133     * internal handle to the parsed GUI structure, stored in a GUIElementTree
[963]134     * </p>
135     */
[1027]136    private GUIElementTree<Long> currentGUIElementTree;
[963]137
138    /**
139     * <p>
[1049]140     * internal handle to the class ancestors
[963]141     * </p>
142     */
[990]143    private List<String> currentTypeHierarchy;
[963]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>();
[1027]151
[963]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;
[1049]168        eventsWithoutTargets = new HashMap<Long, List<Event>>();
169        // setupDefaultEventFilter();
[963]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;
[1049]187        eventsWithoutTargets = new HashMap<Long, List<Event>>();
[963]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 =
[1049]228                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
[963]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() +
[1049]260                    ", column " + e.getColumnNumber() + ".");
[963]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        }
[1049]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        }
[963]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>
[1009]294     * Returns the GUI model that is obtained from parsing log files.
[963]295     * </p>
296     *
297     * @return GUIModel
298     */
299    public GUIModel getGuiModel() {
[1003]300        return currentGUIElementTree.getGUIModel();
[963]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)
[1049]310        throws SAXException
311    {
[963]312        if (qName.equals("sessions")) {
313            currentSequence = new LinkedList<Event>();
[1003]314            if (currentGUIElementTree == null)
[1027]315                currentGUIElementTree = new GUIElementTree<Long>();
[963]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")) {
[1027]326            currentGUIElementHash = Long.parseLong(atts.getValue("hash"), 16);
327            currentGuiElementSpec = new JFCGUIElementSpec();
328            currentGuiElementSpec.setElementHash((int) currentGUIElementHash.longValue());
[963]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")) {
[1027]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);
[963]344        }
345        else if (qName.equals("param")) {
[1049]346            if (currentEventId != null) {
347                if ("source".equals(atts.getValue("name"))) {
[1027]348                    currentEventSource = Long.parseLong(atts.getValue("value"), 16);
349                }
[1049]350                if ("timestamp".equals(atts.getValue("name"))) {
[1027]351                    currentEventTimestamp = Long.parseLong(atts.getValue("value"));
352                }
[963]353                currentEventParameters.put(atts.getValue("name"), atts.getValue("value"));
[1049]354            }
355            else if (currentGUIElementHash != null) {
[1027]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                }
[963]371            }
372        }
[1049]373        else if (qName.equals("ancestor")) {
[1027]374            currentTypeHierarchy.add(atts.getValue("name"));
[963]375        }
[1049]376        else if (qName.equals("ancestors")) {
[1027]377            currentTypeHierarchy = new LinkedList<String>();
[963]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        }
[1049]395        else if (qName.equals("ancestors")) {
[1027]396            currentGuiElementSpec.setTypeHierarchy(currentTypeHierarchy);
[984]397        }
[1009]398        else if (qName.equals("component") && currentGUIElementHash != null) {
[1049]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);
[1051]407                    guiElement.markUsed();
[1049]408                    currentSequence.add(event);
409                }
410                eventsWithoutTargets.remove(currentGUIElementHash);
411            }
[1027]412            currentGUIElementHash = null;
413            currentParentHash = null;
414            currentTypeHierarchy = null;
[963]415        }
416        else if (currentEventId != null) {
417            if (qName.equals("event")) {
[1027]418
[963]419                IGUIElement currentGUIElement;
[1003]420                currentGUIElement = currentGUIElementTree.find(currentEventSource);
[1027]421
[1049]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);
[1027]428
[1049]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();
[1051]442                    currentEventTarget.markUsed();
[1027]443
[1049]444                    currentSequence.add(event);
445                }
446
[963]447                currentEventParameters = null;
448                currentEventId = null;
[1001]449                currentEventTimestamp = -1l;
[963]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>
[1049]459     *
[963]460     * @param eventId
461     *            the id of the event
462     * @param eventParameters
463     *            the parameters provided for the event
[1049]464     *
[963]465     * @return as described
466     *
[1049]467     * @throws SAXException
468     *             thrown if the provided event id is unknown
[963]469     */
[1049]470    private IInteraction instantiateInteraction(JFCEventId eventId,
[963]471                                                Map<String, String> eventParameters)
[1049]472        throws SAXException
473    {
[963]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        }
[1049]497    }
[963]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>
[1049]504     *
[963]505     * @param eventId
506     *            the id of the event
507     * @param eventParameters
508     *            the parameters provided for the event
[1049]509     *
[963]510     * @return as described
511     *
[1049]512     * @throws SAXException
513     *             thrown if the provided event id or button index is unknown
[963]514     */
515    private IInteraction handleMouseAction(JFCEventId eventId, Map<String, String> eventParameters)
[1049]516        throws SAXException
517    {
[963]518        MouseButtonInteraction.Button button = null;
519
[1049]520        if (eventParameters.get("Button") != null) {
[963]521            int buttonId = Integer.parseInt(eventParameters.get("Button"));
[1049]522            if (buttonId == MouseEvent.BUTTON1) {
[963]523                button = MouseButtonInteraction.Button.LEFT;
524            }
[1049]525            else if (buttonId == MouseEvent.BUTTON2) {
[963]526                button = MouseButtonInteraction.Button.MIDDLE;
527            }
[1049]528            else if (buttonId == MouseEvent.BUTTON3) {
[963]529                button = MouseButtonInteraction.Button.RIGHT;
530            }
[1049]531            else {
[963]532                throw new SAXException("unknown mouse button index " + buttonId);
533            }
534        }
535
[1049]536        if (JFCEventId.MOUSE_CLICKED == eventId) {
[963]537            int x = Integer.parseInt(eventParameters.get("X"));
538            int y = Integer.parseInt(eventParameters.get("Y"));
539            return new MouseClick(button, x, y);
540        }
[1049]541        else if (JFCEventId.MOUSE_PRESSED == eventId) {
[963]542            int x = Integer.parseInt(eventParameters.get("X"));
543            int y = Integer.parseInt(eventParameters.get("Y"));
544            return new MouseButtonDown(button, x, y);
545        }
[1049]546        else if (JFCEventId.MOUSE_RELEASED == eventId) {
[963]547            int x = Integer.parseInt(eventParameters.get("X"));
548            int y = Integer.parseInt(eventParameters.get("Y"));
549            return new MouseButtonUp(button, x, y);
550        }
[1049]551        else {
[963]552            throw new SAXException("unknown event id " + eventId);
553        }
[1049]554    }
[963]555
556    /**
557     * <p>
558     * handles a keyboard interaction. The method determines based on the event id and the
[1049]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
[963]561     * </p>
[1049]562     *
[963]563     * @param eventId
564     *            the id of the event
565     * @param eventParameters
566     *            the parameters provided for the event
[1049]567     *
[963]568     * @return as described
569     *
[1049]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
[963]573     */
574    private IInteraction handleKeyAction(JFCEventId eventId, Map<String, String> eventParameters)
[1049]575        throws SAXException
576    {
[963]577        // TODO handle shortcuts
[1049]578        if (JFCEventId.KEY_PRESSED == eventId) {
[963]579            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
580            mPressedKeys.add(key);
581
582            return new KeyPressed(key);
583        }
[1049]584        else if (JFCEventId.KEY_RELEASED == eventId) {
[963]585            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
[1049]586            if (mPressedKeys.contains(key)) {
[963]587                mPressedKeys.remove(key);
588            }
[1049]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");
[963]593            }
594
595            return new KeyReleased(key);
596        }
597
598        throw new SAXException("unknown event id " + eventId);
[1049]599    }
[963]600
601    /**
602     * <p>
603     * handles explicit keyboard focus changes.
604     * </p>
[1049]605     *
[963]606     * @param eventId
607     *            the id of the event
608     * @param eventParameters
609     *            the parameters provided for the event
[1049]610     *
[963]611     * @return as described
612     *
[1049]613     * @throws SAXException
614     *             thrown if the provided event id is unknown
[963]615     */
616    private IInteraction handleNewFocus(JFCEventId eventId, Map<String, String> eventParameters)
[1049]617        throws SAXException
618    {
619        if (JFCEventId.FOCUS_GAINED == eventId) {
[963]620            return new KeyboardFocusChange();
621        }
[1049]622        else {
[963]623            throw new SAXException("unknown event id " + eventId);
624        }
[1049]625    }
[963]626
627    /**
628     * <p>
629     * creates a default event filter that ignores focus changes, mouse pressed and mouse released
630     * events.
631     * </p>
632     */
[1049]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     */
[963]638}
Note: See TracBrowser for help on using the repository browser.