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

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