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

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