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
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.android;
16
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;
28import java.util.logging.Level;
29
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;
41import de.ugoe.cs.autoquest.eventcore.gui.CharacterTyped;
42import de.ugoe.cs.autoquest.eventcore.gui.IInteraction;
43import de.ugoe.cs.autoquest.eventcore.gui.TouchSingle;
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;
48import de.ugoe.cs.autoquest.plugin.android.guimodel.ANDROIDGUIElement;
49import de.ugoe.cs.autoquest.plugin.android.guimodel.ANDROIDGUIElementSpec;
50import de.ugoe.cs.util.console.Console;
51
52/**
53 * <p>
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.
56 * </p>
57 *
58 * @author Florian Unger
59 * @version 1.0
60 */
61public class AndroidLogParser extends DefaultHandler {
62
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;
75
76    /**
77     * <p>
78     * Internal handle to the parameters of the event currently being parsed.
79     * </p>
80     */
81    private Map<String, String> currentEventParameters;
82
83    /**
84     * <p>
85     * Internal handle to the source of the event that is currently being parsed.
86     * </p>
87     */
88    private Long currentEventSource;
89
90    /**
91     * <p>
92     * Internal handle to the timestamp of the event that is currently being parsed.
93     */
94    private Long currentEventTimestamp = -1l;
95
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;
103
104    /**
105     * <p>
106     * Internal handle to the parsed GUI structure, stored in a GUIElementTree
107     * </p>
108     */
109    private GUIElementTree<Long> currentGUIElementTree;
110
111    /**
112     * <p>
113     * Internal handle to the specification currently parsed for a GUI element
114     * </p>
115     */
116    private ANDROIDGUIElementSpec currentGUIElementSpec;
117
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;
125
126    /**
127     * <p>
128     * Internal handle to the event sequence that is currently being parsed.
129     * </p>
130     */
131    private List<Event> currentSequence;
132
133    /**
134     * <p>
135     * internal handle to the class ancestors
136     * </p>
137     */
138    private List<String> currentTypeHierarchy;
139
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;
147
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;
154
155    /**
156     * Show parsed elements to find out failures. 
157     */
158    private Boolean showSteps = false;
159
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    }
169
170    // TODO create a constructor which creates a new AndroidLogParser with a
171    // specific event filter.
172
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        }
185
186        parseFile(new File(filename));
187    }
188
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        }
201
202        SAXParserFactory spf = SAXParserFactory.newInstance();
203        // set true to validate that the file is well defined
204        spf.setValidating(true);
205
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        }
233
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    }
261
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    }
272
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    }
283
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            }
299
300        }
301
302        if (qName.equals("component")) {
303            currentGUIElementHash = Long.parseLong(atts.getValue("hash"));
304            currentGUIElementSpec = new ANDROIDGUIElementSpec();
305            currentGUIElementSpec.setElementHash((int) currentGUIElementHash.longValue());
306
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                }
318                else if ("title".equals(atts.getValue("name"))) {
319                    currentGUIElementSpec.setName(atts.getValue("value"));
320                }
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                }
330                else if ("position".equals(atts.getValue("name"))) {
331                    currentGUIElementSpec.setElementPosition(Integer.parseInt(atts.getValue("value")));
332                }
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")) {
345            currentTypeHierarchy.add(atts.getValue("name"));
346        }
347        else if (qName.equals("ancestors")) {
348            currentTypeHierarchy = new LinkedList<String>();
349        }
350    }
351
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 {
360
361        showSteps("end element: " + qName, true);
362
363        if (qName.equals("sessions")) {
364            if (currentSequence != null) {
365                sequences.add(currentSequence);
366            }
367        }
368        else if (qName.equals("ancestors")) {
369            currentGUIElementSpec.setTypeHierarchy(currentTypeHierarchy);
370        }
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")) {
385
386            IGUIElement currentGUIElement;
387            currentGUIElement = currentGUIElementTree.find(currentEventSource);
388            Event event;
389
390            // up to now only onClick events are implemented and each
391            // onclick event is processed as a mouse click
392            if (currentGUIElement == null) {
393
394            }
395            else {
396
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    }
410
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    {
430
431        switch (event)
432        {
433            case "onClick":
434
435                float x = Float.parseFloat(currentEventParameters.get("X"));
436                float y = Float.parseFloat(currentEventParameters.get("Y"));
437
438                return new TouchSingle(x, y);
439
440            case "text":
441                return new CharacterTyped(currentEventParameters.get("message"));
442
443            default:
444                throw new SAXException("unhandled event id " + event);
445        }
446
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
465}
Note: See TracBrowser for help on using the repository browser.