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

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