source: trunk/quest-plugin-jfc/src/main/java/de/ugoe/cs/quest/plugin/jfc/JFCLogParser.java @ 743

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