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

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