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

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