source: trunk/autoquest-plugin-android/src/main/java/de/ugoe/cs/autoquest/plugin/android/AndroidLogParser.java @ 1880

Last change on this file since 1880 was 1880, checked in by funger, 9 years ago
  • Add the position of the element in the original GUI.
  • getSimilarity extended (path + title, path + position now compared)
  • Property svn:mime-type set to text/plain
File size: 15.2 KB
RevLine 
[1783]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
[1782]15package de.ugoe.cs.autoquest.plugin.android;
16
[1783]17import java.io.File;
18import java.io.FileInputStream;
19import java.io.FileNotFoundException;
20import java.io.IOException;
21import java.io.InputStreamReader;
22import java.io.UnsupportedEncodingException;
23import java.util.Collection;
24import java.util.HashMap;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
[1803]28import java.util.logging.Level;
[1782]29
[1783]30import javax.xml.parsers.ParserConfigurationException;
31import javax.xml.parsers.SAXParser;
32import javax.xml.parsers.SAXParserFactory;
33
34import org.xml.sax.Attributes;
35import org.xml.sax.InputSource;
36import org.xml.sax.SAXException;
37import org.xml.sax.SAXParseException;
38import org.xml.sax.helpers.DefaultHandler;
39
40import de.ugoe.cs.autoquest.eventcore.Event;
[1815]41import de.ugoe.cs.autoquest.eventcore.gui.CharacterTyped;
[1797]42import de.ugoe.cs.autoquest.eventcore.gui.IInteraction;
[1815]43import de.ugoe.cs.autoquest.eventcore.gui.TouchSingle;
[1783]44import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
45import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
46import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
47import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
[1797]48import de.ugoe.cs.autoquest.plugin.android.guimodel.ANDROIDGUIElement;
49import de.ugoe.cs.autoquest.plugin.android.guimodel.ANDROIDGUIElementSpec;
[1783]50import de.ugoe.cs.util.console.Console;
51
52/**
53 * <p>
[1819]54 * This class provides functionality to parse XML log files generated by the AndroidMonitor of
55 * AutoQUEST. The result of parsing a file is a collection of event sequences.
[1783]56 * </p>
57 *
58 * @author Florian Unger
59 * @version 1.0
60 */
61public class AndroidLogParser extends DefaultHandler {
62
[1819]63    /*
64     * (non-Javadoc)
65     *
66     * int java.lang.Object.hashCode() is used in the Androidmonitor Long is used to internally
67     * handle and compare the number. e.g. currentGUIElementHash != null
68     */
69    /**
70     * <p>
71     * Internal handle to the id of the event that is currently being parsed.
72     * </p>
73     */
74    private String currentEventId = null;
[1783]75
[1819]76    /**
77     * <p>
78     * Internal handle to the parameters of the event currently being parsed.
79     * </p>
80     */
81    private Map<String, String> currentEventParameters;
[1783]82
[1819]83    /**
84     * <p>
85     * Internal handle to the source of the event that is currently being parsed.
86     * </p>
87     */
88    private Long currentEventSource;
[1783]89
[1819]90    /**
91     * <p>
92     * Internal handle to the timestamp of the event that is currently being parsed.
93     */
94    private Long currentEventTimestamp = -1l;
[1783]95
[1819]96    /**
97     *
98     * <p>
99     * Internal handle to the hashcode of the GUI element, that is currently parsed.
100     * </p>
101     */
102    private Long currentGUIElementHash = null;
[1783]103
[1819]104    /**
105     * <p>
106     * Internal handle to the parsed GUI structure, stored in a GUIElementTree
107     * </p>
108     */
109    private GUIElementTree<Long> currentGUIElementTree;
[1783]110
[1819]111    /**
112     * <p>
113     * Internal handle to the specification currently parsed for a GUI element
114     * </p>
115     */
116    private ANDROIDGUIElementSpec currentGUIElementSpec;
[1783]117
[1819]118    /**
119     *
120     * <p>
121     * Internal handle to the hashcode of the parent of the GUI element, that is currently parsed.
122     * </p>
123     */
124    private Long currentParentHash;
[1783]125
[1819]126    /**
[1815]127     * <p>
[1819]128     * Internal handle to the event sequence that is currently being parsed.
129     * </p>
130     */
131    private List<Event> currentSequence;
132
133    /**
134     * <p>
[1815]135     * internal handle to the class ancestors
136     * </p>
137     */
[1819]138    private List<String> currentTypeHierarchy;
[1783]139
[1819]140    /**
141     * <p>
142     * Map that holds events that had no registered target GUI element during parsing. Keys are the
143     * IDs of the unregistered targets.
144     * </p>
145     */
146    // private Map<Long, List<Event>> eventsWithoutTargets;
[1783]147
[1819]148    /**
149     * <p>
150     * Collection of event sequences that is contained in the log file, which is parsed.
151     * </p>
152     */
153    private Collection<List<Event>> sequences;
[1783]154
[1819]155    /**
[1869]156     * Show parsed elements to find out failures. 
157     */
[1870]158    private Boolean showSteps = false;
[1783]159
[1819]160    /**
161     * <p>
162     * Constructor. Creates a new AndroidLogParser.
163     * </p>
164     */
165    public AndroidLogParser() {
166        sequences = new LinkedList<List<Event>>();
167        currentSequence = null;
168    }
[1783]169
[1819]170    // TODO create a constructor which creates a new AndroidLogParser with a
171    // specific event filter.
[1783]172
[1819]173    /**
174     * <p>
175     * Parses a log file written by the JFCMonitor and creates a collection of event sequences.
176     * </p>
177     *
178     * @param filename
179     *            name and path of the log file
180     */
181    public void parseFile(String filename) {
182        if (filename == null) {
183            throw new IllegalArgumentException("filename must not be null");
184        }
[1783]185
[1819]186        parseFile(new File(filename));
187    }
[1783]188
[1819]189    /**
190     * <p>
191     * Parses a log file written by the JFCMonitor and creates a collection of event sequences.
192     * </p>
193     *
194     * @param file
195     *            name and path of the log file
196     */
197    public void parseFile(File file) {
198        if (file == null) {
199            throw new IllegalArgumentException("file must not be null");
200        }
[1783]201
[1819]202        SAXParserFactory spf = SAXParserFactory.newInstance();
203        // set true to validate that the file is well defined
204        spf.setValidating(true);
[1797]205
[1819]206        SAXParser saxParser = null;
207        InputSource inputSource = null;
208        try {
209            saxParser = spf.newSAXParser();
210            inputSource =
211                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
212        }
213        catch (UnsupportedEncodingException e) {
214            Console.printerr("Error parsing file + " + file.getName());
215            Console.logException(e);
216            return;
217        }
218        catch (ParserConfigurationException e) {
219            Console.printerr("Error parsing file + " + file.getName());
220            Console.logException(e);
221            return;
222        }
223        catch (SAXException e) {
224            Console.printerr("Error parsing file + " + file.getName());
225            Console.logException(e);
226            return;
227        }
228        catch (FileNotFoundException e) {
229            Console.printerr("Error parsing file + " + file.getName());
230            Console.logException(e);
231            return;
232        }
[1783]233
[1819]234        if (inputSource != null) {
235            inputSource.setSystemId("file://" + file.getAbsolutePath());
236            try {
237                // called a second time to be sure that no error happens
238                if (saxParser == null) {
239                    throw new RuntimeException("SAXParser creation failed");
240                }
241                saxParser.parse(inputSource, this);
242            }
243            catch (SAXParseException e) {
244                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
245                    ", column " + e.getColumnNumber() + ".");
246                Console.logException(e);
247                return;
248            }
249            catch (SAXException e) {
250                Console.printerr("Error parsing file + " + file.getName());
251                Console.logException(e);
252                return;
253            }
254            catch (IOException e) {
255                Console.printerr("Error parsing file + " + file.getName());
256                Console.logException(e);
257                return;
258            }
259        }
260    }
[1783]261
[1819]262    /**
263     * <p>
264     * Returns the collection of event sequences that is obtained from parsing log files.
265     * </p>
266     *
267     * @return collection of event sequences
268     */
269    public Collection<List<Event>> getSequences() {
270        return sequences;
271    }
[1783]272
[1819]273    /**
274     * <p>
275     * Returns the GUI model that is obtained from parsing log files.
276     * </p>
277     *
278     * @return GUIModel
279     */
280    public GUIModel getGuiModel() {
281        return currentGUIElementTree.getGUIModel();
282    }
[1803]283
[1819]284    /*
285     * (non-Javadoc)
286     *
287     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
288     * java.lang.String, org.xml.sax.Attributes)
289     */
290    public void startElement(String uri, String localName, String qName, Attributes atts)
291        throws SAXException
292    {
293        showSteps("start element: " + qName, true);
294        if (qName.equals("sessions")) {
295            currentSequence = new LinkedList<Event>();
296            if (currentGUIElementTree == null) {
297                currentGUIElementTree = new GUIElementTree<Long>();
298            }
[1783]299
[1819]300        }
[1803]301
[1819]302        if (qName.equals("component")) {
303            currentGUIElementHash = Long.parseLong(atts.getValue("hash"));
304            currentGUIElementSpec = new ANDROIDGUIElementSpec();
[1869]305            currentGUIElementSpec.setElementHash((int) currentGUIElementHash.longValue());
[1783]306
[1819]307        }
308        else if (qName.equals("event")) {
309            currentEventId = atts.getValue("id");
310            currentEventParameters = new HashMap<String, String>();
311
312        }
313        else if (qName.equals("param")) {
314            if (currentGUIElementHash != null) {
315                if ("class".equals(atts.getValue("name"))) {
316                    currentGUIElementSpec.setType(atts.getValue("value"));
317                }
[1869]318                else if ("title".equals(atts.getValue("name"))) {
319                    currentGUIElementSpec.setName(atts.getValue("value"));
320                }
[1819]321                else if ("path".equals(atts.getValue("name"))) {
322                    currentGUIElementSpec.setPath(atts.getValue("value"));
323                }
324                else if ("id".equals(atts.getValue("name"))) {
325                    currentGUIElementSpec.setIndex(Integer.parseInt(atts.getValue("value")));
326                }
327                else if ("parent".equals(atts.getValue("name"))) {
328                    currentParentHash = Long.parseLong(atts.getValue("value"));
329                }
[1880]330                else if ("position".equals(atts.getValue("name"))) {
331                    currentGUIElementSpec.setElementPosition(Integer.parseInt(atts.getValue("value")));
332                }
[1819]333            }
334            else if (currentEventId != null) {
335                if ("source".equals(atts.getValue("name"))) {
336                    currentEventSource = Long.parseLong(atts.getValue("value"));
337                }
338                if ("timestamp".equals(atts.getValue("name"))) {
339                    currentEventTimestamp = Long.parseLong(atts.getValue("value"));
340                }
341                currentEventParameters.put(atts.getValue("name"), atts.getValue("value"));
342            }
343        }
344        else if (qName.equals("ancestor")) {
[1815]345            currentTypeHierarchy.add(atts.getValue("name"));
346        }
347        else if (qName.equals("ancestors")) {
348            currentTypeHierarchy = new LinkedList<String>();
349        }
[1819]350    }
[1783]351
[1819]352    /*
353     * (non-Javadoc)
354     *
355     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
356     * java.lang.String)
357     */
358    @Override
359    public void endElement(String uri, String localName, String qName) throws SAXException {
[1803]360
[1819]361        showSteps("end element: " + qName, true);
[1803]362
[1819]363        if (qName.equals("sessions")) {
364            if (currentSequence != null) {
365                sequences.add(currentSequence);
366            }
367        }
368        else if (qName.equals("ancestors")) {
[1815]369            currentGUIElementSpec.setTypeHierarchy(currentTypeHierarchy);
370        }
[1819]371        else if (qName.equals("component") && currentGUIElementHash != null) {
372            try {
373                currentGUIElementTree.add(currentGUIElementHash, currentParentHash,
374                                          currentGUIElementSpec);
375            }
376            catch (GUIModelException e) {
377                throw new SAXException("could not handle GUI element with hash " +
378                    currentGUIElementHash + ": " + e.getMessage(), e);
379            }
380            currentGUIElementHash = null;
381            currentParentHash = null;
382            currentTypeHierarchy = null;
383        }
384        else if (currentEventId != null && qName.equals("event")) {
[1803]385
[1819]386            IGUIElement currentGUIElement;
387            currentGUIElement = currentGUIElementTree.find(currentEventSource);
388            Event event;
[1783]389
[1819]390            // up to now only onClick events are implemented and each
391            // onclick event is processed as a mouse click
392            if (currentGUIElement == null) {
[1803]393
[1819]394            }
395            else {
[1803]396
[1819]397                event =
398                    new Event(instantiateInteraction(currentEventId, currentEventParameters),
399                              currentGUIElement);
400                ANDROIDGUIElement currentEventTarget = (ANDROIDGUIElement) event.getTarget();
401                currentEventTarget.markUsed();
402                event.setTimestamp(currentEventTimestamp);
403                currentSequence.add(event);
404            }
405            currentEventParameters = null;
406            currentEventId = null;
407            currentEventTimestamp = -1l;
408        }
409    }
[1783]410
[1819]411    /**
412     * <p>
413     * depending on the event id and the event parameters, this method instantiates the concrete
414     * interaction, that took place, i.e. the event type
415     * </p>
416     *
417     * @param eventId
418     *            the id of the event
419     * @param eventParameters
420     *            the parameters provided for the event
421     *
422     * @return as described
423     *
424     * @throws SAXException
425     *             thrown if the provided event id is unknown
426     */
427    private IInteraction instantiateInteraction(String event, Map<String, String> eventParameters)
428        throws SAXException
429    {
[1803]430
[1819]431        switch (event)
432        {
433            case "onClick":
[1797]434
[1819]435                float x = Float.parseFloat(currentEventParameters.get("X"));
436                float y = Float.parseFloat(currentEventParameters.get("Y"));
[1797]437
[1819]438                return new TouchSingle(x, y);
[1803]439
[1819]440            case "text":
441                return new CharacterTyped(currentEventParameters.get("message"));
[1803]442
[1819]443            default:
444                throw new SAXException("unhandled event id " + event);
445        }
[1803]446
[1819]447    }
448
449    private void showSteps(String message, Boolean ln) {
450        if (showSteps) {
451            if (ln) {
452                Console.traceln(Level.INFO, message);
453            }
454            else {
455                Console.trace(Level.INFO, message);
456            }
457
458        }
459    }
460
461    public void setShowSteps(Boolean showSteps) {
462        this.showSteps = showSteps;
463    }
464
[1782]465}
Note: See TracBrowser for help on using the repository browser.