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

Last change on this file since 704 was 704, checked in by pharms, 12 years ago
  • renamed interaction event list to correctly state, what it is, a list of sorted interaction events
File size: 22.6 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.HashSet;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Map;
18import java.util.logging.Level;
19import java.util.regex.Matcher;
20import java.util.regex.Pattern;
21
22import javax.xml.parsers.ParserConfigurationException;
23import javax.xml.parsers.SAXParser;
24import javax.xml.parsers.SAXParserFactory;
25
26import org.xml.sax.Attributes;
27import org.xml.sax.InputSource;
28import org.xml.sax.SAXException;
29import org.xml.sax.SAXParseException;
30import org.xml.sax.helpers.DefaultHandler;
31
32import de.ugoe.cs.quest.eventcore.Event;
33import de.ugoe.cs.quest.eventcore.gui.IInteraction;
34import de.ugoe.cs.quest.eventcore.gui.KeyPressed;
35import de.ugoe.cs.quest.eventcore.gui.KeyReleased;
36import de.ugoe.cs.quest.eventcore.gui.KeyboardFocusChange;
37import de.ugoe.cs.quest.eventcore.gui.MouseButtonDown;
38import de.ugoe.cs.quest.eventcore.gui.MouseButtonInteraction;
39import de.ugoe.cs.quest.eventcore.gui.MouseButtonUp;
40import de.ugoe.cs.quest.eventcore.gui.MouseClick;
41import de.ugoe.cs.quest.eventcore.guimodel.GUIElementFactory;
42import de.ugoe.cs.quest.eventcore.guimodel.GUIModel;
43import de.ugoe.cs.quest.eventcore.guimodel.GUIModelException;
44import de.ugoe.cs.quest.eventcore.guimodel.IGUIElement;
45import de.ugoe.cs.quest.plugin.jfc.eventcore.JFCEventId;
46import de.ugoe.cs.quest.plugin.jfc.guimodel.JFCGUIElementSpec;
47import de.ugoe.cs.tasktree.keyboardmaps.VirtualKey;
48import de.ugoe.cs.util.console.Console;
49
50/**
51 * <p>
52 * This class provides functionality to parse XML log files generated by the JFCMonitor of
53 * EventBench. The result of parsing a file is a collection of event sequences.
54 * </p>
55 *
56 * @author Steffen Herbold
57 * @version 1.0
58 */
59public class JFCLogParser extends DefaultHandler {
60
61    /**
62     * <p>
63     * Collection of event sequences that is contained in the log file, which is parsed.
64     * </p>
65     */
66    private Collection<List<Event>> sequences;
67
68    /**
69     * <p>
70     * Internal handle to the id of the event that is currently being parsed.
71     * </p>
72     */
73    private JFCEventId currentEventId;
74   
75    /**
76     * <p>
77     * Internal handle to the parameters of the event that is currently being parsed.
78     * </p>
79     */
80    private Map<String, String> currentEventParameters;
81
82    /**
83     * <p>
84     * Internal handle to the source parameters of the event that is currently being parsed.
85     * </p>
86     */
87    private Map<String, String> currentSourceParameters;
88
89    /**
90     * <p>
91     * Internal handle to the event sequence that is currently being parsed.
92     * </p>
93     */
94    private List<Event> currentSequence;
95   
96    /**
97     * <p>
98     * internal handle to the parameters currently parsed for a component
99     * </p>
100     */
101    private JFCGUIElementSpec currentGuiElementSpec;
102
103    /**
104     * <p>
105     * internal handle to the last parsed component
106     * </p>
107     */
108    private List<JFCGUIElementSpec> currentGuiElementPath = new ArrayList<JFCGUIElementSpec>();
109
110    /**
111     * <p>
112     * internal handle to the component of the previous event to be potentially reused for the
113     * current
114     * </p>
115     */
116    private IGUIElement lastGUIElement;
117
118    /**
119     * <p>
120     * the model of the GUI elements, that were found during parsing
121     * </p>
122     */
123    private GUIModel guiModel = new GUIModel();
124
125    /**
126     * <p>
127     * this is used to check, if for every pressed key, there is a release of it
128     * </p>
129     */
130    private List<VirtualKey> mPressedKeys = new ArrayList<VirtualKey>();
131
132    /**
133     * <p>
134     * Enumeration to differentiate if a parameter belongs to an event, a source or the parent of a
135     * source.
136     * </p>
137     *
138     * @author Steffen Herbold
139     * @version 1.0
140     */
141    private enum ParamSource {
142        EVENT, SOURCE, PARENT, COMPONENT
143    };
144
145    /**
146     * <p>
147     * Specifies whether the parameters that are currently being read belong the the event, the
148     * source or the parent.
149     * </p>
150     */
151    ParamSource paramSource = null;
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 JFCLogParser() {
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 JFCLogParser(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 InvalidParameterException("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 InvalidParameterException("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            // TODO handle Exception
231            e.printStackTrace();
232        }
233        catch (ParserConfigurationException e) {
234            // TODO handle Exception
235            e.printStackTrace();
236        }
237        catch (SAXException e) {
238            // TODO handle Exception
239            e.printStackTrace();
240        }
241        catch (FileNotFoundException e) {
242            // TODO handle Exception
243            e.printStackTrace();
244        }
245        if (inputSource != null) {
246            inputSource.setSystemId("file://" + file.getAbsolutePath());
247            try {
248                if (saxParser == null) {
249                    throw new RuntimeException("SAXParser creation failed");
250                }
251                saxParser.parse(inputSource, this);
252            }
253            catch (SAXParseException e) {
254                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
255                    ", column " + e.getColumnNumber() + ".");
256                e.printStackTrace();
257            }
258            catch (SAXException e) {
259                // TODO handle Exception
260                e.printStackTrace();
261            }
262            catch (IOException e) {
263                // TODO handle Exception
264                e.printStackTrace();
265            }
266        }
267    }
268
269    /**
270     * <p>
271     * Returns the collection of event sequences that is obtained from parsing log files.
272     * </p>
273     *
274     * @return collection of event sequences
275     */
276    public Collection<List<Event>> getSequences() {
277        return sequences;
278    }
279
280    /**
281     * <p>
282     * Returns the gui model that is obtained from parsing log files.
283     * </p>
284     *
285     * @return collection of event sequences
286     */
287    public GUIModel getGuiModel() {
288        return guiModel;
289    }
290
291    /*
292     * (non-Javadoc)
293     *
294     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
295     * java.lang.String, org.xml.sax.Attributes)
296     */
297    public void startElement(String uri, String localName, String qName, Attributes atts)
298        throws SAXException
299    {
300        if (qName.equals("sessions")) {
301            currentSequence = new LinkedList<Event>();
302        }
303        if (qName.equals("newsession")) {
304            Console.traceln(Level.FINE, "start of session");
305            if (currentSequence != null && !currentSequence.isEmpty()) {
306                // create a copy of the list just to have a correctly typed one.
307                sequences.add(currentSequence.subList(0, currentSequence.size() - 1));
308            }
309            currentSequence = new LinkedList<Event>();
310        }
311        else if (qName.equals("event")) {
312            JFCEventId eventId = JFCEventId.parseEventId(atts.getValue("id"));
313            if ((eventFilter == null) || (!eventFilter.contains(eventId))) {
314                currentEventId = eventId;
315                currentEventParameters = new HashMap<String, String>();
316                currentSourceParameters = new HashMap<String, String>();
317                paramSource = ParamSource.EVENT;
318            }
319        }
320        else if (currentEventId != null) {
321            if (qName.equals("param")) {
322                if (paramSource == ParamSource.EVENT) {
323                    currentEventParameters.put(atts.getValue("name"), atts.getValue("value"));
324                }
325                else if (paramSource == ParamSource.SOURCE) {
326                    currentSourceParameters.put(atts.getValue("name"), atts.getValue("value"));
327                }
328                else if (paramSource == ParamSource.COMPONENT) {
329                    if ("title".equals(atts.getValue("name"))) {
330                        currentGuiElementSpec.setName(atts.getValue("value"));
331                    }
332                    else if ("class".equals(atts.getValue("name"))) {
333                        currentGuiElementSpec.setType(atts.getValue("value"));
334                    }
335                    else if ("icon".equals(atts.getValue("name"))) {
336                        currentGuiElementSpec.setIcon(atts.getValue("value"));
337                    }
338                    else if ("index".equals(atts.getValue("name"))) {
339                        currentGuiElementSpec.setIndex(Integer.parseInt(atts.getValue("value")));
340                    }
341                    else if ("hash".equals(atts.getValue("name"))) {
342                        currentGuiElementSpec.setElementHash(atts.getValue("value"));
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                    // TODO right now, there is null pointer exception possible, if the factory cannot create a the GUIElement. We need to devise where and how this should be handled best
389                    currentGUIElement = guiModel.integratePath
390                        (currentGuiElementPath, GUIElementFactory.getInstance());
391                }
392                catch (GUIModelException e) {
393                    throw new SAXException("error in the GUI model provided in the log", e);
394                }
395               
396                Event event = new Event
397                    (instantiateInteraction(currentEventId, currentEventParameters),
398                     (currentGUIElement == null ? lastGUIElement : currentGUIElement));
399               
400                currentSequence.add(event);
401               
402                currentEventParameters = null;
403                currentSourceParameters = null;
404                currentGuiElementSpec = null;
405                currentGuiElementPath.clear();
406               
407                currentEventId = null;
408               
409                if (currentGUIElement != null) {
410                    lastGUIElement = currentGUIElement;
411                }
412               
413                currentGUIElement = null;
414            }
415            else if (qName.equals("source")) {
416                paramSource = ParamSource.EVENT;
417            }
418            else if (qName.equals("parent")) {
419                paramSource = ParamSource.SOURCE;
420            }
421            else if (qName.equals("component")) {
422                paramSource.equals(ParamSource.SOURCE);
423                currentGuiElementPath.add(currentGuiElementSpec);
424                currentGuiElementSpec = null;
425            }
426        }
427    }
428
429    /**
430     * <p>
431     * depending on the event id and the event parameters, this method instantiates the concrete
432     * interaction, that took place, i.e. the event type
433     * </p>
434     *
435     * @param eventId
436     *            the id of the event
437     * @param eventParameters
438     *            the parameters provided for the event
439     *           
440     * @return as described
441     *
442     * @throws SAXException thrown if the provided event id is unknown
443     */
444    private IInteraction instantiateInteraction(JFCEventId          eventId,
445                                                Map<String, String> eventParameters)
446      throws SAXException
447    {
448        switch (eventId)
449        {
450            case FOCUS_GAINED:
451                return handleNewFocus(eventId, eventParameters);
452
453            case KEY_PRESSED:
454            case KEY_RELEASED:
455            case KEY_TYPED:
456                return handleKeyAction(eventId, eventParameters);
457
458            case MOUSE_CLICKED:
459            case MOUSE_PRESSED:
460            case MOUSE_RELEASED:
461            case MOUSE_MOVED:
462            case MOUSE_ENTERED:
463            case MOUSE_EXITED:
464            case MOUSE_DRAGGED:
465            case MOUSE_WHEEL:
466                return handleMouseAction(eventId, eventParameters);
467
468            default:
469                throw new SAXException("unhandled event id " + eventId);
470        }
471    }
472
473    /**
474     * <p>
475     * handles a mouse interaction. The method determines based on the event id and the parameters
476     * which mouse button is pressed, released or clicked.
477     * </p>
478     *
479     * @param eventId
480     *            the id of the event
481     * @param eventParameters
482     *            the parameters provided for the event
483     *           
484     * @return as described
485     *
486     * @throws SAXException thrown if the provided event id or button index is unknown
487     */
488    private IInteraction handleMouseAction(JFCEventId eventId, Map<String, String> eventParameters)
489        throws SAXException
490    {
491        MouseButtonInteraction.Button button = null;
492
493        if (eventParameters.get("Button") != null)
494        {
495            int buttonId = Integer.parseInt(eventParameters.get("Button"));
496            if (buttonId == MouseEvent.BUTTON1)
497            {
498                button = MouseButtonInteraction.Button.LEFT;
499            }
500            else if (buttonId == MouseEvent.BUTTON2)
501            {
502                button = MouseButtonInteraction.Button.MIDDLE;
503            }
504            else if (buttonId == MouseEvent.BUTTON3)
505            {
506                button = MouseButtonInteraction.Button.RIGHT;
507            }
508            else
509            {
510                throw new SAXException("unknown mouse button index " + buttonId);
511            }
512        }
513
514        if (JFCEventId.MOUSE_CLICKED == eventId)
515        {
516            return new MouseClick(button);
517        }
518        else if (JFCEventId.MOUSE_PRESSED == eventId)
519        {
520            return new MouseButtonDown(button);
521        }
522        else if (JFCEventId.MOUSE_RELEASED == eventId)
523        {
524            return new MouseButtonUp(button);
525        }
526        else
527        {
528            throw new SAXException("unknown event id " + eventId);
529        }
530    }
531
532    /**
533     * <p>
534     * handles a keyboard interaction. The method determines based on the event id and the
535     * parameters which key on the keyboard is pressed or released. It further checks, if for
536     * every released key there is also a pressed event
537     * </p>
538     *
539     * @param eventId
540     *            the id of the event
541     * @param eventParameters
542     *            the parameters provided for the event
543     *           
544     * @return as described
545     *
546     * @throws SAXException thrown if the provided event id is unknown or if there is a key
547     *                      release without a preceding press of the same key
548     */
549    private IInteraction handleKeyAction(JFCEventId eventId, Map<String, String> eventParameters)
550      throws SAXException
551    {
552        // TODO handle shortcuts
553        if (JFCEventId.KEY_PRESSED == eventId)
554        {
555            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
556            mPressedKeys.add(key);
557
558            return new KeyPressed(key);
559        }
560        else if (JFCEventId.KEY_RELEASED == eventId)
561        {
562            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
563            if (mPressedKeys.contains(key))
564            {
565                mPressedKeys.remove(key);
566            }
567            else
568            {
569                throw new SAXException
570                  ("log file has an error, as it contains a key up event on key " + key +
571                   " 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     * for some events in the log file, no component specification is provided. In this case the
610     * GUI element on which the event is executed must be determined based on the
611     * <code>toString</code> parameter of the event. This is achieved through this method. The
612     * <code>toString</code> parameter does not always carry sufficient information for the GUI
613     * elements. For example the title is not necessarily provided. Therefore some of this
614     * information is generated.
615     * </p>
616     *
617     * @param toStringValue
618     *            the <code>toString</code> parameter of the event to be parsed for the GUI element
619     *           
620     * @return the appropriate GUI Element
621     *
622     * @throws SAXException thrown if the provided value of the <code>toString</code> parameter
623     *                      can not be parsed
624     */
625    private void getGUIElementSpecFromToString(String toStringValue)
626        throws SAXException
627    {
628        try
629        {
630            String pattern = "([\\w$\\.]*)\\[.*\\]";
631
632            Matcher matcher1 = Pattern.compile(pattern).matcher(toStringValue);
633
634            if (!matcher1.find())
635            {
636                throw new IllegalArgumentException
637                    ("could not parse target from toString parameter");
638            }
639
640            String type = matcher1.group(1);
641
642            currentGuiElementSpec.setName("unknown");
643            currentGuiElementSpec.setType(type);
644            currentGuiElementSpec.setIcon("unknown");
645            currentGuiElementSpec.setIndex(-1);
646            currentGuiElementSpec.setElementHash("-1");
647        }
648        catch (Exception e)
649        {
650            throw new SAXException("could not parse target", e);
651        }
652    }
653
654    /**
655     * <p>
656     * creates a default event filter that ignores focus changes, mouse pressed and mouse released
657     * events.
658     * </p>
659     */
660    private void setupDefaultEventFilter() {
661        eventFilter = new HashSet<JFCEventId>();
662        eventFilter.add(JFCEventId.MOUSE_PRESSED);
663        eventFilter.add(JFCEventId.MOUSE_RELEASED);
664        eventFilter.add(JFCEventId.FOCUS_GAINED);
665    }
666
667}
Note: See TracBrowser for help on using the repository browser.