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

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