[927] | 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 |
|
---|
[922] | 15 | package de.ugoe.cs.autoquest.plugin.mfc;
|
---|
[1] | 16 |
|
---|
| 17 | import java.io.File;
|
---|
[45] | 18 | import java.io.FileInputStream;
|
---|
[1] | 19 | import java.io.FileNotFoundException;
|
---|
| 20 | import java.io.IOException;
|
---|
[45] | 21 | import java.io.InputStreamReader;
|
---|
[774] | 22 | import java.io.UnsupportedEncodingException;
|
---|
[203] | 23 | import java.util.Collection;
|
---|
[619] | 24 | import java.util.HashMap;
|
---|
[1] | 25 | import java.util.LinkedList;
|
---|
| 26 | import java.util.List;
|
---|
[619] | 27 | import java.util.Map;
|
---|
[1] | 28 | import java.util.SortedMap;
|
---|
| 29 | import java.util.TreeMap;
|
---|
[639] | 30 | import java.util.logging.Level;
|
---|
[1] | 31 |
|
---|
| 32 | import javax.xml.parsers.ParserConfigurationException;
|
---|
| 33 | import javax.xml.parsers.SAXParser;
|
---|
| 34 | import javax.xml.parsers.SAXParserFactory;
|
---|
| 35 |
|
---|
| 36 | import org.xml.sax.Attributes;
|
---|
| 37 | import org.xml.sax.InputSource;
|
---|
| 38 | import org.xml.sax.SAXException;
|
---|
| 39 | import org.xml.sax.SAXParseException;
|
---|
| 40 | import org.xml.sax.helpers.DefaultHandler;
|
---|
| 41 |
|
---|
[922] | 42 | import de.ugoe.cs.autoquest.eventcore.Event;
|
---|
[1006] | 43 | import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementTree;
|
---|
[922] | 44 | import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
|
---|
| 45 | import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessage;
|
---|
| 46 | import de.ugoe.cs.autoquest.plugin.mfc.eventcore.WindowsMessageType;
|
---|
| 47 | import de.ugoe.cs.autoquest.plugin.mfc.guimodel.MFCGUIElement;
|
---|
[1] | 48 | import de.ugoe.cs.util.StringTools;
|
---|
| 49 | import de.ugoe.cs.util.console.Console;
|
---|
| 50 |
|
---|
[171] | 51 | /**
|
---|
| 52 | * <p>
|
---|
[619] | 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.
|
---|
[171] | 57 | * </p>
|
---|
| 58 | *
|
---|
| 59 | * @author Steffen Herbold
|
---|
[1006] | 60 | * @author Fabian Glaser
|
---|
[171] | 61 | * @version 1.0
|
---|
| 62 | */
|
---|
[297] | 63 | public class MFCLogParser extends DefaultHandler {
|
---|
[171] | 64 |
|
---|
[619] | 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;
|
---|
[171] | 72 |
|
---|
[619] | 73 | /**
|
---|
| 74 | * <p>
|
---|
[1006] | 75 | * internal handle to the gui element tree
|
---|
[619] | 76 | * </p>
|
---|
| 77 | */
|
---|
[1006] | 78 | private GUIElementTree guiElementTree;
|
---|
[171] | 79 |
|
---|
[619] | 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;
|
---|
[171] | 100 |
|
---|
[619] | 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;
|
---|
[171] | 107 |
|
---|
[619] | 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;
|
---|
[171] | 115 |
|
---|
[619] | 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;
|
---|
[171] | 123 |
|
---|
[619] | 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 | }
|
---|
[171] | 132 |
|
---|
[619] | 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 | }
|
---|
[171] | 149 |
|
---|
[619] | 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) {
|
---|
[766] | 160 | throw new IllegalArgumentException("filename must not be null");
|
---|
[619] | 161 | }
|
---|
[171] | 162 |
|
---|
[619] | 163 | parseFile(new File(filename));
|
---|
| 164 | }
|
---|
[171] | 165 |
|
---|
[619] | 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) {
|
---|
[766] | 176 | throw new IllegalArgumentException("file must not be null");
|
---|
[619] | 177 | }
|
---|
[171] | 178 |
|
---|
[619] | 179 | SAXParserFactory spf = SAXParserFactory.newInstance();
|
---|
| 180 | spf.setValidating(true);
|
---|
[171] | 181 |
|
---|
[619] | 182 | SAXParser saxParser = null;
|
---|
| 183 | InputSource inputSource = null;
|
---|
| 184 | try {
|
---|
| 185 | saxParser = spf.newSAXParser();
|
---|
[774] | 186 | inputSource = new InputSource(new InputStreamReader(new FileInputStream(file), "UTF-8"));
|
---|
| 187 | }
|
---|
| 188 | catch (UnsupportedEncodingException e) {
|
---|
[837] | 189 | Console.printerr("Error parsing file " + file.getName());
|
---|
| 190 | Console.logException(e);
|
---|
[619] | 191 | }
|
---|
| 192 | catch (ParserConfigurationException e) {
|
---|
[837] | 193 | Console.printerr("Error parsing file " + file.getName());
|
---|
| 194 | Console.logException(e);
|
---|
[619] | 195 | }
|
---|
| 196 | catch (SAXException e) {
|
---|
[837] | 197 | Console.printerr("Error parsing file " + file.getName());
|
---|
| 198 | Console.logException(e);
|
---|
[619] | 199 | }
|
---|
| 200 | catch (FileNotFoundException e) {
|
---|
[837] | 201 | Console.printerr("Error parsing file " + file.getName());
|
---|
| 202 | Console.logException(e);
|
---|
[619] | 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() + ".");
|
---|
[844] | 216 | Console.logException(e);
|
---|
[619] | 217 | }
|
---|
| 218 | catch (SAXException e) {
|
---|
[837] | 219 | Console.printerr("Error parsing file " + file.getName());
|
---|
| 220 | Console.logException(e);
|
---|
[619] | 221 | }
|
---|
| 222 | catch (IOException e) {
|
---|
[837] | 223 | Console.printerr("Error parsing file " + file.getName());
|
---|
| 224 | Console.logException(e);
|
---|
[619] | 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 | }
|
---|
[171] | 245 |
|
---|
[619] | 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() {
|
---|
[1006] | 254 | if( guiElementTree!=null ) {
|
---|
| 255 | return guiElementTree.getGUIModel();
|
---|
[844] | 256 | } else {
|
---|
| 257 | return null;
|
---|
| 258 | }
|
---|
[619] | 259 | }
|
---|
[171] | 260 |
|
---|
[619] | 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")) {
|
---|
[639] | 272 | Console.traceln(Level.FINE, "start of session");
|
---|
[619] | 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.
|
---|
[1006] | 275 | if (guiElementTree == null) {
|
---|
| 276 | guiElementTree = new GUIElementTree();
|
---|
[619] | 277 | }
|
---|
[1006] | 278 | sequenceSplitter = new SequenceSplitter(guiElementTree);
|
---|
[619] | 279 | }
|
---|
| 280 | else if (qName.equals("msg")) {
|
---|
| 281 | currentMessageType = WindowsMessageType.parseMessageType(atts.getValue("type"));
|
---|
[171] | 282 |
|
---|
[619] | 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 | }
|
---|
[171] | 292 |
|
---|
[619] | 293 | if (currentMessageType == WindowsMessageType.WM_CREATE) {
|
---|
[1006] | 294 | currentHandler = new HandlerCreate(guiElementTree);
|
---|
[619] | 295 | currentHandler.onStartElement();
|
---|
| 296 | }
|
---|
| 297 | else if (currentMessageType == WindowsMessageType.WM_DESTROY) {
|
---|
[1006] | 298 | currentHandler = new HandlerDestroy(guiElementTree);
|
---|
[619] | 299 | currentHandler.onStartElement();
|
---|
| 300 | }
|
---|
| 301 | else if (currentMessageType == WindowsMessageType.WM_SETTEXT) {
|
---|
[1006] | 302 | currentHandler = new HandlerSetText(guiElementTree);
|
---|
[619] | 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");
|
---|
[1006] | 346 | MFCGUIElement target = (MFCGUIElement) guiElementTree.find(hwnd);
|
---|
[619] | 347 |
|
---|
| 348 | WindowsMessage message = new WindowsMessage
|
---|
| 349 | (currentMessageType, target, currentMessageParameters);
|
---|
| 350 |
|
---|
| 351 | sequenceSplitter.addMessage(message);
|
---|
| 352 | }
|
---|
[766] | 353 | catch (IllegalArgumentException e) {
|
---|
[639] | 354 | Console.traceln(Level.WARNING, e.getMessage() + " WindowsMessage " + currentMessageType +
|
---|
[619] | 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 | }
|
---|
[639] | 365 | Console.traceln(Level.FINE, "end of session");
|
---|
[619] | 366 | }
|
---|
| 367 | }
|
---|
| 368 |
|
---|
[1] | 369 | }
|
---|