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