package de.ugoe.cs.quest.plugin.mfc; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.TreeMap; import java.util.logging.Level; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; import de.ugoe.cs.quest.eventcore.Event; import de.ugoe.cs.quest.eventcore.guimodel.GUIModel; import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowsMessage; import de.ugoe.cs.quest.plugin.mfc.eventcore.WindowsMessageType; import de.ugoe.cs.quest.plugin.mfc.guimodel.MFCGUIElement; import de.ugoe.cs.quest.plugin.mfc.guimodel.WindowTree; import de.ugoe.cs.util.StringTools; import de.ugoe.cs.util.console.Console; /** *

* This class provides functionality to parse XML log files generated by the MFCUsageMonitor of * EventBench. The result of parsing a file is a collection of event sequences. It uses the * {@link SequenceSplitter} and the {@link EventGenerator} as well as custom defined * {@link MessageHandler} for the parsing. *

* * @author Steffen Herbold * @version 1.0 */ public class MFCLogParser extends DefaultHandler { /** *

* If a custom message handler is used, this field contains its handle. Otherwise this field is * {@code null}. *

*/ private MessageHandler currentHandler; /** *

* internal handle to the current window tree *

*/ private WindowTree currentWindowTree; /** *

* the type of the currently parsed message *

*/ private WindowsMessageType currentMessageType; /** *

* the parameters of the currently parsed message *

*/ private Map currentMessageParameters = new HashMap(); /** *

* {@link SequenceSplitter} instance used by the {@link MFCLogParser}. *

*/ private SequenceSplitter sequenceSplitter; /** *

* Collection of message sequences that is contained in the log file, which is parsed. *

*/ private Collection> sequences; /** *

* Debugging variable that allows the analysis which message type occurs how often in the log * file. Can be used to enhance the message filter. *

*/ private SortedMap typeCounter; /** *

* Debugging variable that enables the counting of the occurrences of each message. Used in * combination with {@link #typeCounter}. *

*/ private boolean countMessageOccurences; /** *

* Constructor. Creates a new LogParser that does not count message occurrences. *

*/ public MFCLogParser() { this(false); } /** *

* Constructor. Creates a new LogParser. *

* * @param countMessageOccurences * if true, the occurrences of each message type in the log is counted. */ public MFCLogParser(boolean countMessageOccurences) { sequences = new LinkedList>(); currentHandler = null; this.countMessageOccurences = countMessageOccurences; if (countMessageOccurences) { typeCounter = new TreeMap(); } } /** *

* Parses a log file written by the MFCMonitor and creates a collection of event sequences. *

* * @param filename * name and path of the log file */ public void parseFile(String filename) { if (filename == null) { throw new IllegalArgumentException("filename must not be null"); } parseFile(new File(filename)); } /** *

* Parses a log file written by the MFCMonitor and creates a collection of event sequences. *

* * @param file * name and path of the log file */ public void parseFile(File file) { if (file == null) { throw new IllegalArgumentException("file must not be null"); } SAXParserFactory spf = SAXParserFactory.newInstance(); spf.setValidating(true); SAXParser saxParser = null; InputSource inputSource = null; try { saxParser = spf.newSAXParser(); inputSource = new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8")); } catch (UnsupportedEncodingException e) { Console.printerr("Error parsing file " + file.getName()); Console.logException(e); } catch (ParserConfigurationException e) { Console.printerr("Error parsing file " + file.getName()); Console.logException(e); } catch (SAXException e) { Console.printerr("Error parsing file " + file.getName()); Console.logException(e); } catch (FileNotFoundException e) { Console.printerr("Error parsing file " + file.getName()); Console.logException(e); } if (inputSource != null) { inputSource.setSystemId("file://" + file.getAbsolutePath()); try { if (saxParser == null) { throw new RuntimeException("SAXParser creation failed"); } saxParser.parse(inputSource, this); } catch (SAXParseException e) { Console.printerrln("Failure parsing file in line " + e.getLineNumber() + ", column " + e.getColumnNumber() + "."); Console.logException(e); } catch (SAXException e) { Console.printerr("Error parsing file " + file.getName()); Console.logException(e); } catch (IOException e) { Console.printerr("Error parsing file " + file.getName()); Console.logException(e); } } if (countMessageOccurences) { Console.println("Message statistics:"); Console.println (typeCounter.toString().replace(" ", StringTools.ENDLINE).replaceAll("[\\{\\}]", "")); } } /** *

* Returns the collection of event sequences that is obtained from parsing log files. *

* * @return collection of event sequences */ public Collection> getSequences() { return sequences; } /** *

* Returns the gui model that is obtained from parsing log files. *

* * @return collection of event sequences */ public GUIModel getGuiModel() { if( currentWindowTree!=null ) { return currentWindowTree.getGUIModel(); } else { return null; } } /* * (non-Javadoc) * * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, * java.lang.String, org.xml.sax.Attributes) */ @Override public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { if (qName.equals("session")) { Console.traceln(Level.FINE, "start of session"); // in some logs, the session end may be marked in between the log. This is because // of thread problems. So instead of creating a new GUI model, preserve it. if (currentWindowTree == null) { currentWindowTree = new WindowTree(); } sequenceSplitter = new SequenceSplitter(currentWindowTree); } else if (qName.equals("msg")) { currentMessageType = WindowsMessageType.parseMessageType(atts.getValue("type")); if (countMessageOccurences) { Integer currentCount = typeCounter.get(currentMessageType); if (currentCount == null) { typeCounter.put(currentMessageType, 1); } else { typeCounter.put(currentMessageType, currentCount + 1); } } if (currentMessageType == WindowsMessageType.WM_CREATE) { currentHandler = new HandlerCreate(currentWindowTree); currentHandler.onStartElement(); } else if (currentMessageType == WindowsMessageType.WM_DESTROY) { currentHandler = new HandlerDestroy(currentWindowTree); currentHandler.onStartElement(); } else if (currentMessageType == WindowsMessageType.WM_SETTEXT) { currentHandler = new HandlerSetText(currentWindowTree); currentHandler.onStartElement(); } } else if (qName.equals("param")) { if (currentHandler != null) { currentHandler.onParameter(atts.getValue("name"), atts.getValue("value")); } else { // provide the parameters directly in the correct type String paramName = atts.getValue("name"); if (("window.hwnd".equals(paramName)) || ("source".equals(paramName)) || ("LPARAM".equals(paramName)) || ("WPARAM".equals(paramName)) || ("scrollPos".equals(paramName)) || ("scrollBarHandle".equals(paramName))) { Long paramValue = Long.parseLong(atts.getValue("value")); currentMessageParameters.put(paramName, paramValue); } else { currentMessageParameters.put(paramName, atts.getValue("value")); } } } } /* * (non-Javadoc) * * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, * java.lang.String) */ @Override public void endElement(String uri, String localName, String qName) throws SAXException { if (qName.equals("msg")) { if (currentHandler != null) { currentHandler.onEndElement(); currentHandler = null; } else { try { long hwnd = (Long) currentMessageParameters.get("window.hwnd"); MFCGUIElement target = currentWindowTree.find(hwnd); WindowsMessage message = new WindowsMessage (currentMessageType, target, currentMessageParameters); sequenceSplitter.addMessage(message); } catch (IllegalArgumentException e) { Console.traceln(Level.WARNING, e.getMessage() + " WindowsMessage " + currentMessageType + " ignored."); } } } else if (qName.equals("session")) { sequenceSplitter.endSession(); List seq = sequenceSplitter.getSequence(); if (seq != null && !seq.isEmpty()) { sequences.add(seq); } Console.traceln(Level.FINE, "end of session"); } } }