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

Last change on this file since 1232 was 1232, checked in by pharms, 11 years ago
  • added some documentation
  • corrected handling of unparseable file during compression
File size: 11.4 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.File;
18import java.io.FileNotFoundException;
19import java.io.FileOutputStream;
20import java.io.OutputStreamWriter;
21import java.io.PrintWriter;
22import java.io.UnsupportedEncodingException;
23import java.text.DecimalFormat;
24import java.util.HashSet;
25import java.util.LinkedList;
26import java.util.List;
27import java.util.Map;
28import java.util.Set;
29
30import org.xml.sax.SAXException;
31
32import de.ugoe.cs.util.StringTools;
33import de.ugoe.cs.util.console.Console;
34
35/**
36 * <p>
37 * This class can be used to compress a bunch of HTML log files to one file, that contains only
38 * one full GUI model.
39 * </p>
40 *
41 * @author Patrick Harms
42 * @version 1.0
43 *
44 */
45public class HTMLLogCompressor extends AbstractDefaultLogParser {
46   
47    /**
48     * <p>
49     * The output writer into which the compressed variant of the log files is written
50     * </p>
51     */
52    private PrintWriter outputWriter;
53   
54    /**
55     * <p>
56     * the GUI elements, that were already logged and need therefore not be logged again into
57     * the same file
58     * </p>
59     */
60    private Set<String> loggedGUIElementIds = new HashSet<String>();
61
62    /**
63     * <p>
64     * the events that were read
65     * </p>
66     */
67    private List<EventEntry> sortedEvents = new LinkedList<EventEntry>();
68
69    /**
70     * <p>
71     * called to compress all log files in the provided directory. The method reuses
72     * {@link #compressFilesInDirectory(File)}.
73     * </p>
74     *
75     * @param directory the directory containing the log files to be compressed
76     */
77    public void compressFilesInDirectory(String directory) {
78        if (directory == null) {
79            throw new IllegalArgumentException("directory must not be null");
80        }
81
82        compressFilesInDirectory(new File(directory));
83    }
84
85    /**
86     * <p>
87     * called to compress all log files in the provided directory. The directory if processed
88     * recursively. For each directory containing at least one log file the log files are
89     * replaced by one large log file containing the GUI model as well as all events contained in
90     * the compressed log file. If one log file could not be parsed, it is ignored and not
91     * compressed with the other ones.
92     * </p>
93     *
94     * @param directory the directory containing the log files to be compressed
95     */
96    public void compressFilesInDirectory(File directory) {
97        if (directory == null) {
98            throw new IllegalArgumentException("directory must not be null");
99        }
100       
101        if (!directory.exists()) {
102            throw new IllegalArgumentException("directory must denote an existing directory");
103        }
104       
105        if (!directory.isDirectory()) {
106            throw new IllegalArgumentException("directory must denote a directory");
107        }
108       
109        File[] files = directory.listFiles();
110       
111        if ((files == null) || (files.length == 0)) {
112            Console.println("directory is empty");
113            return;
114        }
115       
116        File outFile = null;
117       
118        int logFileIndex = -1;
119        do {
120            outFile = new File(directory, getLogFileName(directory.getName(), logFileIndex));
121            logFileIndex++;
122        }
123        while (outFile.exists());
124       
125        List<File> compressedFiles = new LinkedList<File>();
126       
127        try {
128            FileOutputStream fis = new FileOutputStream(outFile);
129            outputWriter = new PrintWriter(new OutputStreamWriter(fis, "UTF-8"));
130            outputWriter.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
131            outputWriter.println("<session>");
132
133            loggedGUIElementIds.clear();
134            sortedEvents.clear();
135
136            for (File file : files) {
137                if (file.isFile()) {
138                    try {
139                        super.parseFile(file);
140                        Console.println("compressed " + file);
141                        compressedFiles.add(file);
142                    }
143                    catch (Exception e) {
144                        Console.printerrln("could not parse and compress file " + file);
145                    }
146                }
147            }
148           
149            for (EventEntry event : sortedEvents) {
150                event.dump();
151            }
152
153            outputWriter.println("</session>");
154            outputWriter.flush();
155        }
156        catch (FileNotFoundException e) {
157            Console.printerrln("could not create compressed file " + outFile);
158            compressedFiles.clear();
159        }
160        catch (UnsupportedEncodingException e) {
161            // this should never happen
162            e.printStackTrace();
163            compressedFiles.clear();
164        }
165        finally {
166            if (outputWriter != null) {
167                outputWriter.close();
168                outputWriter = null;
169            }
170        }
171       
172        for (File file : compressedFiles) {
173            if (!file.delete()) {
174                Console.printerrln("could not delete compressed file " + file);
175            }
176        }
177       
178        if (outFile.exists()) {
179            File finalOutFile = null;
180           
181            logFileIndex = -1;
182            do {
183                logFileIndex++;
184                finalOutFile =
185                    new File(directory, getLogFileName(directory.getName(), logFileIndex));
186            }
187            while (finalOutFile.exists());
188
189            outFile.renameTo(finalOutFile);
190            Console.println("created " + finalOutFile);
191        }
192    }
193   
194    /* (non-Javadoc)
195     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.lang.String)
196     */
197    @Override
198    public void parseFile(String filename) {
199        throw new IllegalStateException("this method must not be called externally");
200    }
201
202    /* (non-Javadoc)
203     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.io.File)
204     */
205    @Override
206    public void parseFile(File file) {
207        throw new IllegalStateException("this method must not be called externally");
208    }
209
210    /* (non-Javadoc)
211     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
212     */
213    @Override
214    protected boolean handleGUIElement(String id, Map<String, String> parameters)
215        throws SAXException
216    {
217        if (!loggedGUIElementIds.contains(id)) {
218            outputWriter.print("<component id=\"");
219            outputWriter.print(id);
220            outputWriter.println("\">");
221       
222            for (Map.Entry<String, String> param : parameters.entrySet()) {
223                dumpParam(param.getKey(), param.getValue());
224            }
225           
226            outputWriter.println("</component>");
227       
228            loggedGUIElementIds.add(id);
229        }
230       
231        return true;
232    }
233
234    /* (non-Javadoc)
235     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String,Map)
236     */
237    @Override
238    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
239        String timestampStr = parameters.get("timestamp");
240       
241        long timestamp = Long.MAX_VALUE;
242       
243        if (timestampStr != null) {
244            timestamp = Long.parseLong(timestampStr);
245        }
246       
247        EventEntry newEvent = new EventEntry(type, parameters, timestamp);
248       
249        boolean added = false;
250        for (int i = 0; i < sortedEvents.size(); i++) {
251            if (sortedEvents.get(i).timestamp > newEvent.timestamp) {
252                sortedEvents.add(i, newEvent);
253                added = true;
254                break;
255            }
256        }
257       
258        if (!added) {
259            sortedEvents.add(newEvent);
260        }
261
262        return true;
263    }
264
265    /**
266     * <p>
267     * dumps a parameter with the given name and value to the log file. The result is a
268     * tag named param with a name attribute and a value attribute. The value is transformed
269     * to a String if it is no String already. Furthermore, an XML entity replacement is performed
270     * if required.
271     * </p>
272     *
273     * @param name  the name of the parameter to be dumped
274     * @param value the value of the parameter to be dumped
275     */
276    private void dumpParam(String name, Object value) {
277        if (value == null) {
278            return;
279        }
280       
281        String val;
282       
283        if (value instanceof String) {
284            val = (String) value;
285        }
286        else {
287            val = String.valueOf(value);
288        }
289       
290        outputWriter.print(" <param name=\"");
291        outputWriter.print(name);
292        outputWriter.print("\" value=\"");
293        outputWriter.print(StringTools.xmlEntityReplacement(val));
294        outputWriter.println("\"/>");
295    }
296
297
298    /**
299     * <p>
300     * used to calculate a log file name. If the provided index is smaller 0, then no index
301     * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log".
302     * </p>
303     *
304     * @param index the index of the log file or -1 one, if no index shall be added
305     *
306     * @return the file name as described
307     */
308    private String getLogFileName(String clientId, int index) {
309        String result = "htmlmonitor_" + clientId;
310       
311        if (index >= 0) {
312            result += "_" + new DecimalFormat("000" ).format(index);
313        }
314       
315        result += ".log";
316       
317        return result;
318    }
319   
320    /**
321     * <p>
322     * this class is used internally for storing events in a sorted list together with the
323     * timestamps, being the sort criteria.
324     * </p>
325     */
326    private class EventEntry {
327       
328        /**
329         * <p>
330         * the type of the event
331         * </p>
332         */
333        private String type;
334       
335        /**
336         * <p>
337         * the parameters of the event
338         * </p>
339         */
340        private Map<String, String> parameters;
341       
342        /**
343         * <p>
344         * the timestamp of the event
345         * </p>
346         */
347        private long timestamp;
348
349        /**
350         * <p>
351         * creates a new event entry with event type, parameters and the timestamp
352         * </p>
353         */
354        private EventEntry(String type, Map<String, String> parameters, long timestamp) {
355            this.type = type;
356            this.parameters = parameters;
357            this.timestamp = timestamp;
358        }
359       
360        /**
361         * <p>
362         * convenience method for dumping the event into the compressed log file
363         * </p>
364         */
365        private void dump() {
366            outputWriter.print("<event type=\"");
367            outputWriter.print(type);
368            outputWriter.println("\">");
369           
370            for (Map.Entry<String, String> param : parameters.entrySet()) {
371                dumpParam(param.getKey(), param.getValue());
372            }
373           
374            outputWriter.println("</event>");
375        }
376    }
377}
Note: See TracBrowser for help on using the repository browser.