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