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

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