source: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/AbstractDefaultLogParser.java @ 1247

Last change on this file since 1247 was 1247, checked in by pharms, 11 years ago
  • extended, improved and corrected HTML logging for HTML 5
File size: 14.8 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.html;
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.Locator;
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.guimodel.GUIElementTree;
42import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
43import de.ugoe.cs.util.console.Console;
44
45/**
46 * <p>
47 * This class provides the functionality to parse XML log files generated monitors of
48 * AutoQUEST. The result of parsing a file is a collection of event sequences and a GUI model.
49 * This class must be extended by implementing a subclass and the abstract method to complete
50 * the implementation.
51 * </p>
52 *
53 * @author Patrick Harms
54 * @version 1.0
55 *
56 */
57public abstract class AbstractDefaultLogParser extends DefaultHandler {
58   
59    /**
60     * <p>
61     * Collection of event sequences that is contained in the parsed log file.
62     * </p>
63     */
64    private Collection<List<Event>> sequences;
65
66    /**
67     * <p>
68     * Internal handle to the parsed GUI structure, stored in a GUIElementTree
69     * </p>
70     */
71    private GUIElementTree<String> guiElementTree;
72
73    /**
74     * <p>
75     * Id of the GUI element currently being parsed.
76     * </p>
77     */
78    private String currentGUIElementId;
79
80    /**
81     * <p>
82     * the buffer for GUI elements already parsed but not processed yet (because e.g. the parent
83     * GUI element has not been parsed yet)
84     * </p>
85     */
86    private List<BufferEntry> guiElementBuffer;
87
88    /**
89     * <p>
90     * Internal handle to type of the event currently being parsed.
91     * </p>
92     */
93    private String currentEventType;
94
95    /**
96     * <p>
97     * the buffer for events already parsed but not processed yet (because e.g. the target
98     * GUI element has not been parsed yet)
99     * </p>
100     */
101    private List<BufferEntry> eventBuffer;
102
103    /**
104     * <p>
105     * Internal handle to the parameters of the event currently being entity.
106     * </p>
107     */
108    private Map<String, String> currentParameters;
109
110    /**
111     * <p>
112     * Internal handle to the sequence currently being parsed.
113     * </p>
114     */
115    private List<Event> currentSequence;
116
117    /**
118     * <p>
119     * the handle to the locator for correctly throwing exceptions with location information
120     * </p>
121     */
122    private Locator locator;
123
124    /**
125     * <p>
126     * Constructor. Creates a new logParser.
127     * </p>
128     */
129    public AbstractDefaultLogParser() {
130        sequences = new LinkedList<List<Event>>();
131        guiElementTree = new GUIElementTree<String>();
132        guiElementBuffer = new LinkedList<BufferEntry>();
133        eventBuffer = new LinkedList<BufferEntry>();
134        currentSequence = new LinkedList<Event>();
135    }
136
137    /**
138     * <p>
139     * Parses a log file written by the HTMLMonitor and creates a collection of event sequences.
140     * </p>
141     *
142     * @param filename
143     *            name and path of the log file
144     *
145     * @throws SAXException in the case, the file could not be parsed
146     */
147    public void parseFile(String filename) throws SAXException {
148        if (filename == null) {
149            throw new IllegalArgumentException("filename must not be null");
150        }
151
152        parseFile(new File(filename));
153    }
154
155    /**
156     * <p>
157     * Parses a log file written by the HTMLMonitor and creates a collection of event sequences.
158     * </p>
159     *
160     * @param file
161     *            file to be parsed
162     *
163     * @throws SAXException in the case, the file could not be parsed
164     */
165    public void parseFile(File file) throws SAXException {
166        if (file == null) {
167            throw new IllegalArgumentException("file must not be null");
168        }
169        SAXParserFactory spf = SAXParserFactory.newInstance();
170        spf.setValidating(true);
171        SAXParser saxParser = null;
172        InputSource inputSource = null;
173        try {
174            saxParser = spf.newSAXParser();
175            inputSource =
176                new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
177        }
178        catch (UnsupportedEncodingException e) {
179            Console.printerrln("Error parsing file " + file.getName());
180            Console.logException(e);
181            throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
182        }
183        catch (ParserConfigurationException e) {
184            Console.printerrln("Error parsing file " + file.getName());
185            Console.logException(e);
186            throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
187        }
188        catch (FileNotFoundException e) {
189            Console.printerrln("Error parsing file " + file.getName());
190            Console.logException(e);
191            throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
192        }
193       
194        if (inputSource != null) {
195            inputSource.setSystemId("file://" + file.getAbsolutePath());
196            try {
197                if (saxParser == null) {
198                    throw new RuntimeException("SaxParser creation failed");
199                }
200                saxParser.parse(inputSource, this);
201            }
202            catch (IOException e) {
203                Console.printerrln("Error parsing file " + file.getName());
204                Console.logException(e);
205                throw new SAXParseException("Error parsing file " + file.getName(), locator, e);
206            }
207            catch (SAXParseException e) {
208                if ("XML document structures must start and end within the same entity.".equals
209                    (e.getMessage()))
210                {
211                    // this only denotes, that the final session tag is missing, because the
212                    // file wasn't completed. Ignore this but complete the session
213                    eventBuffer.add(new BufferEntry("sessionSwitch", null));
214                    processEvents();
215                }
216                else {
217                    throw e;
218                }
219            }
220        }
221    }
222
223    /**
224     * <p>
225     * Returns a collection of event sequences that was obtained from parsing log files.
226     * </p>
227     *
228     * @return
229     */
230    public Collection<List<Event>> getSequences() {
231        return sequences;
232    }
233
234    /**
235     * <p>
236     * Returns the GUI model that is obtained from parsing log files.
237     * </p>
238     *
239     * @return GUIModel
240     */
241    public GUIModel getGuiModel() {
242        return guiElementTree.getGUIModel();
243    }
244
245    /* (non-Javadoc)
246     * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
247     */
248    @Override
249    public void setDocumentLocator(Locator locator) {
250        this.locator = locator;
251    }
252
253    /*
254     * (non-Javadoc)
255     *
256     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
257     * java.lang.String, org.xml.sax.Attributes)
258     */
259    @Override
260    public void startElement(String uri, String localName, String qName, Attributes atts)
261        throws SAXException
262    {
263        if (qName.equals("session")) {
264            // do nothing
265        }
266        else if (qName.equals("component")) {
267            currentGUIElementId = atts.getValue("id");
268            currentParameters = new HashMap<String, String>();
269        }
270        else if (qName.equals("event")) {
271            currentEventType = atts.getValue("type");
272            currentParameters = new HashMap<String, String>();
273        }
274        else if (qName.equals("param")) {
275            String paramName = atts.getValue("name");
276            if ((currentGUIElementId != null) || (currentEventType != null)) {
277                currentParameters.put(paramName, atts.getValue("value"));
278            }
279            else {
280                throw new SAXException("param tag found where it should not be.");
281            }
282        }
283        else {
284            throw new SAXException("unknown tag found: " + qName);
285        }
286
287    }
288
289    /*
290     * (non-Javadoc)
291     *
292     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
293     * java.lang.String)
294     */
295    @Override
296    public void endElement(String uri, String localName, String qName) throws SAXException {
297        if (qName.equals("session") && (eventBuffer != null)) {
298            eventBuffer.add(new BufferEntry("sessionSwitch", null));
299            processEvents();
300        }
301        else if (qName.equals("component") && (currentGUIElementId != null)) {
302            guiElementBuffer.add(new BufferEntry(currentGUIElementId, currentParameters));
303
304            processGUIElements();
305
306            currentGUIElementId = null;
307            currentParameters = null;
308        }
309        else if (qName.equals("event") && (currentEventType != null)) {
310            eventBuffer.add(new BufferEntry(currentEventType, currentParameters));
311
312            processEvents();
313
314            currentEventType = null;
315            currentParameters = null;
316        }
317    }
318
319    /**
320     * <p>
321     * called to handle parsed GUI elements
322     * </p>
323     *
324     * @param id         the id of the parsed GUI element
325     * @param parameters all parameters parsed for the GUI element
326     *
327     * @return true, if the GUI element could be handled. In this case this method is not called
328     *         again for the same GUI element. Otherwise the method is called later again. This
329     *         may be required, if a child GUI element is parsed before the parent GUI element
330     */
331    protected abstract boolean handleGUIElement(String id, Map<String, String> parameters)
332        throws SAXException;
333   
334    /**
335     * <p>
336     * called to handle parsed events
337     * </p>
338     *
339     * @param type       the type of the parsed event
340     * @param parameters the parameters of the parsed event
341     *
342     * @return true, if the event could be handled. In this case this method is not called
343     *         again for the same event. Otherwise the method is called later again. This
344     *         may be required, if e.g. the target of the event, i.e. the GUI element, is not yet
345     *         parsed
346     */
347    protected abstract boolean handleEvent(String type, Map<String, String> parameters)
348        throws SAXException;
349   
350    /**
351     * <p>
352     * returns the tree of parsed GUI elements, which consists of the ids of the GUI elements
353     * </p>
354     *
355     * @return as described
356     */
357    protected GUIElementTree<String> getGUIElementTree() {
358        return guiElementTree;
359    }
360
361    /**
362     * <p>
363     * adds an event to the currently parsed sequence of events
364     * </p>
365     *
366     * @param event
367     */
368    protected void addToSequence(Event event) {
369        currentSequence.add(event);
370    }
371
372    /**
373     * <p>
374     * this method internally processes GUI elements, that have been parsed but not processed yet.
375     * I.e., for such GUI elements, either the method {@link #handleGUIElement(String, Map)} has
376     * not been called yet, or it returned false for the previous calls. In this case, the method
377     * is called (again). Furthermore, the processing of events is initiated by a call to
378     * {@link #processEvents()}.
379     * </p>
380     */
381    private void processGUIElements() throws SAXException {
382        int processedElements = 0;
383        boolean processedElement;
384       
385        do {
386            processedElement = false;
387            // search for the next GUI element that can be processed
388            for (int i = 0; i < guiElementBuffer.size(); i++) {
389                BufferEntry entry = guiElementBuffer.get(i);
390                processedElement = handleGUIElement(entry.id, entry.parameters);
391                if (processedElement) {
392                    guiElementBuffer.remove(i);
393                    processedElements++;
394                    break;
395                }
396            }
397        }
398        while (processedElement);
399       
400        if (processedElements > 0) {
401            processEvents();
402        }
403    }
404
405    /**
406     * <p>
407     * this method internally processes events, that have been parsed but not processed yet.
408     * I.e., for such events, either the method {@link #handleEvent(String, Map)} has
409     * not been called yet, or it returned false for the previous calls. In this case, the method
410     * is called (again).
411     * </p>
412     */
413    private void processEvents() throws SAXException {
414        boolean processedEvent;
415       
416        do {
417            processedEvent = false;
418            // check, if the next event can be processed
419            if (eventBuffer.size() > 0) {
420                BufferEntry entry = eventBuffer.get(0);
421               
422                if ((entry != null) && (entry.id != null) && (entry.parameters != null)) {
423                    processedEvent = handleEvent(entry.id, entry.parameters);
424                    if (processedEvent) {
425                        eventBuffer.remove(0);
426                    }
427                }
428                else {
429                    // the entry signals a session switch. Close the current session and start the
430                    // next one
431                    if (currentSequence.size() > 0) {
432                        sequences.add(currentSequence);
433                        currentSequence = new LinkedList<Event>();
434                    }
435                    eventBuffer.remove(0);
436                    processedEvent = true;
437                }
438            }
439        }
440        while (processedEvent);
441    }
442
443    /**
444     * <p>
445     * This class is used internally for storing events and GUI elements in lists.
446     * </p>
447     */
448    private static class BufferEntry {
449       
450        /** */
451        private String id;
452       
453        /** */
454        private Map<String, String> parameters;
455       
456        /**
457         *
458         */
459        private BufferEntry(String id, Map<String, String> parameters) {
460            this.id = id;
461            this.parameters = parameters;
462        }
463    }
464
465}
Note: See TracBrowser for help on using the repository browser.