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

Last change on this file since 573 was 573, checked in by pharms, 12 years ago
  • integrated event bench parsing implementation for JFC with parser for task tree stuff, i.e. now not only Strings as targets, but concrete UI objects are instantiated.
File size: 23.0 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.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.JFCEvent;
46import de.ugoe.cs.quest.plugin.jfc.eventcore.JFCEventId;
47import de.ugoe.cs.quest.plugin.jfc.guimodel.JFCGUIElementFactory;
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            e.printStackTrace();
240        }
241        catch (ParserConfigurationException e) {
242            e.printStackTrace();
243        }
244        catch (SAXException e) {
245            e.printStackTrace();
246        }
247        catch (FileNotFoundException e) {
248            e.printStackTrace();
249        }
250        if (inputSource != null) {
251            inputSource.setSystemId("file://" + file.getAbsolutePath());
252            try {
253                if (saxParser == null) {
254                    throw new RuntimeException("SAXParser creation failed");
255                }
256                saxParser.parse(inputSource, this);
257            }
258            catch (SAXParseException e) {
259                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
260                    ", column " + e.getColumnNumber() + ".");
261                e.printStackTrace();
262            }
263            catch (SAXException e) {
264                e.printStackTrace();
265            }
266            catch (IOException e) {
267                e.printStackTrace();
268            }
269        }
270    }
271
272    /**
273     * <p>
274     * Returns the collection of event sequences that is obtained from parsing log files.
275     * </p>
276     *
277     * @return collection of event sequences
278     */
279    public Collection<List<Event>> getSequences() {
280        return sequences;
281    }
282
283    /**
284     * <p>
285     * Returns the gui model that is obtained from parsing log files.
286     * </p>
287     *
288     * @return collection of event sequences
289     */
290    public GUIModel getGuiModel() {
291        return guiModel;
292    }
293
294    /*
295     * (non-Javadoc)
296     *
297     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
298     * java.lang.String, org.xml.sax.Attributes)
299     */
300    public void startElement(String uri, String localName, String qName, Attributes atts)
301        throws SAXException
302    {
303        if (qName.equals("sessions")) {
304            currentSequence = new InteractionEventList();
305        }
306        if (qName.equals("newsession")) {
307            Console.traceln("start of session");
308            if (currentSequence != null && !currentSequence.isEmpty()) {
309                // create a copy of the list just to have a correctly typed one.
310                sequences.add(currentSequence.subList(0, currentSequence.size() - 1));
311            }
312            currentSequence = new InteractionEventList();
313        }
314        else if (qName.equals("event")) {
315            JFCEventId eventId = JFCEventId.parseEventId(atts.getValue("id"));
316            if ((eventFilter == null) || (!eventFilter.contains(eventId))) {
317                currentEventId = eventId;
318                currentEventParameters = new HashMap<String, String>();
319                currentSourceParameters = new HashMap<String, String>();
320                currentParentParameters = new HashMap<String, String>();
321                paramSource = ParamSource.EVENT;
322            }
323        }
324        else if (currentEventId != null) {
325            if (qName.equals("param")) {
326                if (paramSource == ParamSource.EVENT) {
327                    currentEventParameters.put(atts.getValue("name"), atts.getValue("value"));
328                }
329                else if (paramSource == ParamSource.SOURCE) {
330                    currentSourceParameters.put(atts.getValue("name"), atts.getValue("value"));
331                }
332                else if (paramSource == ParamSource.PARENT) {
333                    currentParentParameters.put(atts.getValue("name"), atts.getValue("value"));
334                }
335                else if (paramSource == ParamSource.COMPONENT) {
336                    if ("title".equals(atts.getValue("name"))) {
337                        currentGuiElementSpec.setName(atts.getValue("value"));
338                    }
339                    else if ("class".equals(atts.getValue("name"))) {
340                        currentGuiElementSpec.setType(atts.getValue("value"));
341                    }
342                    else if ("icon".equals(atts.getValue("name"))) {
343                        currentGuiElementSpec.setIcon(atts.getValue("value"));
344                    }
345                    else if ("index".equals(atts.getValue("name"))) {
346                        currentGuiElementSpec.setIndex(Integer.parseInt(atts.getValue("value")));
347                    }
348                    else if ("hash".equals(atts.getValue("name"))) {
349                        currentGuiElementSpec.setElementHash
350                            (Integer.parseInt(atts.getValue("value"), 16));
351                    }
352                }
353            }
354            else if (qName.equals("source")) {
355                paramSource = ParamSource.SOURCE;
356            }
357            else if (qName.equals("parent")) {
358                paramSource = ParamSource.PARENT;
359            }
360            else if (qName.equals("component")) {
361                paramSource = ParamSource.COMPONENT;
362                currentGuiElementSpec = new JFCGUIElementSpec();
363            }
364        }
365    }
366
367    /*
368     * (non-Javadoc)
369     *
370     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
371     * java.lang.String)
372     */
373    @Override
374    public void endElement(String uri, String localName, String qName) throws SAXException {
375        if (qName.equals("sessions")) {
376            if (currentSequence != null && !currentSequence.isEmpty()) {
377                sequences.add(currentSequence);
378            }
379            currentSequence = null;
380        }
381        else if (currentEventId != null) {
382            if (qName.equals("event")) {
383               
384                if (currentGuiElementPath.size() <= 0)
385                {
386                    // no component specification available. Try to parse the GUI element from
387                    // the toString parameter
388                    currentGuiElementSpec = new JFCGUIElementSpec();
389                    getGUIElementSpecFromToString(currentSourceParameters.get("toString"));
390                    currentGuiElementPath.add(currentGuiElementSpec);
391                    currentGuiElementSpec = null;
392                }
393               
394                IGUIElement currentGUIElement;
395                try {
396                    currentGUIElement = guiModel.integratePath
397                        (currentGuiElementPath, JFCGUIElementFactory.getInstance());
398                }
399                catch (GUIModelException e) {
400                    throw new SAXException("error in the GUI model provided in the log", e);
401                }
402               
403                JFCEvent event = new JFCEvent
404                  (instantiateInteraction(currentEventId, currentEventParameters),
405                   (currentGUIElement == null ? lastGUIElement : currentGUIElement),
406                   currentEventParameters, currentSourceParameters, currentParentParameters);
407               
408                currentSequence.add(event);
409               
410                currentEventParameters = null;
411                currentSourceParameters = null;
412                currentParentParameters = null;
413                currentGuiElementSpec = null;
414                currentGuiElementPath.clear();
415               
416                currentEventId = null;
417               
418                if (currentGUIElement != null) {
419                    lastGUIElement = currentGUIElement;
420                }
421               
422                currentGUIElement = null;
423            }
424            else if (qName.equals("source")) {
425                paramSource = ParamSource.EVENT;
426            }
427            else if (qName.equals("parent")) {
428                paramSource = ParamSource.SOURCE;
429            }
430            else if (qName.equals("component")) {
431                paramSource.equals(ParamSource.SOURCE);
432                currentGuiElementPath.add(currentGuiElementSpec);
433                currentGuiElementSpec = null;
434            }
435        }
436    }
437
438    /**
439     * <p>
440     * depending on the event id and the event parameters, this method instantiates the concrete
441     * interaction, that took place, i.e. the event type
442     * </p>
443     *
444     * @param eventId
445     *            the id of the event
446     * @param eventParameters
447     *            the parameters provided for the event
448     *           
449     * @return as described
450     *
451     * @throws SAXException thrown if the provided event id is unknown
452     */
453    private IInteraction instantiateInteraction(JFCEventId          eventId,
454                                                Map<String, String> eventParameters)
455      throws SAXException
456    {
457        switch (eventId)
458        {
459            case FOCUS_GAINED:
460                return handleNewFocus(eventId, eventParameters);
461
462            case KEY_PRESSED:
463            case KEY_RELEASED:
464            case KEY_TYPED:
465                return handleKeyAction(eventId, eventParameters);
466
467            case MOUSE_CLICKED:
468            case MOUSE_PRESSED:
469            case MOUSE_RELEASED:
470            case MOUSE_MOVED:
471            case MOUSE_ENTERED:
472            case MOUSE_EXITED:
473            case MOUSE_DRAGGED:
474            case MOUSE_WHEEL:
475                return handleMouseAction(eventId, eventParameters);
476
477            default:
478                throw new SAXException("unhandled event id " + eventId);
479        }
480    }
481
482    /**
483     * <p>
484     * handles a mouse interaction. The method determines based on the event id and the parameters
485     * which mouse button is pressed, released or clicked.
486     * </p>
487     *
488     * @param eventId
489     *            the id of the event
490     * @param eventParameters
491     *            the parameters provided for the event
492     *           
493     * @return as described
494     *
495     * @throws SAXException thrown if the provided event id or button index is unknown
496     */
497    private IInteraction handleMouseAction(JFCEventId eventId, Map<String, String> eventParameters)
498        throws SAXException
499    {
500        MouseButtonInteraction.Button button = null;
501
502        if (eventParameters.get("Button") != null)
503        {
504            int buttonId = Integer.parseInt(eventParameters.get("Button"));
505            if (buttonId == MouseEvent.BUTTON1)
506            {
507                button = MouseButtonInteraction.Button.LEFT;
508            }
509            else if (buttonId == MouseEvent.BUTTON2)
510            {
511                button = MouseButtonInteraction.Button.MIDDLE;
512            }
513            else if (buttonId == MouseEvent.BUTTON3)
514            {
515                button = MouseButtonInteraction.Button.RIGHT;
516            }
517            else
518            {
519                throw new SAXException("unknown mouse button index " + buttonId);
520            }
521        }
522
523        if (JFCEventId.MOUSE_CLICKED == eventId)
524        {
525            return new MouseClick(button);
526        }
527        else if (JFCEventId.MOUSE_PRESSED == eventId)
528        {
529            return new MouseButtonDown(button);
530        }
531        else if (JFCEventId.MOUSE_RELEASED == eventId)
532        {
533            return new MouseButtonUp(button);
534        }
535        else
536        {
537            throw new SAXException("unknown event id " + eventId);
538        }
539    }
540
541    /**
542     * <p>
543     * handles a keyboard interaction. The method determines based on the event id and the
544     * parameters which key on the keyboard is pressed or released. It further checks, if for
545     * every released key there is also a pressed event
546     * </p>
547     *
548     * @param eventId
549     *            the id of the event
550     * @param eventParameters
551     *            the parameters provided for the event
552     *           
553     * @return as described
554     *
555     * @throws SAXException thrown if the provided event id is unknown or if there is a key
556     *                      release without a preceding press of the same key
557     */
558    private IInteraction handleKeyAction(JFCEventId eventId, Map<String, String> eventParameters)
559      throws SAXException
560    {
561        // TODO handle shortcuts
562        if (JFCEventId.KEY_PRESSED == eventId)
563        {
564            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
565            mPressedKeys.add(key);
566
567            return new KeyPressed(key);
568        }
569        else if (JFCEventId.KEY_RELEASED == eventId)
570        {
571            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
572            if (mPressedKeys.contains(key))
573            {
574                mPressedKeys.remove(key);
575            }
576            else
577            {
578                throw new SAXException
579                  ("log file has an error, as it contains a key up event on key " + key +
580                   " for which there is no preceeding key down event");
581            }
582
583            return new KeyReleased(key);
584        }
585
586        throw new SAXException("unknown event id " + eventId);
587    }
588
589    /**
590     * <p>
591     * handles explicit keyboard focus changes.
592     * </p>
593     *
594     * @param eventId
595     *            the id of the event
596     * @param eventParameters
597     *            the parameters provided for the event
598     *           
599     * @return as described
600     *
601     * @throws SAXException thrown if the provided event id is unknown
602     */
603    private IInteraction handleNewFocus(JFCEventId eventId, Map<String, String> eventParameters)
604        throws SAXException
605    {
606        if (JFCEventId.FOCUS_GAINED == eventId)
607        {
608            return new KeyboardFocusChange();
609        }
610        else
611        {
612            throw new SAXException("unknown event id " + eventId);
613        }
614    }
615
616    /**
617     * <p>
618     * for some events in the log file, no component specification is provided. In this case the
619     * GUI element on which the event is executed must be determined based on the
620     * <code>toString</code> parameter of the event. This is achieved through this method. The
621     * <code>toString</code> parameter does not always carry sufficient information for the GUI
622     * elements. For example the title is not necessarily provided. Therefore some of this
623     * information is generated.
624     * </p>
625     *
626     * @param toStringValue
627     *            the <code>toString</code> parameter of the event to be parsed for the GUI element
628     *           
629     * @return the appropriate GUI Element
630     *
631     * @throws SAXException thrown if the provided value of the <code>toString</code> parameter
632     *                      can not be parsed
633     */
634    private void getGUIElementSpecFromToString(String toStringValue)
635        throws SAXException
636    {
637        try
638        {
639            String pattern = "([\\w$\\.]*)\\[.*\\]";
640
641            Matcher matcher1 = Pattern.compile(pattern).matcher(toStringValue);
642
643            if (!matcher1.find())
644            {
645                throw new IllegalArgumentException
646                    ("could not parse target from toString parameter");
647            }
648
649            String type = matcher1.group(1);
650
651            currentGuiElementSpec.setName("unknown");
652            currentGuiElementSpec.setType(type);
653            currentGuiElementSpec.setIcon("unknown");
654            currentGuiElementSpec.setIndex(-1);
655            currentGuiElementSpec.setElementHash(-1);
656        }
657        catch (Exception e)
658        {
659            throw new SAXException("could not parse target", e);
660        }
661    }
662
663    /**
664     * <p>
665     * creates a default event filter that ignores focus changes, mouse pressed and mouse released
666     * events.
667     * </p>
668     */
669    private void setupDefaultEventFilter() {
670        eventFilter = new HashSet<JFCEventId>();
671        eventFilter.add(JFCEventId.MOUSE_PRESSED);
672        eventFilter.add(JFCEventId.MOUSE_RELEASED);
673        eventFilter.add(JFCEventId.FOCUS_GAINED);
674    }
675
676}
Note: See TracBrowser for help on using the repository browser.