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

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