Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 493   Methods: 15
NCLOC: 252   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
XmlLogger.java 0% 0% 0% 0%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 
 55   
 package org.apache.tools.ant;
 56   
 
 57   
 import java.io.FileOutputStream;
 58   
 import java.io.IOException;
 59   
 import java.io.OutputStream;
 60   
 import java.io.OutputStreamWriter;
 61   
 import java.io.PrintStream;
 62   
 import java.io.Writer;
 63   
 import java.util.Hashtable;
 64   
 import java.util.Stack;
 65   
 import java.util.Enumeration;
 66   
 import javax.xml.parsers.DocumentBuilder;
 67   
 import javax.xml.parsers.DocumentBuilderFactory;
 68   
 import org.apache.tools.ant.util.DOMElementWriter;
 69   
 import org.apache.tools.ant.util.StringUtils;
 70   
 import org.w3c.dom.Document;
 71   
 import org.w3c.dom.Element;
 72   
 import org.w3c.dom.Text;
 73   
 
 74   
 /**
 75   
  * Generates a file in the current directory with
 76   
  * an XML description of what happened during a build.
 77   
  * The default filename is "log.xml", but this can be overridden
 78   
  * with the property <code>XmlLogger.file</code>.
 79   
  *
 80   
  * This implementation assumes in its sanity checking that only one
 81   
  * thread runs a particular target/task at a time. This is enforced
 82   
  * by the way that parallel builds and antcalls are done - and
 83   
  * indeed all but the simplest of tasks could run into problems
 84   
  * if executed in parallel.
 85   
  *
 86   
  * @see Project#addBuildListener(BuildListener)
 87   
  */
 88   
 public class XmlLogger implements BuildLogger {
 89   
 
 90   
     private int msgOutputLevel = Project.MSG_DEBUG;
 91   
     private PrintStream outStream;
 92   
 
 93   
     /** DocumentBuilder to use when creating the document to start with. */
 94   
     private static final DocumentBuilder builder = getDocumentBuilder();
 95   
 
 96   
     /**
 97   
      * Returns a default DocumentBuilder instance or throws an
 98   
      * ExceptionInInitializerError if it can't be created.
 99   
      *
 100   
      * @return a default DocumentBuilder instance.
 101   
      */
 102  0
     private static DocumentBuilder getDocumentBuilder() {
 103  0
         try {
 104  0
             return DocumentBuilderFactory.newInstance().newDocumentBuilder();
 105   
         } catch (Exception exc) {
 106  0
             throw new ExceptionInInitializerError(exc);
 107   
         }
 108   
     }
 109   
 
 110   
     /** XML element name for a build. */
 111   
     private static final String BUILD_TAG = "build";
 112   
     /** XML element name for a target. */
 113   
     private static final String TARGET_TAG = "target";
 114   
     /** XML element name for a task. */
 115   
     private static final String TASK_TAG = "task";
 116   
     /** XML element name for a message. */
 117   
     private static final String MESSAGE_TAG = "message";
 118   
     /** XML attribute name for a name. */
 119   
     private static final String NAME_ATTR = "name";
 120   
     /** XML attribute name for a time. */
 121   
     private static final String TIME_ATTR = "time";
 122   
     /** XML attribute name for a message priority. */
 123   
     private static final String PRIORITY_ATTR = "priority";
 124   
     /** XML attribute name for a file location. */
 125   
     private static final String LOCATION_ATTR = "location";
 126   
     /** XML attribute name for an error description. */
 127   
     private static final String ERROR_ATTR = "error";
 128   
     /** XML element name for a stack trace. */
 129   
     private static final String STACKTRACE_TAG = "stacktrace";
 130   
 
 131   
     /** The complete log document for this build. */
 132   
     private Document doc = builder.newDocument();
 133   
     /** Mapping for when tasks started (Task to TimedElement). */
 134   
     private Hashtable tasks = new Hashtable();
 135   
     /** Mapping for when targets started (Task to TimedElement). */
 136   
     private Hashtable targets = new Hashtable();
 137   
     /**
 138   
      * Mapping of threads to stacks of elements
 139   
      * (Thread to Stack of TimedElement).
 140   
      */
 141   
     private Hashtable threadStacks = new Hashtable();
 142   
     /**
 143   
      * When the build started.
 144   
      */
 145   
     private TimedElement buildElement = null;
 146   
 
 147   
     /** Utility class representing the time an element started. */
 148   
     private static class TimedElement {
 149   
         /**
 150   
          * Start time in milliseconds
 151   
          * (as returned by <code>System.currentTimeMillis()</code>).
 152   
          */
 153   
         private long startTime;
 154   
         /** Element created at the start time. */
 155   
         private Element element;
 156   
     }
 157   
 
 158   
     /**
 159   
      *  Constructs a new BuildListener that logs build events to an XML file.
 160   
      */
 161  0
     public XmlLogger() {
 162   
     }
 163   
 
 164   
     /**
 165   
      * Fired when the build starts, this builds the top-level element for the
 166   
      * document and remembers the time of the start of the build.
 167   
      *
 168   
      * @param event Ignored.
 169   
      */
 170  0
     public void buildStarted(BuildEvent event) {
 171  0
         buildElement = new TimedElement();
 172  0
         buildElement.startTime = System.currentTimeMillis();
 173  0
         buildElement.element = doc.createElement(BUILD_TAG);
 174   
     }
 175   
 
 176   
     /**
 177   
      * Fired when the build finishes, this adds the time taken and any
 178   
      * error stacktrace to the build element and writes the document to disk.
 179   
      *
 180   
      * @param event An event with any relevant extra information.
 181   
      *              Will not be <code>null</code>.
 182   
      */
 183  0
     public void buildFinished(BuildEvent event) {
 184  0
         long totalTime = System.currentTimeMillis() - buildElement.startTime;
 185  0
         buildElement.element.setAttribute(TIME_ATTR,
 186   
                 DefaultLogger.formatTime(totalTime));
 187   
 
 188  0
         if (event.getException() != null) {
 189  0
             buildElement.element.setAttribute(ERROR_ATTR,
 190   
                     event.getException().toString());
 191   
             // print the stacktrace in the build file it is always useful...
 192   
             // better have too much info than not enough.
 193  0
             Throwable t = event.getException();
 194  0
             Text errText = doc.createCDATASection(StringUtils.getStackTrace(t));
 195  0
             Element stacktrace = doc.createElement(STACKTRACE_TAG);
 196  0
             stacktrace.appendChild(errText);
 197  0
             buildElement.element.appendChild(stacktrace);
 198   
         }
 199   
 
 200  0
         String outFilename = event.getProject().getProperty("XmlLogger.file");
 201  0
         if (outFilename == null) {
 202  0
             outFilename = "log.xml";
 203   
         }
 204  0
         String xslUri
 205   
                 = event.getProject().getProperty("ant.XmlLogger.stylesheet.uri");
 206  0
         if (xslUri == null) {
 207  0
             xslUri = "log.xsl";
 208   
         }
 209  0
         Writer out = null;
 210  0
         try {
 211   
             // specify output in UTF8 otherwise accented characters will blow
 212   
             // up everything
 213  0
             OutputStream stream = outStream;
 214  0
             if (stream == null) {
 215  0
                 stream = new FileOutputStream(outFilename);
 216   
             }
 217  0
             out = new OutputStreamWriter(stream, "UTF8");
 218  0
             out.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
 219  0
             if (xslUri.length() > 0) {
 220  0
                 out.write("<?xml-stylesheet type=\"text/xsl\" href=\""
 221   
                         + xslUri + "\"?>\n\n");
 222   
             }
 223  0
             (new DOMElementWriter()).write(buildElement.element, out, 0, "\t");
 224  0
             out.flush();
 225   
         } catch (IOException exc) {
 226  0
             throw new BuildException("Unable to write log file", exc);
 227   
         } finally {
 228  0
             if (out != null) {
 229  0
                 try {
 230  0
                     out.close();
 231   
                 } catch (IOException e) {
 232   
                 }
 233   
             }
 234   
         }
 235  0
         buildElement = null;
 236   
     }
 237   
 
 238   
     /**
 239   
      * Returns the stack of timed elements for the current thread.
 240   
      * @return the stack of timed elements for the current thread
 241   
      */
 242  0
     private Stack getStack() {
 243  0
         Stack threadStack = (Stack) threadStacks.get(Thread.currentThread());
 244  0
         if (threadStack == null) {
 245  0
             threadStack = new Stack();
 246  0
             threadStacks.put(Thread.currentThread(), threadStack);
 247   
         }
 248  0
         return threadStack;
 249   
     }
 250   
 
 251   
     /**
 252   
      * Fired when a target starts building, this pushes a timed element
 253   
      * for the target onto the stack of elements for the current thread,
 254   
      * rememebering the current time and the name of the target.
 255   
      *
 256   
      * @param event An event with any relevant extra information.
 257   
      *              Will not be <code>null</code>.
 258   
      */
 259  0
     public void targetStarted(BuildEvent event) {
 260  0
         Target target = event.getTarget();
 261  0
         TimedElement targetElement = new TimedElement();
 262  0
         targetElement.startTime = System.currentTimeMillis();
 263  0
         targetElement.element = doc.createElement(TARGET_TAG);
 264  0
         targetElement.element.setAttribute(NAME_ATTR, target.getName());
 265  0
         targets.put(target, targetElement);
 266  0
         getStack().push(targetElement);
 267   
     }
 268   
 
 269   
     /**
 270   
      * Fired when a target finishes building, this adds the time taken
 271   
      * and any error stacktrace to the appropriate target element in the log.
 272   
      *
 273   
      * @param event An event with any relevant extra information.
 274   
      *              Will not be <code>null</code>.
 275   
      */
 276  0
     public void targetFinished(BuildEvent event) {
 277  0
         Target target = event.getTarget();
 278  0
         TimedElement targetElement = (TimedElement) targets.get(target);
 279  0
         if (targetElement != null) {
 280  0
             long totalTime
 281   
                     = System.currentTimeMillis() - targetElement.startTime;
 282  0
             targetElement.element.setAttribute(TIME_ATTR,
 283   
                     DefaultLogger.formatTime(totalTime));
 284   
 
 285  0
             TimedElement parentElement = null;
 286  0
             Stack threadStack = getStack();
 287  0
             if (!threadStack.empty()) {
 288  0
                 TimedElement poppedStack = (TimedElement) threadStack.pop();
 289  0
                 if (poppedStack != targetElement) {
 290  0
                     throw new RuntimeException("Mismatch - popped element = "
 291   
                             + poppedStack.element + " finished target element = "
 292   
                             + targetElement.element);
 293   
                 }
 294  0
                 if (!threadStack.empty()) {
 295  0
                     parentElement = (TimedElement) threadStack.peek();
 296   
                 }
 297   
             }
 298  0
             if (parentElement == null) {
 299  0
                 buildElement.element.appendChild(targetElement.element);
 300   
             } else {
 301  0
                 parentElement.element.appendChild(targetElement.element);
 302   
             }
 303   
         }
 304   
     }
 305   
 
 306   
     /**
 307   
      * Fired when a task starts building, this pushes a timed element
 308   
      * for the task onto the stack of elements for the current thread,
 309   
      * rememebering the current time and the name of the task.
 310   
      *
 311   
      * @param event An event with any relevant extra information.
 312   
      *              Will not be <code>null</code>.
 313   
      */
 314  0
     public void taskStarted(BuildEvent event) {
 315  0
         TimedElement taskElement = new TimedElement();
 316  0
         taskElement.startTime = System.currentTimeMillis();
 317  0
         taskElement.element = doc.createElement(TASK_TAG);
 318   
 
 319  0
         Task task = event.getTask();
 320  0
         String name = event.getTask().getTaskName();
 321  0
         taskElement.element.setAttribute(NAME_ATTR, name);
 322  0
         taskElement.element.setAttribute(LOCATION_ATTR,
 323   
                 event.getTask().getLocation().toString());
 324  0
         tasks.put(task, taskElement);
 325  0
         getStack().push(taskElement);
 326   
     }
 327   
 
 328   
     /**
 329   
      * Fired when a task finishes building, this adds the time taken
 330   
      * and any error stacktrace to the appropriate task element in the log.
 331   
      *
 332   
      * @param event An event with any relevant extra information.
 333   
      *              Will not be <code>null</code>.
 334   
      */
 335  0
     public void taskFinished(BuildEvent event) {
 336  0
         Task task = event.getTask();
 337  0
         TimedElement taskElement = (TimedElement) tasks.get(task);
 338  0
         if (taskElement != null) {
 339  0
             long totalTime = System.currentTimeMillis() - taskElement.startTime;
 340  0
             taskElement.element.setAttribute(TIME_ATTR,
 341   
                     DefaultLogger.formatTime(totalTime));
 342  0
             Target target = task.getOwningTarget();
 343  0
             TimedElement targetElement = null;
 344  0
             if (target != null) {
 345  0
                 targetElement = (TimedElement) targets.get(target);
 346   
             }
 347  0
             if (targetElement == null) {
 348  0
                 buildElement.element.appendChild(taskElement.element);
 349   
             } else {
 350  0
                 targetElement.element.appendChild(taskElement.element);
 351   
             }
 352  0
             Stack threadStack = getStack();
 353  0
             if (!threadStack.empty()) {
 354  0
                 TimedElement poppedStack = (TimedElement) threadStack.pop();
 355  0
                 if (poppedStack != taskElement) {
 356  0
                     throw new RuntimeException("Mismatch - popped element = "
 357   
                             + poppedStack.element + " finished task element = "
 358   
                             + taskElement.element);
 359   
                 }
 360   
             }
 361   
         }
 362   
     }
 363   
 
 364   
 
 365   
     /**
 366   
      * Get the TimedElement associated with a task.
 367   
      *
 368   
      * Where the task is not found directly, search for unknown elements which
 369   
      * may be hiding the real task
 370   
      */
 371  0
     private TimedElement getTaskElement(Task task) {
 372  0
         TimedElement element = (TimedElement) tasks.get(task);
 373  0
         if (element != null) {
 374  0
             return element;
 375   
         }
 376   
 
 377  0
         for (Enumeration e = tasks.keys(); e.hasMoreElements();) {
 378  0
             Task key = (Task) e.nextElement();
 379  0
             if (key instanceof UnknownElement) {
 380  0
                 if (((UnknownElement) key).getTask() == task) {
 381  0
                     return (TimedElement) tasks.get(key);
 382   
                 }
 383   
             }
 384   
         }
 385   
 
 386  0
         return null;
 387   
     }
 388   
 
 389   
     /**
 390   
      * Fired when a message is logged, this adds a message element to the
 391   
      * most appropriate parent element (task, target or build) and records
 392   
      * the priority and text of the message.
 393   
      *
 394   
      * @param event An event with any relevant extra information.
 395   
      *              Will not be <code>null</code>.
 396   
      */
 397  0
     public void messageLogged(BuildEvent event) {
 398  0
         int priority = event.getPriority();
 399  0
         if (priority > msgOutputLevel) {
 400  0
             return;
 401   
         }
 402  0
         Element messageElement = doc.createElement(MESSAGE_TAG);
 403   
 
 404  0
         String name = "debug";
 405  0
         switch (event.getPriority()) {
 406   
             case Project.MSG_ERR:
 407  0
                 name = "error";
 408  0
                 break;
 409   
             case Project.MSG_WARN:
 410  0
                 name = "warn";
 411  0
                 break;
 412   
             case Project.MSG_INFO:
 413  0
                 name = "info";
 414  0
                 break;
 415   
             default:
 416  0
                 name = "debug";
 417  0
                 break;
 418   
         }
 419  0
         messageElement.setAttribute(PRIORITY_ATTR, name);
 420   
 
 421  0
         Text messageText = doc.createCDATASection(event.getMessage());
 422  0
         messageElement.appendChild(messageText);
 423   
 
 424  0
         TimedElement parentElement = null;
 425   
 
 426  0
         Task task = event.getTask();
 427   
 
 428  0
         Target target = event.getTarget();
 429  0
         if (task != null) {
 430  0
             parentElement = getTaskElement(task);
 431   
         }
 432  0
         if (parentElement == null && target != null) {
 433  0
             parentElement = (TimedElement) targets.get(target);
 434   
         }
 435   
 
 436   
         /*
 437   
         if (parentElement == null) {
 438   
             Stack threadStack
 439   
                     = (Stack) threadStacks.get(Thread.currentThread());
 440   
             if (threadStack != null) {
 441   
                 if (!threadStack.empty()) {
 442   
                     parentElement = (TimedElement) threadStack.peek();
 443   
                 }
 444   
             }
 445   
         }
 446   
         */
 447   
 
 448  0
         if (parentElement != null) {
 449  0
             parentElement.element.appendChild(messageElement);
 450   
         } else {
 451  0
             buildElement.element.appendChild(messageElement);
 452   
         }
 453   
     }
 454   
 
 455   
     // -------------------------------------------------- BuildLogger interface
 456   
 
 457   
     /**
 458   
      * Set the logging level when using this as a Logger
 459   
      *
 460   
      * @param level the logging level -
 461   
      *        see {@link org.apache.tools.ant.Project#MSG_ERR Project}
 462   
      *        class for level definitions
 463   
      */
 464  0
     public void setMessageOutputLevel(int level) {
 465  0
         msgOutputLevel = level;
 466   
     }
 467   
 
 468   
     /**
 469   
      * Set the output stream to which logging output is sent when operating
 470   
      * as a logger.
 471   
      *
 472   
      * @param output the output PrintStream.
 473   
      */
 474  0
     public void setOutputPrintStream(PrintStream output) {
 475  0
         this.outStream = new PrintStream(output, true);
 476   
     }
 477   
 
 478   
     /**
 479   
      * Ignore emacs mode, as it has no meaning in XML format
 480   
      */
 481  0
     public void setEmacsMode(boolean emacsMode) {
 482   
     }
 483   
 
 484   
     /**
 485   
      * Ignore error print stream. All output will be written to
 486   
      * either the XML log file or the PrintStream provided to
 487   
      * setOutputPrintStream
 488   
      */
 489  0
     public void setErrorPrintStream(PrintStream err) {
 490   
     }
 491   
 
 492   
 }
 493