buildframework/helium/sf/java/legacy/src/com/nokia/ant/XmlLogger.java
changeset 587 85df38eb4012
child 588 c7c26511138f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/buildframework/helium/sf/java/legacy/src/com/nokia/ant/XmlLogger.java	Tue Apr 27 08:33:08 2010 +0300
@@ -0,0 +1,409 @@
+/*
+* Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of the License "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+*
+*/
+ 
+package com.nokia.ant;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintStream;
+import java.io.Writer;
+import java.util.Hashtable;
+import java.util.Stack;
+import java.util.Enumeration;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.apache.tools.ant.util.DOMElementWriter;
+import org.apache.tools.ant.util.StringUtils;
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.util.DateUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Text;
+
+/**
+ * Generates a file in the current directory with
+ * an XML description of what happened during a build.
+ * The default filename is "log.xml", but this can be overridden
+ * with the property <code>XmlLogger.file</code>.
+ *
+ * This implementation assumes in its sanity checking that only one
+ * thread runs a particular target/task at a time. This is enforced
+ * by the way that parallel builds and antcalls are done - and
+ * indeed all but the simplest of tasks could run into problems
+ * if executed in parallel.
+ *
+ * @see Project#addBuildListener(BuildListener)
+ */
+public class XmlLogger implements BuildLogger {
+
+    /** XML element name for a build. */
+    private static final String BUILD_TAG = "build";
+    /** XML element name for a target. */
+    private static final String TARGET_TAG = "target";
+    /** XML element name for a task. */
+    private static final String TASK_TAG = "task";
+    /** XML element name for a message. */
+    private static final String MESSAGE_TAG = "message";
+    /** XML attribute name for a name. */
+    private static final String NAME_ATTR = "name";
+    /** XML attribute name for a time. */
+    private static final String TIME_ATTR = "time";
+    /** XML attribute name for a message priority. */
+    private static final String PRIORITY_ATTR = "priority";
+    /** XML attribute name for a file location. */
+    private static final String LOCATION_ATTR = "location";
+    /** XML attribute name for an error description. */
+    private static final String ERROR_ATTR = "error";
+    /** XML element name for a stack trace. */
+    private static final String STACKTRACE_TAG = "stacktrace";
+    
+    /** DocumentBuilder to use when creating the document to start with. */
+    private static DocumentBuilder builder = getDocumentBuilder();
+    
+    private int msgOutputLevel = Project.MSG_ERR;
+    private PrintStream outStream;
+    
+    /** The complete log document for this build. */
+    private Document doc = builder.newDocument();
+    /** Mapping for when tasks started (Task to TimedElement). */
+    private Hashtable tasks = new Hashtable();
+    /** Mapping for when targets started (Task to TimedElement). */
+    private Hashtable targets = new Hashtable();
+    /**
+     * Mapping of threads to stacks of elements
+     * (Thread to Stack of TimedElement).
+     */
+    private Hashtable<Thread, Stack> threadStacks = new Hashtable<Thread, Stack>();
+    /**
+     * When the build started.
+     */
+    private TimedElement buildElement;
+    
+    /**
+     *  Constructs a new BuildListener that logs build events to an XML file.
+     */
+    public XmlLogger() {
+    }
+    
+    /**
+     * Returns a default DocumentBuilder instance or throws an
+     * ExceptionInInitializerError if it can't be created.
+     *
+     * @return a default DocumentBuilder instance.
+     */
+    private static DocumentBuilder getDocumentBuilder() {
+        try {
+            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        } catch (ParserConfigurationException exc) {
+            throw new ExceptionInInitializerError(exc.getMessage());
+        }
+    }
+
+    /** Utility class representing the time an element started. */
+    private static class TimedElement {
+        /**
+         * Start time in milliseconds
+         * (as returned by <code>System.currentTimeMillis()</code>).
+         */
+        private long startTime;
+        /** Element created at the start time. */
+        private Element element;
+        public String toString() {
+            return element.getTagName() + ":" + element.getAttribute("name");
+        }
+    }
+
+    /**
+     * Fired when the build starts, this builds the top-level element for the
+     * document and remembers the time of the start of the build.
+     *
+     * @param event Ignored.
+     */
+    public void buildStarted(BuildEvent event) {
+        buildElement = new TimedElement();
+        buildElement.startTime = System.currentTimeMillis();
+        buildElement.element = doc.createElement(BUILD_TAG);
+    }
+
+    /**
+     * Fired when the build finishes, this adds the time taken and any
+     * error stacktrace to the build element and writes the document to disk.
+     *
+     * @param event An event with any relevant extra information.
+     *              Will not be <code>null</code>.
+     */
+    public void buildFinished(BuildEvent event) {
+        long totalTime = System.currentTimeMillis() - buildElement.startTime;
+        buildElement.element.setAttribute(TIME_ATTR,
+                DateUtils.formatElapsedTime(totalTime));
+
+        if (event.getException() != null) {
+            buildElement.element.setAttribute(ERROR_ATTR,
+                    event.getException().toString());
+            // print the stacktrace in the build file it is always useful...
+            // better have too much info than not enough.
+            Throwable t = event.getException();
+            Text errText = doc.createCDATASection(StringUtils.getStackTrace(t));
+            Element stacktrace = doc.createElement(STACKTRACE_TAG);
+            stacktrace.appendChild(errText);
+            buildElement.element.appendChild(stacktrace);
+        }
+
+        String outFilename = event.getProject().getProperty("XmlLogger.file");
+        if (outFilename == null) {
+            outFilename = "log.xml";
+        }
+        String xslUri
+                = event.getProject().getProperty("ant.XmlLogger.stylesheet.uri");
+        if (xslUri == null) {
+            xslUri = "log.xsl";
+        }
+        Writer out = null;
+        try {
+            // specify output in UTF8 otherwise accented characters will blow
+            // up everything
+            OutputStream stream = outStream;
+            if (stream == null) {
+                stream = new FileOutputStream(outFilename);
+            }
+            out = new OutputStreamWriter(stream, "UTF8");
+            out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+            if (xslUri.length() > 0) {
+                out.write("<?xml-stylesheet type=\"text/xsl\" href=\""
+                        + xslUri + "\"?>\n\n");
+            }
+            (new DOMElementWriter()).write(buildElement.element, out, 0, "\t");
+            out.flush();
+        } catch (IOException exc) {
+            throw new BuildException("Unable to write log file" + exc.getMessage());
+        } finally {
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    // We are Ignoring the errors as no need to fail the build.
+                    event.getProject().log("Not able to close the file handler " + e.getMessage(), Project.MSG_WARN);
+                    e = null; // ignore
+                }
+            }
+        }
+        buildElement = null;
+    }
+
+    /**
+     * Returns the stack of timed elements for the current thread.
+     * @return the stack of timed elements for the current thread
+     */
+    private Stack getStack() {
+        Stack threadStack = threadStacks.get(Thread.currentThread());
+        if (threadStack == null) {
+            threadStack = new Stack();
+            threadStacks.put(Thread.currentThread(), threadStack);
+        }
+        /* For debugging purposes uncomment:
+        org.w3c.dom.Comment s = doc.createComment("stack=" + threadStack);
+        buildElement.element.appendChild(s);
+         */
+        return threadStack;
+    }
+
+    /**
+     * Fired when a target starts building, this pushes a timed element
+     * for the target onto the stack of elements for the current thread,
+     * remembering the current time and the name of the target.
+     *
+     * @param event An event with any relevant extra information.
+     *              Will not be <code>null</code>.
+     */
+    public void targetStarted(BuildEvent event) {
+    }
+
+    /**
+     * Fired when a target finishes building, this adds the time taken
+     * and any error stacktrace to the appropriate target element in the log.
+     *
+     * @param event An event with any relevant extra information.
+     *              Will not be <code>null</code>.
+     */
+    public void targetFinished(BuildEvent event) {
+    }
+
+    /**
+     * Fired when a task starts building, this pushes a timed element
+     * for the task onto the stack of elements for the current thread,
+     * remembering the current time and the name of the task.
+     *
+     * @param event An event with any relevant extra information.
+     *              Will not be <code>null</code>.
+     */
+    public void taskStarted(BuildEvent event) {
+    }
+
+    /**
+     * Fired when a task finishes building, this adds the time taken
+     * and any error stacktrace to the appropriate task element in the log.
+     *
+     * @param event An event with any relevant extra information.
+     *              Will not be <code>null</code>.
+     */
+    public void taskFinished(BuildEvent event) {
+    }
+
+
+    /**
+     * Get the TimedElement associated with a task.
+     *
+     * Where the task is not found directly, search for unknown elements which
+     * may be hiding the real task
+     */
+    private TimedElement getTaskElement(Task task) {
+        TimedElement element = (TimedElement) tasks.get(task);
+        if (element != null) {
+            return element;
+        }
+
+        for (Enumeration e = tasks.keys(); e.hasMoreElements();) {
+            Task key = (Task) e.nextElement();
+            if (key instanceof UnknownElement) {
+                if (((UnknownElement) key).getTask() == task) {
+                    return (TimedElement) tasks.get(key);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Fired when a message is logged, this adds a message element to the
+     * most appropriate parent element (task, target or build) and records
+     * the priority and text of the message.
+     *
+     * @param event An event with any relevant extra information.
+     *              Will not be <code>null</code>.
+     */
+    public void messageLogged(BuildEvent event) {
+        int priority = event.getPriority();
+        if (priority > msgOutputLevel) {
+            return;
+        }
+        Element messageElement = doc.createElement(MESSAGE_TAG);
+
+        String name = "debug";
+        switch (event.getPriority()) {
+            case Project.MSG_ERR:
+                name = "error";
+                break;
+            case Project.MSG_WARN:
+                name = "warn";
+                break;
+            case Project.MSG_INFO:
+                name = "info";
+                break;
+            default:
+                name = "debug";
+                break;
+        }
+        messageElement.setAttribute(PRIORITY_ATTR, name);
+
+        Throwable ex = event.getException();
+        if (Project.MSG_DEBUG <= msgOutputLevel && ex != null) {
+            Text errText = doc.createCDATASection(StringUtils.getStackTrace(ex));
+            Element stacktrace = doc.createElement(STACKTRACE_TAG);
+            stacktrace.appendChild(errText);
+            buildElement.element.appendChild(stacktrace);
+        }
+        Text messageText = doc.createCDATASection(event.getMessage());
+        messageElement.appendChild(messageText);
+
+        TimedElement parentElement = null;
+
+        Task task = event.getTask();
+
+        Target target = event.getTarget();
+        if (task != null) {
+            parentElement = getTaskElement(task);
+        }
+        if (parentElement == null && target != null) {
+            parentElement = (TimedElement) targets.get(target);
+        }
+
+        /*
+        if (parentElement == null) {
+            Stack threadStack
+                    = (Stack) threadStacks.get(Thread.currentThread());
+            if (threadStack != null) {
+                if (!threadStack.empty()) {
+                    parentElement = (TimedElement) threadStack.peek();
+                }
+            }
+        }
+        */
+
+        if (parentElement != null) {
+            parentElement.element.appendChild(messageElement);
+        } else {
+            buildElement.element.appendChild(messageElement);
+        }
+    }
+
+    // -------------------------------------------------- BuildLogger interface
+
+    /**
+     * Set the logging level when using this as a Logger
+     *
+     * @param level the logging level -
+     *        see {@link org.apache.tools.ant.Project#MSG_ERR Project}
+     *        class for level definitions
+     */
+    public void setMessageOutputLevel(int level) {
+        msgOutputLevel = level;
+    }
+
+    /**
+     * Set the output stream to which logging output is sent when operating
+     * as a logger.
+     *
+     * @param output the output PrintStream.
+     */
+    public void setOutputPrintStream(PrintStream output) {
+        this.outStream = new PrintStream(output, true);
+    }
+
+    /**
+     * Ignore emacs mode, as it has no meaning in XML format
+     *
+     * @param emacsMode true if logger should produce emacs compatible
+     *        output
+     */
+    public void setEmacsMode(boolean emacsMode) {
+    }
+
+    /**
+     * Ignore error print stream. All output will be written to
+     * either the XML log file or the PrintStream provided to
+     * setOutputPrintStream
+     *
+     * @param err the stream we are going to ignore.
+     */
+    public void setErrorPrintStream(PrintStream err) {
+    }
+
+}