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

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