source: trunk/autoquest-plugin-html/src/main/java/de/ugoe/cs/autoquest/plugin/html/HTMLLogParser.java @ 996

Last change on this file since 996 was 996, checked in by pharms, 12 years ago
  • added support for storing timestamps with events
File size: 13.9 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.html;
16
17import java.io.BufferedReader;
18import java.io.File;
19import java.io.FileInputStream;
20import java.io.FileNotFoundException;
21import java.io.IOException;
22import java.io.InputStreamReader;
23import java.net.MalformedURLException;
24import java.net.URL;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.List;
28import java.util.logging.Level;
29import java.util.regex.Matcher;
30import java.util.regex.Pattern;
31
32import de.ugoe.cs.autoquest.eventcore.Event;
33import de.ugoe.cs.autoquest.eventcore.gui.IInteraction;
34import de.ugoe.cs.autoquest.eventcore.gui.KeyboardFocusChange;
35import de.ugoe.cs.autoquest.eventcore.gui.MouseButtonInteraction;
36import de.ugoe.cs.autoquest.eventcore.gui.MouseClick;
37import de.ugoe.cs.autoquest.eventcore.gui.Scroll;
38import de.ugoe.cs.autoquest.eventcore.gui.TextInput;
39import de.ugoe.cs.autoquest.eventcore.guimodel.GUIElementFactory;
40import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModel;
41import de.ugoe.cs.autoquest.eventcore.guimodel.GUIModelException;
42import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElement;
43import de.ugoe.cs.autoquest.eventcore.guimodel.IGUIElementSpec;
44import de.ugoe.cs.autoquest.eventcore.guimodel.ITextArea;
45import de.ugoe.cs.autoquest.eventcore.guimodel.ITextField;
46import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageElementSpec;
47import de.ugoe.cs.autoquest.plugin.html.guimodel.HTMLPageSpec;
48import de.ugoe.cs.util.FileTools;
49import de.ugoe.cs.util.console.Console;
50
51/**
52 * <p>
53 * TODO comment
54 * </p>
55 *
56 * @author Patrick Harms
57 */
58public class HTMLLogParser {
59
60    /**
61     * <p>
62     * Name and path of the robot filter.
63     * </p>
64     */
65    private static final String ROBOTFILTERFILE = "data/robots/robotfilter.txt";
66
67    /**
68     * <p>
69     * Field that contains a regular expression that matches all robots
70     * contained in {@link #ROBOTFILTERFILE}.
71     * </p>
72     */
73    private String robotRegex = null;
74
75    /**
76     *
77     */
78    private Pattern htmlElementPattern = Pattern.compile("(\\w+)(\\[(\\d+)\\]|\\(id=([\\w-]+)\\))");
79
80    /**
81     *
82     */
83    private List<List<Event>> sequences = new ArrayList<List<Event>>();
84   
85    /**
86     *
87     */
88    private GUIModel guiModel = new GUIModel();
89   
90    /**
91     * <p>
92     * TODO: comment
93     * </p>
94     *
95     * @param source
96     */
97    public void parseFile(String source) throws IllegalArgumentException {
98        if (source == null) {
99            throw new IllegalArgumentException("source must not be null");
100        }
101
102        parseFile(new File(source));
103    }
104
105    /**
106     * <p>
107     * TODO: comment
108     * </p>
109     *
110     * @param source
111     */
112    public void parseFile(File source) throws IllegalArgumentException {
113        if (source == null) {
114            throw new IllegalArgumentException("source must not be null");
115        }
116        else if (!source.exists()) {
117            throw new IllegalArgumentException("source file does not exist");
118        }
119        else if (!source.isFile()) {
120            throw new IllegalArgumentException("source is not a file");
121        }
122
123        BufferedReader reader = null;
124       
125        try {
126            reader =
127                new BufferedReader(new InputStreamReader(new FileInputStream(source), "UTF-8"));
128       
129            loadRobotRegex();
130
131            List<Event> sequence = new ArrayList<Event>();
132
133            int lineCounter = 0;
134            String line = reader.readLine();
135            while ((line != null) && (!"".equals(line))) {
136                lineCounter++;
137                String[] values = line.substring(1, line.length() - 1).split("\" \"");
138
139                String clientId = values[0];
140                long timestamp = Long.parseLong(values[1]);
141                String title = values[2];
142                String uriString = values[3];
143                String agent = values[4];
144                String eventName = values[5];
145                String htmlElementPath = values[6];
146
147                List<String> eventParameters = null;
148                if (values.length > 7) {
149                    eventParameters = new ArrayList<String>();
150
151                    for (int i = 7; i < values.length; i++) {
152                        eventParameters.add(values[i]);
153                    }
154                }
155               
156                if (isRobot(agent)) {
157                    // do not handle sessions of robots
158                    Console.println("ignoring robot session: " + agent);
159                    break;
160                }
161               
162               
163                try {
164                    URL url = new URL(uriString);
165                    IGUIElement guiElement = getGUIElement(url, title, htmlElementPath);
166                    IInteraction interaction =
167                        getInteraction(eventName, guiElement, eventParameters);
168                   
169                    if (interaction != null) {
170                        Event event =
171                            createEvent(clientId, interaction, guiElement, timestamp, agent);
172                        sequence.add(event);
173                    }
174                }
175                catch (MalformedURLException e) {
176                    Console.traceln(Level.FINE, "Ignored line " + lineCounter + ": " +
177                                    e.getMessage());
178                }
179               
180                line = reader.readLine();
181            }
182           
183            Console.traceln(Level.INFO, "read user sequence with " + sequence.size() +
184                            " events from " + source);
185           
186            sequences.add(sequence);
187        }
188        catch (Exception e) {
189            Console.printerrln("could not parse file " + source + ": " + e);
190            e.printStackTrace();
191        }
192    }
193
194    /**
195     * <p>
196     * TODO: comment
197     * </p>
198     *
199     * @return
200     */
201    public Collection<List<Event>> getSequences() {
202        return sequences;
203    }
204
205    /**
206     * <p>
207     * TODO: comment
208     * </p>
209     *
210     * @return
211     */
212    public GUIModel getGuiModel() {
213        return guiModel;
214    }
215
216    /**
217     * <p>
218     * Reads {@link #ROBOTFILTERFILE} and creates a regular expression that
219     * matches all the robots defined in the file. The regular expression is
220     * stored in the field {@link #robotRegex}.
221     * </p>
222     *
223     * @throws IOException
224     *             thrown if there is a problem reading the robot filter
225     * @throws FileNotFoundException
226     *             thrown if the robot filter is not found
227     */
228    private void loadRobotRegex() throws IOException, FileNotFoundException {
229        String[] lines = FileTools.getLinesFromFile(ROBOTFILTERFILE);
230        StringBuilder regex = new StringBuilder();
231        for (int i = 0; i < lines.length; i++) {
232            regex.append("(.*" + lines[i] + ".*)");
233            if (i != lines.length - 1) {
234                regex.append('|');
235            }
236        }
237        robotRegex = regex.toString();
238    }
239
240    /**
241     * <p>
242     * TODO: comment
243     * </p>
244     *
245     * @param htmlElementPath
246     * @return
247     * @throws GUIModelException
248     * @throws 
249     */
250    private IGUIElement getGUIElement(URL url, String title, String htmlElementPath)
251        throws GUIModelException
252    {
253        String[] pathElements = htmlElementPath.split("/");
254        List<IGUIElementSpec> guiElementPath = new ArrayList<IGUIElementSpec>();
255       
256        HTMLPageSpec page = new HTMLPageSpec(url, title);
257       
258        guiElementPath.add(page.getServer());
259        guiElementPath.add(page);
260       
261        for (String pathElement : pathElements) {
262            if ((pathElement != null) && (!"".equals(pathElement))) {           
263                Matcher matcher = htmlElementPattern.matcher(pathElement);
264                if (!matcher.matches()) {
265                    throw new IllegalArgumentException("could not parse HTML element " + pathElement);
266                }
267
268                String type = matcher.group(1);
269                String indexStr = matcher.group(3);
270                String id = matcher.group(4);
271
272                int index = -1;
273
274                if ((indexStr != null) && (!"".equals(indexStr))) {
275                    index = Integer.parseInt(indexStr);
276                }
277
278                guiElementPath.add(new HTMLPageElementSpec(page, type, id, index));
279            }
280        }
281       
282        return guiModel.integratePath(guiElementPath, GUIElementFactory.getInstance());
283    }
284
285    /**
286     * <p>
287     * TODO: comment
288     * </p>
289     *
290     * @param eventName
291     * @param guiElement
292     * @param eventParameters
293     * @return
294     */
295    private IInteraction getInteraction(String       eventName,
296                                        IGUIElement  guiElement,
297                                        List<String> eventParameters)
298    {
299        IInteraction result = null;
300       
301        if ("onclick".equals(eventName)) {
302            int[] coordinates =
303                getCoordinateParameter(eventName, eventParameters, 0, "click coordinates");
304            result = new MouseClick
305                (MouseButtonInteraction.Button.LEFT, coordinates[0], coordinates[1]);
306        }
307        else if ("onfocus".equals(eventName)) {
308            result = new KeyboardFocusChange();
309        }
310        else if ("onscroll".equals(eventName)) {
311            int[] coordinates =
312                getCoordinateParameter(eventName, eventParameters, 0, "click coordinates");
313            result = new Scroll(coordinates[0], coordinates[1]);
314        }
315        else if ("onchange".equals(eventName)) {
316            String value = getStringParameter(eventName, eventParameters, 0, "selected value");
317           
318            if ((guiElement instanceof ITextArea) || (guiElement instanceof ITextField)) {
319                result = new TextInput(value, null);
320            }
321            else {
322                throw new IllegalArgumentException("can not handle onchange events on GUI " +
323                                                   "elements of type " + guiElement.getClass());
324            }
325        }
326        else if ("onunload".equals(eventName) || "onbeforeunload".equals(eventName) ||
327                 "onpagehide".equals(eventName) || "onpageshow".equals(eventName))
328        {
329            Console.traceln(Level.FINE, "Ignored event name \"" + eventName + "\"");
330        }
331        else {
332            throw new IllegalArgumentException("unknown event name: \"" + eventName + "\"");
333        }
334       
335        return result;
336    }
337
338    /**
339     * <p>
340     * TODO: comment
341     * </p>
342     *
343     * @param clientId
344     * @param interaction
345     * @param guiElement
346     * @param timestamp
347     * @param agent
348     * @return
349     */
350    private Event createEvent(String       clientId,
351                              IInteraction interaction,
352                              IGUIElement  guiElement,
353                              long         timestamp,
354                              String       agent)
355    {
356        Event event = new Event(interaction, guiElement);
357        event.setParameter("clientId", clientId);
358        event.setParameter("agent", agent);
359       
360        event.setTimestamp(timestamp);
361       
362        return event;
363    }
364
365    /**
366     * <p>
367     * TODO: comment
368     * </p>
369     *
370     * @param eventName
371     * @param eventParameters
372     * @param i
373     * @param string
374     * @return
375     */
376    private String getStringParameter(String       eventName,
377                                      List<String> eventParameters,
378                                      int          parameterIndex,
379                                      String       parameterDesc)
380        throws IllegalArgumentException
381    {
382        String value =
383            eventParameters.size() > parameterIndex ? eventParameters.get(parameterIndex) : null;
384       
385        if (value == null) {
386            throw new IllegalArgumentException
387                (eventName + " event does not provide the " + parameterDesc);
388        }
389       
390        return value;
391    }
392
393    /**
394     * <p>
395     * TODO: comment
396     * </p>
397     *
398     * @param eventName
399     * @param eventParameters
400     * @return
401     */
402    private int[] getCoordinateParameter(String       eventName,
403                                         List<String> eventParameters,
404                                         int          parameterIndex,
405                                         String       parameterDesc)
406    {
407        String value =
408            getStringParameter(eventName, eventParameters, parameterIndex, parameterDesc);
409       
410        String[] intStrs = value.split(",");
411       
412        if ((intStrs == null) || (intStrs.length != 2)) {
413            throw new IllegalArgumentException("the " + parameterDesc + " of an " + eventName +
414                                               " event does not provide two correct coordinates");
415        }
416       
417        try {
418            return new int[] { Integer.parseInt(intStrs[0]), Integer.parseInt(intStrs[1]) };
419        }
420        catch (NumberFormatException e) {
421            throw new IllegalArgumentException("the coordinates provided as " + parameterDesc +
422                                               " of an " + eventName + " event are no numbers");
423        }
424    }
425
426    /**
427     * <p>
428     * Checks whether an agent is a robot.
429     * </p>
430     *
431     * @param agent
432     *            agent that is checked
433     * @return true, if the agent is a robot; false otherwise
434     */
435    private boolean isRobot(String agent) {
436        return agent.matches(robotRegex);
437    }
438
439}
Note: See TracBrowser for help on using the repository browser.