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

Last change on this file since 2210 was 1272, checked in by pharms, 11 years ago
  • removed find bugs warnings
File size: 12.6 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                        Console.printerrln(e.getMessage());
146                    }
147                }
148            }
149           
150            for (EventEntry event : sortedEvents) {
151                event.dump();
152            }
153
154            outputWriter.println("</session>");
155            outputWriter.flush();
156        }
157        catch (FileNotFoundException e) {
158            Console.printerrln("could not create compressed file " + outFile);
159            compressedFiles.clear();
160        }
161        catch (UnsupportedEncodingException e) {
162            // this should never happen
163            e.printStackTrace();
164            compressedFiles.clear();
165        }
166        finally {
167            if (outputWriter != null) {
168                outputWriter.close();
169                outputWriter = null;
170            }
171        }
172       
173        if (compressedFiles.size() > 0) {
174            for (File file : compressedFiles) {
175                if (!file.delete()) {
176                    Console.printerrln("could not delete compressed file " + file);
177                }
178            }
179       
180            if (outFile.exists()) {
181                File finalOutFile = null;
182           
183                logFileIndex = -1;
184                do {
185                    logFileIndex++;
186                    finalOutFile =
187                        new File(directory, getLogFileName(directory.getName(), logFileIndex));
188                }
189                while (finalOutFile.exists());
190
191                if (outFile.renameTo(finalOutFile)) {
192                    Console.println("created " + finalOutFile);
193                }
194                else {
195                    Console.println
196                        ("created " + outFile + " but could not rename to " + finalOutFile);
197                }
198            }
199        }
200        else {
201            if (!outFile.delete()) {
202                Console.printerrln
203                    ("could not delete temporary file " + outFile);
204            }
205        }
206    }
207   
208    /* (non-Javadoc)
209     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.lang.String)
210     */
211    @Override
212    public void parseFile(String filename) {
213        throw new IllegalStateException("this method must not be called externally");
214    }
215
216    /* (non-Javadoc)
217     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#parseFile(java.io.File)
218     */
219    @Override
220    public void parseFile(File file) {
221        throw new IllegalStateException("this method must not be called externally");
222    }
223
224    /* (non-Javadoc)
225     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleGUIElement(String, Map)
226     */
227    @Override
228    protected boolean handleGUIElement(String id, Map<String, String> parameters)
229        throws SAXException
230    {
231        if (!loggedGUIElementIds.contains(id)) {
232            outputWriter.print("<component id=\"");
233            outputWriter.print(id);
234            outputWriter.println("\">");
235       
236            for (Map.Entry<String, String> param : parameters.entrySet()) {
237                dumpParam(param.getKey(), param.getValue());
238            }
239           
240            outputWriter.println("</component>");
241       
242            loggedGUIElementIds.add(id);
243        }
244       
245        return true;
246    }
247
248    /* (non-Javadoc)
249     * @see de.ugoe.cs.autoquest.plugin.html.AbstractDefaultLogParser#handleEvent(String,Map)
250     */
251    @Override
252    protected boolean handleEvent(String type, Map<String, String> parameters) throws SAXException {
253        String timestampStr = parameters.get("timestamp");
254       
255        long timestamp = Long.MAX_VALUE;
256       
257        if (timestampStr != null) {
258            timestamp = Long.parseLong(timestampStr);
259        }
260       
261        EventEntry newEvent = new EventEntry(type, parameters, timestamp);
262       
263        int start = 0;
264        int end = sortedEvents.size();
265        int center = 0;
266        long centerTimestamp;
267       
268        while (start != end) {
269            center = start + ((end - start) / 2);
270           
271            if ((center != start) || (center != end)) {
272                centerTimestamp = sortedEvents.get(center).timestamp;
273           
274                if (centerTimestamp < newEvent.timestamp) {
275                    start = Math.max(center, start + 1);
276                }
277                else if (centerTimestamp > newEvent.timestamp) {
278                    end = Math.min(center, end - 1);
279                }
280                else {
281                    // add the event directly where the center is, as the timestamps of the center
282                    // and the new event are equal
283                    end = center;
284                    start = end;
285                    break;
286                }
287            }
288            else {
289                // add the event to the position denoted by the add index
290                break;
291            }
292        }
293       
294        sortedEvents.add(start, newEvent);
295
296        return true;
297    }
298
299    /**
300     * <p>
301     * dumps a parameter with the given name and value to the log file. The result is a
302     * tag named param with a name attribute and a value attribute. The value is transformed
303     * to a String if it is no String already. Furthermore, an XML entity replacement is performed
304     * if required.
305     * </p>
306     *
307     * @param name  the name of the parameter to be dumped
308     * @param value the value of the parameter to be dumped
309     */
310    private void dumpParam(String name, Object value) {
311        if (value == null) {
312            return;
313        }
314       
315        String val;
316       
317        if (value instanceof String) {
318            val = (String) value;
319        }
320        else {
321            val = String.valueOf(value);
322        }
323       
324        outputWriter.print(" <param name=\"");
325        outputWriter.print(name);
326        outputWriter.print("\" value=\"");
327        outputWriter.print(StringTools.xmlEntityReplacement(val));
328        outputWriter.println("\"/>");
329    }
330
331
332    /**
333     * <p>
334     * used to calculate a log file name. If the provided index is smaller 0, then no index
335     * is added to the file name. A filename is e.g. "htmlmonitor_12345_001.log".
336     * </p>
337     *
338     * @param index the index of the log file or -1 one, if no index shall be added
339     *
340     * @return the file name as described
341     */
342    private String getLogFileName(String clientId, int index) {
343        String result = "htmlmonitor_" + clientId;
344       
345        if (index >= 0) {
346            result += "_" + new DecimalFormat("000" ).format(index);
347        }
348       
349        result += ".log";
350       
351        return result;
352    }
353   
354    /**
355     * <p>
356     * this class is used internally for storing events in a sorted list together with the
357     * timestamps, being the sort criteria.
358     * </p>
359     */
360    private class EventEntry {
361       
362        /**
363         * <p>
364         * the type of the event
365         * </p>
366         */
367        private String type;
368       
369        /**
370         * <p>
371         * the parameters of the event
372         * </p>
373         */
374        private Map<String, String> parameters;
375       
376        /**
377         * <p>
378         * the timestamp of the event
379         * </p>
380         */
381        private long timestamp;
382
383        /**
384         * <p>
385         * creates a new event entry with event type, parameters and the timestamp
386         * </p>
387         */
388        private EventEntry(String type, Map<String, String> parameters, long timestamp) {
389            this.type = type;
390            this.parameters = parameters;
391            this.timestamp = timestamp;
392        }
393       
394        /**
395         * <p>
396         * convenience method for dumping the event into the compressed log file
397         * </p>
398         */
399        private void dump() {
400            outputWriter.print("<event type=\"");
401            outputWriter.print(type);
402            outputWriter.println("\">");
403           
404            for (Map.Entry<String, String> param : parameters.entrySet()) {
405                dumpParam(param.getKey(), param.getValue());
406            }
407           
408            outputWriter.println("</event>");
409        }
410    }
411}
Note: See TracBrowser for help on using the repository browser.