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

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