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

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