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

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