KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > XmlLogger


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant;
20
21 import java.io.FileOutputStream JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.OutputStream JavaDoc;
24 import java.io.OutputStreamWriter JavaDoc;
25 import java.io.PrintStream JavaDoc;
26 import java.io.Writer JavaDoc;
27 import java.util.Hashtable JavaDoc;
28 import java.util.Stack JavaDoc;
29 import java.util.Enumeration JavaDoc;
30 import javax.xml.parsers.DocumentBuilder JavaDoc;
31 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
32 import org.apache.tools.ant.util.DOMElementWriter;
33 import org.apache.tools.ant.util.StringUtils;
34 import org.w3c.dom.Document JavaDoc;
35 import org.w3c.dom.Element JavaDoc;
36 import org.w3c.dom.Text JavaDoc;
37
38 /**
39  * Generates a file in the current directory with
40  * an XML description of what happened during a build.
41  * The default filename is "log.xml", but this can be overridden
42  * with the property <code>XmlLogger.file</code>.
43  *
44  * This implementation assumes in its sanity checking that only one
45  * thread runs a particular target/task at a time. This is enforced
46  * by the way that parallel builds and antcalls are done - and
47  * indeed all but the simplest of tasks could run into problems
48  * if executed in parallel.
49  *
50  * @see Project#addBuildListener(BuildListener)
51  */

