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

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