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

Last change on this file since 585 was 585, checked in by sherbold, 12 years ago
  • rewrote create of GUI elements by the IGUIElementFactory implementations. We now do not require platform-specific GUI element factories. Instead, mappings files located in the folder data/guimappings that start with guimapping- and and with .txt are used to determine which classes in the GUI structure belong to which GUI elements.
File size: 23.2 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.AbstractDefaultGUIElementFactory;
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            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                    // 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
397                    currentGUIElement = guiModel.integratePath
398                        (currentGuiElementPath, AbstractDefaultGUIElementFactory.getInstance());
399                }
400                catch (GUIModelException e) {
401                    throw new SAXException("error in the GUI model provided in the log", e);
402                }
403               
404                JFCEvent event = new JFCEvent
405                  (instantiateInteraction(currentEventId, currentEventParameters),
406                   (currentGUIElement == null ? lastGUIElement : currentGUIElement),
407                   currentEventParameters, currentSourceParameters, currentParentParameters);
408               
409                currentSequence.add(event);
410               
411                currentEventParameters = null;
412                currentSourceParameters = null;
413                currentParentParameters = null;
414                currentGuiElementSpec = null;
415                currentGuiElementPath.clear();
416               
417                currentEventId = null;
418               
419                if (currentGUIElement != null) {
420                    lastGUIElement = currentGUIElement;
421                }
422               
423                currentGUIElement = null;
424            }
425            else if (qName.equals("source")) {
426                paramSource = ParamSource.EVENT;
427            }
428            else if (qName.equals("parent")) {
429                paramSource = ParamSource.SOURCE;
430            }
431            else if (qName.equals("component")) {
432                paramSource.equals(ParamSource.SOURCE);
433                currentGuiElementPath.add(currentGuiElementSpec);
434                currentGuiElementSpec = null;
435            }
436        }
437    }
438
439    /**
440     * <p>
441     * depending on the event id and the event parameters, this method instantiates the concrete
442     * interaction, that took place, i.e. the event type
443     * </p>
444     *
445     * @param eventId
446     *            the id of the event
447     * @param eventParameters
448     *            the parameters provided for the event
449     *           
450     * @return as described
451     *
452     * @throws SAXException thrown if the provided event id is unknown
453     */
454    private IInteraction instantiateInteraction(JFCEventId          eventId,
455                                                Map<String, String> eventParameters)
456      throws SAXException
457    {
458        switch (eventId)
459        {
460            case FOCUS_GAINED:
461                return handleNewFocus(eventId, eventParameters);
462
463            case KEY_PRESSED:
464            case KEY_RELEASED:
465            case KEY_TYPED:
466                return handleKeyAction(eventId, eventParameters);
467
468            case MOUSE_CLICKED:
469            case MOUSE_PRESSED:
470            case MOUSE_RELEASED:
471            case MOUSE_MOVED:
472            case MOUSE_ENTERED:
473            case MOUSE_EXITED:
474            case MOUSE_DRAGGED:
475            case MOUSE_WHEEL:
476                return handleMouseAction(eventId, eventParameters);
477
478            default:
479                throw new SAXException("unhandled event id " + eventId);
480        }
481    }
482
483    /**
484     * <p>
485     * handles a mouse interaction. The method determines based on the event id and the parameters
486     * which mouse button is pressed, released or clicked.
487     * </p>
488     *
489     * @param eventId
490     *            the id of the event
491     * @param eventParameters
492     *            the parameters provided for the event
493     *           
494     * @return as described
495     *
496     * @throws SAXException thrown if the provided event id or button index is unknown
497     */
498    private IInteraction handleMouseAction(JFCEventId eventId, Map<String, String> eventParameters)
499        throws SAXException
500    {
501        MouseButtonInteraction.Button button = null;
502
503        if (eventParameters.get("Button") != null)
504        {
505            int buttonId = Integer.parseInt(eventParameters.get("Button"));
506            if (buttonId == MouseEvent.BUTTON1)
507            {
508                button = MouseButtonInteraction.Button.LEFT;
509            }
510            else if (buttonId == MouseEvent.BUTTON2)
511            {
512                button = MouseButtonInteraction.Button.MIDDLE;
513            }
514            else if (buttonId == MouseEvent.BUTTON3)
515            {
516                button = MouseButtonInteraction.Button.RIGHT;
517            }
518            else
519            {
520                throw new SAXException("unknown mouse button index " + buttonId);
521            }
522        }
523
524        if (JFCEventId.MOUSE_CLICKED == eventId)
525        {
526            return new MouseClick(button);
527        }
528        else if (JFCEventId.MOUSE_PRESSED == eventId)
529        {
530            return new MouseButtonDown(button);
531        }
532        else if (JFCEventId.MOUSE_RELEASED == eventId)
533        {
534            return new MouseButtonUp(button);
535        }
536        else
537        {
538            throw new SAXException("unknown event id " + eventId);
539        }
540    }
541
542    /**
543     * <p>
544     * handles a keyboard interaction. The method determines based on the event id and the
545     * parameters which key on the keyboard is pressed or released. It further checks, if for
546     * every released key there is also a pressed event
547     * </p>
548     *
549     * @param eventId
550     *            the id of the event
551     * @param eventParameters
552     *            the parameters provided for the event
553     *           
554     * @return as described
555     *
556     * @throws SAXException thrown if the provided event id is unknown or if there is a key
557     *                      release without a preceding press of the same key
558     */
559    private IInteraction handleKeyAction(JFCEventId eventId, Map<String, String> eventParameters)
560      throws SAXException
561    {
562        // TODO handle shortcuts
563        if (JFCEventId.KEY_PRESSED == eventId)
564        {
565            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
566            mPressedKeys.add(key);
567
568            return new KeyPressed(key);
569        }
570        else if (JFCEventId.KEY_RELEASED == eventId)
571        {
572            VirtualKey key = VirtualKey.parseVirtualKey(eventParameters.get("KeyCode"));
573            if (mPressedKeys.contains(key))
574            {
575                mPressedKeys.remove(key);
576            }
577            else
578            {
579                throw new SAXException
580                  ("log file has an error, as it contains a key up event on key " + key +
581                   " for which there is no preceeding key down event");
582            }
583
584            return new KeyReleased(key);
585        }
586
587        throw new SAXException("unknown event id " + eventId);
588    }
589
590    /**
591     * <p>
592     * handles explicit keyboard focus changes.
593     * </p>
594     *
595     * @param eventId
596     *            the id of the event
597     * @param eventParameters
598     *            the parameters provided for the event
599     *           
600     * @return as described
601     *
602     * @throws SAXException thrown if the provided event id is unknown
603     */
604    private IInteraction handleNewFocus(JFCEventId eventId, Map<String, String> eventParameters)
605        throws SAXException
606    {
607        if (JFCEventId.FOCUS_GAINED == eventId)
608        {
609            return new KeyboardFocusChange();
610        }
611        else
612        {
613            throw new SAXException("unknown event id " + eventId);
614        }
615    }
616
617    /**
618     * <p>
619     * for some events in the log file, no component specification is provided. In this case the
620     * GUI element on which the event is executed must be determined based on the
621     * <code>toString</code> parameter of the event. This is achieved through this method. The
622     * <code>toString</code> parameter does not always carry sufficient information for the GUI
623     * elements. For example the title is not necessarily provided. Therefore some of this
624     * information is generated.
625     * </p>
626     *
627     * @param toStringValue
628     *            the <code>toString</code> parameter of the event to be parsed for the GUI element
629     *           
630     * @return the appropriate GUI Element
631     *
632     * @throws SAXException thrown if the provided value of the <code>toString</code> parameter
633     *                      can not be parsed
634     */
635    private void getGUIElementSpecFromToString(String toStringValue)
636        throws SAXException
637    {
638        try
639        {
640            String pattern = "([\\w$\\.]*)\\[.*\\]";
641
642            Matcher matcher1 = Pattern.compile(pattern).matcher(toStringValue);
643
644            if (!matcher1.find())
645            {
646                throw new IllegalArgumentException
647                    ("could not parse target from toString parameter");
648            }
649
650            String type = matcher1.group(1);
651
652            currentGuiElementSpec.setName("unknown");
653            currentGuiElementSpec.setType(type);
654            currentGuiElementSpec.setIcon("unknown");
655            currentGuiElementSpec.setIndex(-1);
656            currentGuiElementSpec.setElementHash(-1);
657        }
658        catch (Exception e)
659        {
660            throw new SAXException("could not parse target", e);
661        }
662    }
663
664    /**
665     * <p>
666     * creates a default event filter that ignores focus changes, mouse pressed and mouse released
667     * events.
668     * </p>
669     */
670    private void setupDefaultEventFilter() {
671        eventFilter = new HashSet<JFCEventId>();
672        eventFilter.add(JFCEventId.MOUSE_PRESSED);
673        eventFilter.add(JFCEventId.MOUSE_RELEASED);
674        eventFilter.add(JFCEventId.FOCUS_GAINED);
675    }
676
677}
Note: See TracBrowser for help on using the repository browser.