source: trunk/autoquest-plugin-mfc/src/main/java/de/ugoe/cs/autoquest/plugin/mfc/MFCLogParser.java @ 1006

Last change on this file since 1006 was 1006, checked in by fglaser, 12 years ago
  • MFCLogParser and its components updated to work with generalized GUIElementTree
  • NOTE THAT GUIElementTree ADDS ALL COMPONENTS TO GUIModel NOT ONLY THE USED ONES (compare MFCWindowTree).
  • guimapping-MFC-Dummy.txt added to cover newly discovered classes (needs to be updated).
File size: 12.6 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.mfc;
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;
28import java.util.SortedMap;
29import java.util.TreeMap;
30import java.util.logging.Level;
31
32import javax.xml.parsers.ParserConfigurationException;
33import javax.xml.parsers.SAXParser;
34import javax.xml.parsers.SAXParserFactory;
35
36import org.xml.sax.Attributes;
37import org.xml.sax.InputSource;
38import org.xml.sax.SAXException;
39import org.xml.sax.SAXParseException;
40import org.xml.sax.helpers.DefaultHandler;
41
42import de.ugoe.cs.autoquest.eventcore.Event;
43import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
44import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
45import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessage;
46import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessageType;
47import de.ugoe.cs.autoquest.plugin.mfc.guimodel.MFCGUIElement;
48import de.ugoe.cs.util.StringTools;
49import de.ugoe.cs.util.console.Console;
50
51/**
52 * <p>
53 * This class provides functionality to parse XML log files generated by the MFCUsageMonitor of
54 * EventBench. The result of parsing a file is a collection of event sequences. It uses the
55 * {@link SequenceSplitter} and the {@link EventGenerator} as well as custom defined
56 * {@link MessageHandler} for the parsing.
57 * </p>
58 *
59 * @author Steffen Herbold
60 * @author Fabian Glaser
61 * @version 1.0
62 */
63public class MFCLogParser extends DefaultHandler {
64
65    /**
66     * <p>
67     * If a custom message handler is used, this field contains its handle. Otherwise this field is
68     * {@code null}.
69     * </p>
70     */
71    private MessageHandler currentHandler;
72
73    /**
74     * <p>
75     * internal handle to the gui element tree
76     * </p>
77     */
78    private GUIElementTree guiElementTree;
79
80    /**
81     * <p>
82     * the type of the currently parsed message
83     * </p>
84     */
85    private WindowsMessageType currentMessageType;
86   
87    /**
88     * <p>
89     * the parameters of the currently parsed message
90     * </p>
91     */
92    private Map<String, Object> currentMessageParameters = new HashMap<String, Object>();
93   
94    /**
95     * <p>
96     * {@link SequenceSplitter} instance used by the {@link MFCLogParser}.
97     * </p>
98     */
99    private SequenceSplitter sequenceSplitter;
100
101    /**
102     * <p>
103     * Collection of message sequences that is contained in the log file, which is parsed.
104     * </p>
105     */
106    private Collection<List<Event>> sequences;
107
108    /**
109     * <p>
110     * Debugging variable that allows the analysis which message type occurs how often in the log
111     * file. Can be used to enhance the message filter.
112     * </p>
113     */
114    private SortedMap<WindowsMessageType, Integer> typeCounter;
115
116    /**
117     * <p>
118     * Debugging variable that enables the counting of the occurrences of each message. Used in
119     * combination with {@link #typeCounter}.
120     * </p>
121     */
122    private boolean countMessageOccurences;
123
124    /**
125     * <p>
126     * Constructor. Creates a new LogParser that does not count message occurrences.
127     * </p>
128     */
129    public MFCLogParser() {
130        this(false);
131    }
132
133    /**
134     * <p>
135     * Constructor. Creates a new LogParser.
136     * </p>
137     *
138     * @param countMessageOccurences
139     *            if true, the occurrences of each message type in the log is counted.
140     */
141    public MFCLogParser(boolean countMessageOccurences) {
142        sequences = new LinkedList<List<Event>>();
143        currentHandler = null;
144        this.countMessageOccurences = countMessageOccurences;
145        if (countMessageOccurences) {
146            typeCounter = new TreeMap<WindowsMessageType, Integer>();
147        }
148    }
149
150    /**
151     * <p>
152     * Parses a log file written by the MFCMonitor and creates a collection of event sequences.
153     * </p>
154     *
155     * @param filename
156     *            name and path of the log file
157     */
158    public void parseFile(String filename) {
159        if (filename == null) {
160            throw new IllegalArgumentException("filename must not be null");
161        }
162
163        parseFile(new File(filename));
164    }
165
166    /**
167     * <p>
168     * Parses a log file written by the MFCMonitor and creates a collection of event sequences.
169     * </p>
170     *
171     * @param file
172     *            name and path of the log file
173     */
174    public void parseFile(File file) {
175        if (file == null) {
176            throw new IllegalArgumentException("file must not be null");
177        }
178
179        SAXParserFactory spf = SAXParserFactory.newInstance();
180        spf.setValidating(true);
181
182        SAXParser saxParser = null;
183        InputSource inputSource = null;
184        try {
185            saxParser = spf.newSAXParser();
186            inputSource = new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
187        }
188        catch (UnsupportedEncodingException e) {
189            Console.printerr("Error parsing file " + file.getName());
190            Console.logException(e);
191        }
192        catch (ParserConfigurationException e) {
193            Console.printerr("Error parsing file " + file.getName());
194            Console.logException(e);
195        }
196        catch (SAXException e) {
197            Console.printerr("Error parsing file " + file.getName());
198            Console.logException(e);
199        }
200        catch (FileNotFoundException e) {
201            Console.printerr("Error parsing file " + file.getName());
202            Console.logException(e);
203        }
204       
205        if (inputSource != null) {
206            inputSource.setSystemId("file://" + file.getAbsolutePath());
207            try {
208                if (saxParser == null) {
209                    throw new RuntimeException("SAXParser creation failed");
210                }
211                saxParser.parse(inputSource, this);
212            }
213            catch (SAXParseException e) {
214                Console.printerrln("Failure parsing file in line " + e.getLineNumber() +
215                                   ", column " + e.getColumnNumber() + ".");
216                Console.logException(e);
217            }
218            catch (SAXException e) {
219                Console.printerr("Error parsing file " + file.getName());
220                Console.logException(e);
221            }
222            catch (IOException e) {
223                Console.printerr("Error parsing file " + file.getName());
224                Console.logException(e);
225            }
226        }
227       
228        if (countMessageOccurences) {
229            Console.println("Message statistics:");
230            Console.println
231                (typeCounter.toString().replace(" ", StringTools.ENDLINE).replaceAll("[\\{\\}]", ""));
232        }
233    }
234   
235    /**
236     * <p>
237     * Returns the collection of event sequences that is obtained from parsing log files.
238     * </p>
239     *
240     * @return collection of event sequences
241     */
242    public Collection<List<Event>> getSequences() {
243        return sequences;
244    }
245
246    /**
247     * <p>
248     * Returns the gui model that is obtained from parsing log files.
249     * </p>
250     *
251     * @return collection of event sequences
252     */
253    public GUIModel getGuiModel() {
254        if( guiElementTree!=null ) {
255            return guiElementTree.getGUIModel();
256        } else {
257            return null;
258        }
259    }
260
261    /*
262     * (non-Javadoc)
263     *
264     * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String,
265     * java.lang.String, org.xml.sax.Attributes)
266     */
267    @Override
268    public void startElement(String uri, String localName, String qName, Attributes atts)
269        throws SAXException
270    {
271        if (qName.equals("session")) {
272            Console.traceln(Level.FINE, "start of session");
273            // in some logs, the session end may be marked in between the log. This is because
274            // of thread problems. So instead of creating a new GUI model, preserve it.
275            if (guiElementTree == null) {
276                guiElementTree = new GUIElementTree();
277            }
278            sequenceSplitter = new SequenceSplitter(guiElementTree);
279        }
280        else if (qName.equals("msg")) {
281            currentMessageType = WindowsMessageType.parseMessageType(atts.getValue("type"));
282
283            if (countMessageOccurences) {
284                Integer currentCount = typeCounter.get(currentMessageType);
285                if (currentCount == null) {
286                    typeCounter.put(currentMessageType, 1);
287                }
288                else {
289                    typeCounter.put(currentMessageType, currentCount + 1);
290                }
291            }
292
293            if (currentMessageType == WindowsMessageType.WM_CREATE) {
294                currentHandler = new HandlerCreate(guiElementTree);
295                currentHandler.onStartElement();
296            }
297            else if (currentMessageType == WindowsMessageType.WM_DESTROY) {
298                currentHandler = new HandlerDestroy(guiElementTree);
299                currentHandler.onStartElement();
300            }
301            else if (currentMessageType == WindowsMessageType.WM_SETTEXT) {
302                currentHandler = new HandlerSetText(guiElementTree);
303                currentHandler.onStartElement();
304            }
305        }
306        else if (qName.equals("param")) {
307            if (currentHandler != null) {
308                currentHandler.onParameter(atts.getValue("name"), atts.getValue("value"));
309            }
310            else {
311                // provide the parameters directly in the correct type
312                String paramName = atts.getValue("name");
313                if (("window.hwnd".equals(paramName)) ||
314                    ("source".equals(paramName)) ||
315                    ("LPARAM".equals(paramName)) ||
316                    ("WPARAM".equals(paramName)) ||
317                    ("scrollPos".equals(paramName)) ||
318                    ("scrollBarHandle".equals(paramName)))
319                {
320                    Long paramValue = Long.parseLong(atts.getValue("value"));
321                    currentMessageParameters.put(paramName, paramValue);
322                }
323                else {
324                    currentMessageParameters.put(paramName, atts.getValue("value"));
325                }
326            }
327        }
328    }
329
330    /*
331     * (non-Javadoc)
332     *
333     * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String,
334     * java.lang.String)
335     */
336    @Override
337    public void endElement(String uri, String localName, String qName) throws SAXException {
338        if (qName.equals("msg")) {
339            if (currentHandler != null) {
340                currentHandler.onEndElement();
341                currentHandler = null;
342            }
343            else {
344                try {
345                    long hwnd = (Long) currentMessageParameters.get("window.hwnd");
346                    MFCGUIElement target = (MFCGUIElement) guiElementTree.find(hwnd);
347                   
348                    WindowsMessage message = new WindowsMessage
349                        (currentMessageType, target, currentMessageParameters);
350                   
351                    sequenceSplitter.addMessage(message);
352                }
353                catch (IllegalArgumentException e) {
354                    Console.traceln(Level.WARNING, e.getMessage() + " WindowsMessage " + currentMessageType +
355                                    " ignored.");
356                }
357            }
358        }
359        else if (qName.equals("session")) {
360            sequenceSplitter.endSession();
361            List<Event> seq = sequenceSplitter.getSequence();
362            if (seq != null && !seq.isEmpty()) {
363                sequences.add(seq);
364            }
365            Console.traceln(Level.FINE, "end of session");
366        }
367    }
368
369}
Note: See TracBrowser for help on using the repository browser.