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

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