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

Last change on this file since 1234 was 1234, checked in by pharms, 11 years ago
  • corrected log compression and log file restructuring
File size: 14.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.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.
213                }
214                else {
215                    throw e;
216                }
217            }
218        }
219    }
220
221    /**
222     * <p>
223     * Returns a collection of event sequences that was obtained from parsing log files.
224     * </p>
225     *
226     * @return
227     */
228    public Collection<List<Event>> getSequences() {
229        return sequences;
230    }
231
232    /**
233     * <p>
234     * Returns the GUI model that is obtained from parsing log files.
235     * </p>
236     *
237     * @return GUIModel
238     */
239    public GUIModel getGuiModel() {
240        return guiElementTree.getGUIModel();
241    }
242
243    /* (non-Javadoc)
244     * @see org.xml.sax.helpers.DefaultHandler#setDocumentLocator(org.xml.sax.Locator)
245     */
246    @Override
247    public void setDocumentLocator(Locator locator) {
248        this.locator = locator;
249    }
250
251    /*
252     * (non-Javadoc)
253     *
254     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
255     * java.lang.String, org.xml.sax.Attributes)
256     */
257    @Override
258    public void startElement(String uri, String localName, String qName, Attributes atts)
259        throws SAXException
260    {
261        if (qName.equals("session")) {
262            // do nothing
263        }
264        else if (qName.equals("component")) {
265            currentGUIElementId = atts.getValue("id");
266            currentParameters = new HashMap<String, String>();
267        }
268        else if (qName.equals("event")) {
269            currentEventType = atts.getValue("type");
270            currentParameters = new HashMap<String, String>();
271        }
272        else if (qName.equals("param")) {
273            String paramName = atts.getValue("name");
274            if ((currentGUIElementId != null) || (currentEventType != null)) {
275                currentParameters.put(paramName, atts.getValue("value"));
276            }
277            else {
278                throw new SAXException("param tag found where it should not be.");
279            }
280        }
281        else {
282            throw new SAXException("unknown tag found: " + qName);
283        }
284
285    }
286
287    /*
288     * (non-Javadoc)
289     *
290     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
291     * java.lang.String)
292     */
293    @Override
294    public void endElement(String uri, String localName, String qName) throws SAXException {
295        if (qName.equals("session") && (eventBuffer != null)) {
296            eventBuffer.add(new BufferEntry("sessionSwitch", null));
297            processEvents();
298        }
299        else if (qName.equals("component") && (currentGUIElementId != null)) {
300            guiElementBuffer.add(new BufferEntry(currentGUIElementId, currentParameters));
301
302            processGUIElements();
303
304            currentGUIElementId = null;
305            currentParameters = null;
306        }
307        else if (qName.equals("event") && (currentEventType != null)) {
308            eventBuffer.add(new BufferEntry(currentEventType, currentParameters));
309
310            processEvents();
311
312            currentEventType = null;
313            currentParameters = null;
314        }
315    }
316
317    /**
318     * <p>
319     * called to handle parsed GUI elements
320     * </p>
321     *
322     * @param id         the id of the parsed GUI element
323     * @param parameters all parameters parsed for the GUI element
324     *
325     * @return true, if the GUI element could be handled. In this case this method is not called
326     *         again for the same GUI element. Otherwise the method is called later again. This
327     *         may be required, if a child GUI element is parsed before the parent GUI element
328     */
329    protected abstract boolean handleGUIElement(String id, Map<String, String> parameters)
330        throws SAXException;
331   
332    /**
333     * <p>
334     * called to handle parsed events
335     * </p>
336     *
337     * @param type       the type of the parsed event
338     * @param parameters the parameters of the parsed event
339     *
340     * @return true, if the event could be handled. In this case this method is not called
341     *         again for the same event. Otherwise the method is called later again. This
342     *         may be required, if e.g. the target of the event, i.e. the GUI element, is not yet
343     *         parsed
344     */
345    protected abstract boolean handleEvent(String type, Map<String, String> parameters)
346        throws SAXException;
347   
348    /**
349     * <p>
350     * returns the tree of parsed GUI elements, which consists of the ids of the GUI elements
351     * </p>
352     *
353     * @return as described
354     */
355    protected GUIElementTree<String> getGUIElementTree() {
356        return guiElementTree;
357    }
358
359    /**
360     * <p>
361     * adds an event to the currently parsed sequence of events
362     * </p>
363     *
364     * @param event
365     */
366    protected void addToSequence(Event event) {
367        currentSequence.add(event);
368    }
369
370    /**
371     * <p>
372     * this method internally processes GUI elements, that have been parsed but not processed yet.
373     * I.e., for such GUI elements, either the method {@link #handleGUIElement(String, Map)} has
374     * not been called yet, or it returned false for the previous calls. In this case, the method
375     * is called (again). Furthermore, the processing of events is initiated by a call to
376     * {@link #processEvents()}.
377     * </p>
378     */
379    private void processGUIElements() throws SAXException {
380        int processedElements = 0;
381        boolean processedElement;
382       
383        do {
384            processedElement = false;
385            // search for the next GUI element that can be processed
386            for (int i = 0; i < guiElementBuffer.size(); i++) {
387                BufferEntry entry = guiElementBuffer.get(i);
388                processedElement = handleGUIElement(entry.id, entry.parameters);
389                if (processedElement) {
390                    guiElementBuffer.remove(i);
391                    processedElements++;
392                    break;
393                }
394            }
395        }
396        while (processedElement);
397       
398        if (processedElements > 0) {
399            processEvents();
400        }
401    }
402
403    /**
404     * <p>
405     * this method internally processes events, that have been parsed but not processed yet.
406     * I.e., for such events, either the method {@link #handleEvent(String, Map)} has
407     * not been called yet, or it returned false for the previous calls. In this case, the method
408     * is called (again).
409     * </p>
410     */
411    private void processEvents() throws SAXException {
412        boolean processedEvent;
413       
414        do {
415            processedEvent = false;
416            // check, if the next event can be processed
417            if (eventBuffer.size() > 0) {
418                BufferEntry entry = eventBuffer.get(0);
419               
420                if ((entry != null) && (entry.id != null) && (entry.parameters != null)) {
421                    processedEvent = handleEvent(entry.id, entry.parameters);
422                    if (processedEvent) {
423                        eventBuffer.remove(0);
424                    }
425                }
426                else {
427                    // the entry signals a session switch. Close the current session and start the
428                    // next one
429                    if (currentSequence.size() > 0) {
430                        sequences.add(currentSequence);
431                        currentSequence = new LinkedList<Event>();
432                    }
433                    eventBuffer.remove(0);
434                    processedEvent = true;
435                }
436            }
437        }
438        while (processedEvent);
439    }
440
441    /**
442     * <p>
443     * This class is used internally for storing events and GUI elements in lists.
444     * </p>
445     */
446    private static class BufferEntry {
447       
448        /** */
449        private String id;
450       
451        /** */
452        private Map<String, String> parameters;
453       
454        /**
455         *
456         */
457        private BufferEntry(String id, Map<String, String> parameters) {
458            this.id = id;
459            this.parameters = parameters;
460        }
461    }
462
463}
Note: See TracBrowser for help on using the repository browser.