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

Last change on this file since 1783 was 1783, checked in by funger, 10 years ago

Parser for onClick events only. Using MouseClick? to simulate onClick events.

  • Property svn:mime-type set to text/plain
File size: 11.5 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;
28
29import javax.xml.parsers.ParserConfigurationException;
30import javax.xml.parsers.SAXParser;
31import javax.xml.parsers.SAXParserFactory;
32
33import org.xml.sax.Attributes;
34import org.xml.sax.InputSource;
35import org.xml.sax.SAXException;
36import org.xml.sax.SAXParseException;
37import org.xml.sax.helpers.DefaultHandler;
38
39import de.ugoe.cs.autoquest.eventcore.Event;
40import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction;
41import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
42import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
43import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
44import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
45import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
46import de.ugoe.cs.autoquest.plugin.android.guimodel.AndroidGUIElementSpec;
47import de.ugoe.cs.util.console.Console;
48
49/**
50 * <p>
51 * This class provides functionality to parse XML log files generated by the
52 * AndroidMonitor of autoquest. The result of parsing a file is a collection of
53 * event sequences.
54 * </p>
55 *
56 * @author Florian Unger
57 * @version 1.0
58 */
59public class AndroidLogParser extends DefaultHandler {
60
61        /*
62         * (non-Javadoc)
63         *
64         * int java.lang.Object.hashCode() is used in the Androidmonitor Long is
65         * used to internally handle and compare the number. e.g.
66         * currentGUIElementHash != null
67         */
68        /**
69         * <p>
70         * Internal handle to the id of the event that is currently being parsed.
71         * </p>
72         */
73        private String currentEventId;
74
75        /**
76         * <p>
77         * Internal handle to the parameters of the event currently being parsed.
78         * </p>
79         */
80        private Map<String, String> currentEventParameters;
81
82        /**
83         * <p>
84         * Internal handle to the source of the event that is currently being
85         * 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
93         * parsed.
94         */
95        private Long currentEventTimestamp = -1l;
96
97        /**
98         *
99         * <p>
100         * Internal handle to the hashcode of the GUI element, that is currently
101         * parsed.
102         * </p>
103         */
104        private Long currentGUIElementHash;
105
106        /**
107         * <p>
108         * internal handle to the parsed GUI structure, stored in a GUIElementTree
109         * </p>
110         */
111        private GUIElementTree<Long> currentGUIElementTree;
112
113        /**
114         * <p>
115         * internal handle to the specification currently parsed for a GUI element
116         * </p>
117         */
118        private AndroidGUIElementSpec currentGUIElementSpec;
119
120        /**
121         *
122         * <p>
123         * Internal handle to the hashcode of the parent of the GUI element, that is
124         * currently parsed.
125         * </p>
126         */
127        private Long currentParentHash;
128
129        /**
130         * <p>
131         * Internal handle to the event sequence that is currently being parsed.
132         * </p>
133         */
134        private List<Event> currentSequence;
135
136        /**
137         * <p>
138         * Internal handle to the event sequence that is currently being parsed.
139         * </p>
140         */
141        // private List<Event> currentSequence;
142
143        /**
144         * <p>
145         * Map that holds events that had no registered target GUI element during
146         * parsing. Keys are the IDs of the unregistered targets.
147         * </p>
148         */
149        private Map<Long, List<Event>> eventsWithoutTargets;
150
151        /**
152         * <p>
153         * Collection of event sequences that is contained in the log file, which is
154         * parsed.
155         * </p>
156         */
157        private Collection<List<Event>> sequences;
158
159        /**
160         * <p>
161         * Constructor. Creates a new AndroidLogParser.
162         * </p>
163         */
164        public AndroidLogParser() {
165                sequences = new LinkedList<List<Event>>();
166                // currentSequence = null;
167        }
168
169        // TODO create a constructor which creates a new AndroidLogParser with a
170        // specific event filter.
171
172        /**
173         * <p>
174         * Parses a log file written by the JFCMonitor and creates a collection of
175         * 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
192         * event sequences.
193         * </p>
194         *
195         * @param file
196         *            name and path of the log file
197         */
198        public void parseFile(File file) {
199                if (file == null) {
200                        throw new IllegalArgumentException("file must not be null");
201                }
202
203                SAXParserFactory spf = SAXParserFactory.newInstance();
204                // set true to validate that the file is well defined
205                spf.setValidating(true);
206
207                SAXParser saxParser = null;
208                InputSource inputSource = null;
209                try {
210                        saxParser = spf.newSAXParser();
211                        inputSource = new InputSource(new InputStreamReader(
212                                        new FileInputStream(file), "UTF-8"));
213                } catch (UnsupportedEncodingException e) {
214                        Console.printerr("Error parsing file + " + file.getName());
215                        Console.logException(e);
216                        return;
217                } catch (ParserConfigurationException e) {
218                        Console.printerr("Error parsing file + " + file.getName());
219                        Console.logException(e);
220                        return;
221                } catch (SAXException e) {
222                        Console.printerr("Error parsing file + " + file.getName());
223                        Console.logException(e);
224                        return;
225                } catch (FileNotFoundException e) {
226                        Console.printerr("Error parsing file + " + file.getName());
227                        Console.logException(e);
228                        return;
229                }
230                if (inputSource != null) {
231                        inputSource.setSystemId("file://" + file.getAbsolutePath());
232                        try {
233                                // called a second time to be sure that no error happens
234                                if (saxParser == null) {
235                                        throw new RuntimeException("SAXParser creation failed");
236                                }
237                                saxParser.parse(inputSource, this);
238                        } catch (SAXParseException e) {
239                                Console.printerrln("Failure parsing file in line "
240                                                + e.getLineNumber() + ", column " + e.getColumnNumber()
241                                                + ".");
242                                Console.logException(e);
243                                return;
244                        } catch (SAXException e) {
245                                Console.printerr("Error parsing file + " + file.getName());
246                                Console.logException(e);
247                                return;
248                        } catch (IOException e) {
249                                Console.printerr("Error parsing file + " + file.getName());
250                                Console.logException(e);
251                                return;
252                        }
253                }
254                if (!eventsWithoutTargets.isEmpty()) {
255                        Console.printerr("Some events reference GUI elements that are not part of logfile. "
256                                        + "These events have been parsed without target.");
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                if (qName.equals("sessions")) {
292                        // currentSequence = new LinkedList<Event>(); Up to know it is
293                        // necessary to handle different sessions. All components are known
294                        // before an event occurs.
295                        if (currentGUIElementTree == null)
296                                currentGUIElementTree = new GUIElementTree<Long>();
297                }
298
299                if (qName.equals("component")) {
300                        currentGUIElementHash = Long.parseLong(atts.getValue("hash"));
301                        currentGUIElementSpec = new AndroidGUIElementSpec();
302                        currentGUIElementSpec.setHashCode((int) currentGUIElementHash
303                                        .longValue());
304                } else if (qName.equals("event")) {
305                        currentEventId = atts.getValue("id");
306                        currentEventParameters = new HashMap<String, String>();
307
308                } else if (qName.equals("param")) {
309                        if (currentGUIElementHash != null) {
310                                if ("class".equals(atts.getValue("name"))) {
311                                        currentGUIElementSpec.setType(atts.getValue("value"));
312                                } else if ("path".equals(atts.getValue("name"))) {
313                                        currentGUIElementSpec.setPath(atts.getValue("value"));
314                                } else if ("id".equals(atts.getValue("name"))) {
315                                        currentGUIElementSpec.setIndex(Integer.parseInt(atts
316                                                        .getValue("value")));
317                                } else if ("parent".equals(atts.getValue("name"))) {
318                                        currentParentHash = Long.parseLong(atts.getValue("value"));
319                                }
320                        } else if (currentEventId != null) {
321                                if ("source".equals(atts.getValue("name"))) {
322                                        currentEventSource = Long.parseLong(atts.getValue("value"));
323                                }
324                                if ("timestamp".equals(atts.getValue("name"))) {
325                                        currentEventTimestamp = Long.parseLong(atts
326                                                        .getValue("value"));
327                                }
328                                currentEventParameters.put(atts.getValue("name"),
329                                                atts.getValue("value"));
330                        }
331                }
332
333                // TODO add hierarchy information if available -> Up to know gathering
334                // this information leads to an out of memory exception in the
335                // androidmonitor @see: AndroidmonitorLogFile#addComponent
336        }
337
338        /*
339         * (non-Javadoc)
340         *
341         * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String,
342         * java.lang.String, java.lang.String)
343         */
344        @Override
345        public void endElement(String uri, String localName, String qName)
346                        throws SAXException {
347                if (qName.equals("component") && currentGUIElementHash != null) {
348                        try {
349                                currentGUIElementTree.add(currentGUIElementHash,
350                                                currentParentHash, currentGUIElementSpec);
351                        } catch (GUIModelException e) {
352                                throw new SAXException(
353                                                "could not handle GUI element with hash "
354                                                                + currentGUIElementHash + ": " + e.getMessage(),
355                                                e);
356                        }
357                        currentGUIElementHash = null;
358                        currentParentHash = null;
359                } else if (currentEventId != null) {
360                        if (qName.equals("event")) {
361                                IGUIElement currentGUIElement;
362                                currentGUIElement = currentGUIElementTree
363                                                .find(currentEventSource);
364                                Event event;
365
366                                // up to now only onClick events are implemented and each
367                                // onclick event is processed as a mouse click
368                                int x = Integer.parseInt(currentEventParameters.get("X"));
369                                int y = Integer.parseInt(currentEventParameters.get("Y"));
370                                MouseButtonInteraction.Button button = null;
371                                button = MouseButtonInteraction.Button.LEFT;
372
373                                // maybe it would be necessary in the future to check weather
374                                // the GUI element exits.
375                                event = new Event(new MouseClick(button, x, y),
376                                                currentGUIElement);
377
378                                event.setTimestamp(currentEventTimestamp);
379                                currentSequence.add(event);
380
381                                currentEventParameters = null;
382                                currentEventId = null;
383                                currentEventTimestamp = -1l;
384                        }
385                } else if (qName.equals("sessions")) {
386                        if (currentSequence != null) {
387                                sequences.add(currentSequence);
388                        }
389                }
390        }
391
392}
Note: See TracBrowser for help on using the repository browser.