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

Last change on this file since 1113 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
RevLine 
[1113]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
[963]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;
[1003]51import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
[963]52import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
[1084]53import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
[963]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>
[1049]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>
[963]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;
[1027]94
[963]95    /**
96     *
97     * <p>
[1009]98     * Internal handle to the hashcode of the GUI element, that is currently parsed.
[963]99     * </p>
100     */
[1009]101    private Long currentGUIElementHash;
[1027]102
[963]103    /**
104     *
105     * <p>
[1049]106     * Internal handle to the hashcode of the parent of the GUI element, that is currently parsed.
[963]107     * </p>
108     */
[1004]109    private Long currentParentHash;
[1027]110
[963]111    /**
112     * <p>
[1001]113     * Internal handle to the source of the event that is currently being parsed.
[963]114     * </p>
115     */
[1004]116    private Long currentEventSource;
[1027]117
[1001]118    /**
119     * <p>
120     * Internal handle to the timestamp of the event that is currently being parsed.
121     */
122    private Long currentEventTimestamp = -1l;
[1027]123
[963]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;
[1027]137
[963]138    /**
139     * <p>
[1009]140     * internal handle to the specification currently parsed for a GUI element
[963]141     * </p>
142     */
143    private JFCGUIElementSpec currentGuiElementSpec;
144
145    /**
146     * <p>
[1009]147     * internal handle to the parsed GUI structure, stored in a GUIElementTree
[963]148     * </p>
149     */
[1027]150    private GUIElementTree<Long> currentGUIElementTree;
[963]151
152    /**
153     * <p>
[1049]154     * internal handle to the class ancestors
[963]155     * </p>
156     */
[990]157    private List<String> currentTypeHierarchy;
[963]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>();
[1027]165
[963]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;
[1049]182        eventsWithoutTargets = new HashMap<Long, List<Event>>();
183        // setupDefaultEventFilter();
[963]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;
[1049]201        eventsWithoutTargets = new HashMap<Long, List<Event>>();
[963]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 =
[1049]242                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
[963]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() +
[1049]274                    ", column " + e.getColumnNumber() + ".");
[963]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        }
[1049]289        if (!eventsWithoutTargets.isEmpty()) {
[1058]290            Console.printerr("Some events reference GUI elements that are not part of logfile. " +
291                        "These events have been parsed without target.");
[1049]292        }
[963]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>
[1009]308     * Returns the GUI model that is obtained from parsing log files.
[963]309     * </p>
310     *
311     * @return GUIModel
312     */
313    public GUIModel getGuiModel() {
[1003]314        return currentGUIElementTree.getGUIModel();
[963]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)
[1049]324        throws SAXException
325    {
[963]326        if (qName.equals("sessions")) {
327            currentSequence = new LinkedList<Event>();
[1003]328            if (currentGUIElementTree == null)
[1027]329                currentGUIElementTree = new GUIElementTree<Long>();
[963]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")) {
[1027]340            currentGUIElementHash = Long.parseLong(atts.getValue("hash"), 16);
341            currentGuiElementSpec = new JFCGUIElementSpec();
342            currentGuiElementSpec.setElementHash((int) currentGUIElementHash.longValue());
[963]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")) {
[1027]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);
[963]358        }
359        else if (qName.equals("param")) {
[1049]360            if (currentEventId != null) {
361                if ("source".equals(atts.getValue("name"))) {
[1027]362                    currentEventSource = Long.parseLong(atts.getValue("value"), 16);
363                }
[1049]364                if ("timestamp".equals(atts.getValue("name"))) {
[1027]365                    currentEventTimestamp = Long.parseLong(atts.getValue("value"));
366                }
[963]367                currentEventParameters.put(atts.getValue("name"), atts.getValue("value"));
[1049]368            }
369            else if (currentGUIElementHash != null) {
[1027]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                }
[963]385            }
386        }
[1049]387        else if (qName.equals("ancestor")) {
[1027]388            currentTypeHierarchy.add(atts.getValue("name"));
[963]389        }
[1049]390        else if (qName.equals("ancestors")) {
[1027]391            currentTypeHierarchy = new LinkedList<String>();
[963]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        }
[1049]409        else if (qName.equals("ancestors")) {
[1027]410            currentGuiElementSpec.setTypeHierarchy(currentTypeHierarchy);
[984]411        }
[1009]412        else if (qName.equals("component") && currentGUIElementHash != null) {
[1084]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            }
[1049]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);
[1051]427                    guiElement.markUsed();
[1049]428                }
429                eventsWithoutTargets.remove(currentGUIElementHash);
430            }
[1027]431            currentGUIElementHash = null;
432            currentParentHash = null;
433            currentTypeHierarchy = null;
[963]434        }
435        else if (currentEventId != null) {
436            if (qName.equals("event")) {
[1027]437
[963]438                IGUIElement currentGUIElement;
[1003]439                currentGUIElement = currentGUIElementTree.find(currentEventSource);
[1058]440                Event event;
[1049]441                // in some rare cases the target GUI element of the event is not
442                // known yet
443                if (currentGUIElement == null) {
[1058]444                    event = new Event(instantiateInteraction(currentEventId, currentEventParameters));
[1049]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 {
[1058]453                    event = new Event(instantiateInteraction(currentEventId, currentEventParameters),
[1049]454                                  currentGUIElement);
455                    JFCGUIElement currentEventTarget = (JFCGUIElement) event.getTarget();
[1051]456                    currentEventTarget.markUsed();
[1049]457                }
[1058]458                event.setTimestamp(currentEventTimestamp);
459                currentSequence.add(event);
[1049]460
[963]461                currentEventParameters = null;
462                currentEventId = null;
[1001]463                currentEventTimestamp = -1l;
[963]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>
[1049]473     *
[963]474     * @param eventId
475     *            the id of the event
476     * @param eventParameters
477     *            the parameters provided for the event
[1049]478     *
[963]479     * @return as described
480     *
[1049]481     * @throws SAXException
482     *             thrown if the provided event id is unknown
[963]483     */
[1049]484    private IInteraction instantiateInteraction(JFCEventId eventId,
[963]485                                                Map<String, String> eventParameters)
[1049]486        throws SAXException
487    {
[963]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        }
[1049]511    }
[963]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>
[1049]518     *
[963]519     * @param eventId
520     *            the id of the event
521     * @param eventParameters
522     *            the parameters provided for the event
[1049]523     *
[963]524     * @return as described
525     *
[1049]526     * @throws SAXException
527     *             thrown if the provided event id or button index is unknown
[963]528     */
529    private IInteraction handleMouseAction(JFCEventId eventId, Map<String, String> eventParameters)
[1049]530        throws SAXException
531    {
[963]532        MouseButtonInteraction.Button button = null;
533
[1049]534        if (eventParameters.get("Button") != null) {
[963]535            int buttonId = Integer.parseInt(eventParameters.get("Button"));
[1049]536            if (buttonId == MouseEvent.BUTTON1) {
[963]537                button = MouseButtonInteraction.Button.LEFT;
538            }
[1049]539            else if (buttonId == MouseEvent.BUTTON2) {
[963]540                button = MouseButtonInteraction.Button.MIDDLE;
541            }
[1049]542            else if (buttonId == MouseEvent.BUTTON3) {
[963]543                button = MouseButtonInteraction.Button.RIGHT;
544            }
[1049]545            else {
[963]546                throw new SAXException("unknown mouse button index " + buttonId);
547            }
548        }
549
[1049]550        if (JFCEventId.MOUSE_CLICKED == eventId) {
[963]551            int x = Integer.parseInt(eventParameters.get("X"));
552            int y = Integer.parseInt(eventParameters.get("Y"));
553            return new MouseClick(button, x, y);
554        }
[1049]555        else if (JFCEventId.MOUSE_PRESSED == eventId) {
[963]556            int x = Integer.parseInt(eventParameters.get("X"));
557            int y = Integer.parseInt(eventParameters.get("Y"));
558            return new MouseButtonDown(button, x, y);
559        }
[1049]560        else if (JFCEventId.MOUSE_RELEASED == eventId) {
[963]561            int x = Integer.parseInt(eventParameters.get("X"));
562            int y = Integer.parseInt(eventParameters.get("Y"));
563            return new MouseButtonUp(button, x, y);
564        }
[1049]565        else {
[963]566            throw new SAXException("unknown event id " + eventId);
567        }
[1049]568    }
[963]569
570    /**
571     * <p>
572     * handles a keyboard interaction. The method determines based on the event id and the
[1049]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
[963]575     * </p>
[1049]576     *
[963]577     * @param eventId
578     *            the id of the event
579     * @param eventParameters
580     *            the parameters provided for the event
[1049]581     *
[963]582     * @return as described
583     *
[1049]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
[963]587     */
588    private IInteraction handleKeyAction(JFCEventId eventId, Map<String, String> eventParameters)
[1049]589        throws SAXException
590    {
[963]591        // TODO handle shortcuts
[1049]592        if (JFCEventId.KEY_PRESSED == eventId) {
[963]593            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
594            mPressedKeys.add(key);
595
596            return new KeyPressed(key);
597        }
[1049]598        else if (JFCEventId.KEY_RELEASED == eventId) {
[963]599            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
[1049]600            if (mPressedKeys.contains(key)) {
[963]601                mPressedKeys.remove(key);
602            }
[1049]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");
[963]607            }
608
609            return new KeyReleased(key);
610        }
611
612        throw new SAXException("unknown event id " + eventId);
[1049]613    }
[963]614
615    /**
616     * <p>
617     * handles explicit keyboard focus changes.
618     * </p>
[1049]619     *
[963]620     * @param eventId
621     *            the id of the event
622     * @param eventParameters
623     *            the parameters provided for the event
[1049]624     *
[963]625     * @return as described
626     *
[1049]627     * @throws SAXException
628     *             thrown if the provided event id is unknown
[963]629     */
630    private IInteraction handleNewFocus(JFCEventId eventId, Map<String, String> eventParameters)
[1049]631        throws SAXException
632    {
633        if (JFCEventId.FOCUS_GAINED == eventId) {
[963]634            return new KeyboardFocusChange();
635        }
[1049]636        else {
[963]637            throw new SAXException("unknown event id " + eventId);
638        }
[1049]639    }
[963]640
641    /**
642     * <p>
643     * creates a default event filter that ignores focus changes, mouse pressed and mouse released
644     * events.
645     * </p>
646     */
[1049]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     */
[963]652}
Note: See TracBrowser for help on using the repository browser.