52 public class XmlLogger implements BuildLogger {
53
54     private int msgOutputLevel = Project.MSG_DEBUG;
55     private PrintStream JavaDoc outStream;
56
57     /** DocumentBuilder to use when creating the document to start with. */
58     private static DocumentBuilder JavaDoc builder = getDocumentBuilder();
59
60     /**
61      * Returns a default DocumentBuilder instance or throws an
62      * ExceptionInInitializerError if it can't be created.
63      *
64      * @return a default DocumentBuilder instance.
65      */

66     private static DocumentBuilder JavaDoc getDocumentBuilder() {
67         try {
68             return DocumentBuilderFactory.newInstance().newDocumentBuilder();
69         } catch (Exception JavaDoc exc) {
70             throw new ExceptionInInitializerError JavaDoc(exc);
71         }
72     }
73
74     /** XML element name for a build. */
75     private static final String JavaDoc BUILD_TAG = "build";
76     /** XML element name for a target. */
77     private static final String JavaDoc TARGET_TAG = "target";
78     /** XML element name for a task. */
79     private static final String JavaDoc TASK_TAG = "task";
80     /** XML element name for a message. */
81     private static final String JavaDoc MESSAGE_TAG = "message";
82     /** XML attribute name for a name. */
83     private static final String JavaDoc NAME_ATTR = "name";
84     /** XML attribute name for a time. */
85     private static final String JavaDoc TIME_ATTR = "time";
86     /** XML attribute name for a message priority. */
87     private static final String JavaDoc PRIORITY_ATTR = "priority";
88     /** XML attribute name for a file location. */
89     private static final String JavaDoc LOCATION_ATTR = "location";
90     /** XML attribute name for an error description. */
91     private static final String JavaDoc ERROR_ATTR = "error";
92     /** XML element name for a stack trace. */
93     private static final String JavaDoc STACKTRACE_TAG = "stacktrace";
94
95     /** The complete log document for this build. */
96     private Document JavaDoc doc = builder.newDocument();
97     /** Mapping for when tasks started (Task to TimedElement). */
98     private Hashtable JavaDoc tasks = new Hashtable JavaDoc();
99     /** Mapping for when targets started (Task to TimedElement). */
100     private Hashtable JavaDoc targets = new Hashtable JavaDoc();
101     /**
102      * Mapping of threads to stacks of elements
103      * (Thread to Stack of TimedElement).
104      */

105     private Hashtable JavaDoc threadStacks = new Hashtable JavaDoc();
106     /**
107      * When the build started.
108      */

109     private TimedElement buildElement = null;
110
111     /** Utility class representing the time an element started. */
112     private static class TimedElement {
113         /**
114          * Start time in milliseconds
115          * (as returned by <code>System.currentTimeMillis()</code>).
116          */

117         private long startTime;
118         /** Element created at the start time. */
119         private Element JavaDoc element;
120         public String JavaDoc toString() {
121             return element.getTagName() + ":" + element.getAttribute("name");
122         }
123     }
124
125     /**
126      * Constructs a new BuildListener that logs build events to an XML file.
127      */

128     public XmlLogger() {
129     }
130
131     /**
132      * Fired when the build starts, this builds the top-level element for the
133      * document and remembers the time of the start of the build.
134      *
135      * @param event Ignored.
136      */

137     public void buildStarted(BuildEvent event) {
138         buildElement = new TimedElement();
139         buildElement.startTime = System.currentTimeMillis();
140         buildElement.element = doc.createElement(BUILD_TAG);
141     }
142
143     /**
144      * Fired when the build finishes, this adds the time taken and any
145      * error stacktrace to the build element and writes the document to disk.
146      *
147      * @param event An event with any relevant extra information.
148      * Will not be <code>null</code>.
149      */

150     public void buildFinished(BuildEvent event) {
151         long totalTime = System.currentTimeMillis() - buildElement.startTime;
152         buildElement.element.setAttribute(TIME_ATTR,
153                 DefaultLogger.formatTime(totalTime));
154
155         if (event.getException() != null) {
156             buildElement.element.setAttribute(ERROR_ATTR,
157                     event.getException().toString());
158             // print the stacktrace in the build file it is always useful...
159
// better have too much info than not enough.
160
Throwable JavaDoc t = event.getException();
161             Text JavaDoc errText = doc.createCDATASection(StringUtils.getStackTrace(t));
162             Element JavaDoc stacktrace = doc.createElement(STACKTRACE_TAG);
163             stacktrace.appendChild(errText);
164             buildElement.element.appendChild(stacktrace);
165         }
166
167         String JavaDoc outFilename = event.getProject().getProperty("XmlLogger.file");
168         if (outFilename == null) {
169             outFilename = "log.xml";
170         }
171         String JavaDoc xslUri
172                 = event.getProject().getProperty("ant.XmlLogger.stylesheet.uri");
173         if (xslUri == null) {
174             xslUri = "log.xsl";
175         }
176         Writer JavaDoc out = null;
177         try {
178             // specify output in UTF8 otherwise accented characters will blow
179
// up everything
180
OutputStream JavaDoc stream = outStream;
181             if (stream == null) {
182                 stream = new FileOutputStream JavaDoc(outFilename);
183             }
184             out = new OutputStreamWriter JavaDoc(stream, "UTF8");
185             out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
186             if (xslUri.length() > 0) {
187                 out.write("<?xml-stylesheet type=\"text/xsl\" HREF=\""
188                         + xslUri + "\"?>\n\n");
189             }
190             (new DOMElementWriter()).write(buildElement.element, out, 0, "\t");
191             out.flush();
192         } catch (IOException JavaDoc exc) {
193             throw new BuildException("Unable to write log file", exc);
194         } finally {
195             if (out != null) {
196                 try {
197                     out.close();
198                 } catch (IOException JavaDoc e) {
199                     // ignore
200
}
201             }
202         }
203         buildElement = null;
204     }
205
206     /**
207      * Returns the stack of timed elements for the current thread.
208      * @return the stack of timed elements for the current thread
209      */

210     private Stack JavaDoc getStack() {
211         Stack JavaDoc threadStack = (Stack JavaDoc) threadStacks.get(Thread.currentThread());
212         if (threadStack == null) {
213             threadStack = new Stack JavaDoc();
214             threadStacks.put(Thread.currentThread(), threadStack);
215         }
216         /* For debugging purposes uncomment:
217         org.w3c.dom.Comment s = doc.createComment("stack=" + threadStack);
218         buildElement.element.appendChild(s);
219          */

220         return threadStack;
221     }
222
223     /**
224      * Fired when a target starts building, this pushes a timed element
225      * for the target onto the stack of elements for the current thread,
226      * remembering the current time and the name of the target.
227      *
228      * @param event An event with any relevant extra information.
229      * Will not be <code>null</code>.
230      */

231     public void targetStarted(BuildEvent event) {
232         Target target = event.getTarget();
233         TimedElement targetElement = new TimedElement();
234         targetElement.startTime = System.currentTimeMillis();
235         targetElement.element = doc.createElement(TARGET_TAG);
236         targetElement.element.setAttribute(NAME_ATTR, target.getName());
237         targets.put(target, targetElement);
238         getStack().push(targetElement);
239     }
240
241     /**
242      * Fired when a target finishes building, this adds the time taken
243      * and any error stacktrace to the appropriate target element in the log.
244      *
245      * @param event An event with any relevant extra information.
246      * Will not be <code>null</code>.
247      */

248     public void targetFinished(BuildEvent event) {
249         Target target = event.getTarget();
250         TimedElement targetElement = (TimedElement) targets.get(target);
251         if (targetElement != null) {
252             long totalTime
253                     = System.currentTimeMillis() - targetElement.startTime;
254             targetElement.element.setAttribute(TIME_ATTR,
255                     DefaultLogger.formatTime(totalTime));
256
257             TimedElement parentElement = null;
258             Stack JavaDoc threadStack = getStack();
259             if (!threadStack.empty()) {
260                 TimedElement poppedStack = (TimedElement) threadStack.pop();
261                 if (poppedStack != targetElement) {
262                     throw new RuntimeException JavaDoc("Mismatch - popped element = "
263                             + poppedStack
264                             + " finished target element = "
265                             + targetElement);
266                 }
267                 if (!threadStack.empty()) {
268                     parentElement = (TimedElement) threadStack.peek();
269                 }
270             }
271             if (parentElement == null) {
272                 buildElement.element.appendChild(targetElement.element);
273             } else {
274                 parentElement.element.appendChild(targetElement.element);
275             }
276         }
277         targets.remove(target);
278     }
279
280     /**
281      * Fired when a task starts building, this pushes a timed element
282      * for the task onto the stack of elements for the current thread,
283      * remembering the current time and the name of the task.
284      *
285      * @param event An event with any relevant extra information.
286      * Will not be <code>null</code>.
287      */

288     public void taskStarted(BuildEvent event) {
289         TimedElement taskElement = new TimedElement();
290         taskElement.startTime = System.currentTimeMillis();
291         taskElement.element = doc.createElement(TASK_TAG);
292
293         Task task = event.getTask();
294         String JavaDoc name = event.getTask().getTaskName();
295         if (name == null) {
296             name = "";
297         }
298         taskElement.element.setAttribute(NAME_ATTR, name);
299         taskElement.element.setAttribute(LOCATION_ATTR,
300                 event.getTask().getLocation().toString());
301         tasks.put(task, taskElement);
302         getStack().push(taskElement);
303     }
304
305     /**
306      * Fired when a task finishes building, this adds the time taken
307      * and any error stacktrace to the appropriate task element in the log.
308      *
309      * @param event An event with any relevant extra information.
310      * Will not be <code>null</code>.
311      */

312     public void taskFinished(BuildEvent event) {
313         Task task = event.getTask();
314         TimedElement taskElement = (TimedElement) tasks.get(task);
315         if (taskElement != null) {
316             long totalTime = System.currentTimeMillis() - taskElement.startTime;
317             taskElement.element.setAttribute(TIME_ATTR,
318                     DefaultLogger.formatTime(totalTime));
319             Target target = task.getOwningTarget();
320             TimedElement targetElement = null;
321             if (target != null) {
322                 targetElement = (TimedElement) targets.get(target);
323             }
324             if (targetElement == null) {
325                 buildElement.element.appendChild(taskElement.element);
326             } else {
327                 targetElement.element.appendChild(taskElement.element);
328             }
329             Stack JavaDoc threadStack = getStack();
330             if (!threadStack.empty()) {
331                 TimedElement poppedStack = (TimedElement) threadStack.pop();
332                 if (poppedStack != taskElement) {
333                     throw new RuntimeException JavaDoc("Mismatch - popped element = "
334                             + poppedStack + " finished task element = "
335                             + taskElement);
336                 }
337             }
338             tasks.remove(task);
339         } else {
340             throw new RuntimeException JavaDoc("Unknown task " + task + " not in " + tasks);
341         }
342     }
343
344
345     /**
346      * Get the TimedElement associated with a task.
347      *
348      * Where the task is not found directly, search for unknown elements which
349      * may be hiding the real task
350      */

351     private TimedElement getTaskElement(Task task) {
352         TimedElement element = (TimedElement) tasks.get(task);
353         if (element != null) {
354             return element;
355         }
356
357         for (Enumeration JavaDoc e = tasks.keys(); e.hasMoreElements();) {
358             Task key = (Task) e.nextElement();
359             if (key instanceof UnknownElement) {
360                 if (((UnknownElement) key).getTask() == task) {
361                     return (TimedElement) tasks.get(key);
362                 }
363             }
364         }
365
366         return null;
367     }
368
369     /**
370      * Fired when a message is logged, this adds a message element to the
371      * most appropriate parent element (task, target or build) and records
372      * the priority and text of the message.
373      *
374      * @param event An event with any relevant extra information.
375      * Will not be <code>null</code>.
376      */

377     public void messageLogged(BuildEvent event) {
378         int priority = event.getPriority();
379         if (priority > msgOutputLevel) {
380             return;
381         }
382         Element JavaDoc messageElement = doc.createElement(MESSAGE_TAG);
383
384         String JavaDoc name = "debug";
385         switch (event.getPriority()) {
386             case Project.MSG_ERR:
387                 name = "error";
388                 break;
389             case Project.MSG_WARN:
390                 name = "warn";
391                 break;
392             case Project.MSG_INFO:
393                 name = "info";
394                 break;
395             default:
396                 name = "debug";
397                 break;
398         }
399         messageElement.setAttribute(PRIORITY_ATTR, name);
400
401         Throwable JavaDoc ex = event.getException();
402         if (Project.MSG_DEBUG <= msgOutputLevel && ex != null) {
403             Text JavaDoc errText = doc.createCDATASection(StringUtils.getStackTrace(ex));
404             Element JavaDoc stacktrace = doc.createElement(STACKTRACE_TAG);
405             stacktrace.appendChild(errText);
406             buildElement.element.appendChild(stacktrace);
407         }
408         Text JavaDoc messageText = doc.createCDATASection(event.getMessage());
409         messageElement.appendChild(messageText);
410
411         TimedElement parentElement = null;
412
413         Task task = event.getTask();
414
415         Target target = event.getTarget();
416         if (task != null) {
417             parentElement = getTaskElement(task);
418         }
419         if (parentElement == null && target != null) {
420             parentElement = (TimedElement) targets.get(target);
421         }
422
423         /*
424         if (parentElement == null) {
425             Stack threadStack
426                     = (Stack) threadStacks.get(Thread.currentThread());
427             if (threadStack != null) {
428                 if (!threadStack.empty()) {
429                     parentElement = (TimedElement) threadStack.peek();
430                 }
431             }
432         }
433         */

434
435         if (parentElement != null) {
436             parentElement.element.appendChild(messageElement);
437         } else {
438             buildElement.element.appendChild(messageElement);
439         }
440     }
441
442     // -------------------------------------------------- BuildLogger interface
443

444     /**
445      * Set the logging level when using this as a Logger
446      *
447      * @param level the logging level -
448      * see {@link org.apache.tools.ant.Project#MSG_ERR Project}
449      * class for level definitions
450      */

451     public void setMessageOutputLevel(int level) {
452         msgOutputLevel = level;
453     }
454
455     /**
456      * Set the output stream to which logging output is sent when operating
457      * as a logger.
458      *
459      * @param output the output PrintStream.
460      */

461     public void setOutputPrintStream(PrintStream JavaDoc output) {
462         this.outStream = new PrintStream JavaDoc(output, true);
463     }
464
465     /**
466      * Ignore emacs mode, as it has no meaning in XML format
467      *
468      * @param emacsMode true if logger should produce emacs compatible
469      * output
470      */

471     public void setEmacsMode(boolean emacsMode) {
472     }
473
474     /**
475      * Ignore error print stream. All output will be written to
476      * either the XML log file or the PrintStream provided to
477      * setOutputPrintStream
478      *
479      * @param err the stream we are going to ignore.
480      */

481     public void setErrorPrintStream(PrintStream JavaDoc err) {
482     }
483
484 }
485
Popular Tags