diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/XmlLogger.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/XmlLogger.java | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/XmlLogger.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/XmlLogger.java new file mode 100644 index 00000000..a67a260e --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/XmlLogger.java @@ -0,0 +1,474 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.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.Enumeration; +import java.util.Hashtable; +import java.util.Stack; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.tools.ant.util.DOMElementWriter; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +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 { + + private int msgOutputLevel = Project.MSG_DEBUG; + private PrintStream outStream; + + /** DocumentBuilder to use when creating the document to start with. */ + private static DocumentBuilder builder = getDocumentBuilder(); + + /** + * 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 (Exception exc) { + throw new ExceptionInInitializerError(exc); + } + } + + /** 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"; + + /** The complete log document for this build. */ + private Document doc = builder.newDocument(); + + /** Mapping for when tasks started (Task to TimedElement). */ + private Hashtable<Task, TimedElement> tasks = new Hashtable<Task, TimedElement>(); + + /** Mapping for when targets started (Target to TimedElement). */ + private Hashtable<Target, TimedElement> targets = new Hashtable<Target, XmlLogger.TimedElement>(); + + /** + * Mapping of threads to stacks of elements + * (Thread to Stack of TimedElement). + */ + private Hashtable<Thread, Stack<TimedElement>> threadStacks = new Hashtable<Thread, Stack<TimedElement>>(); + + /** + * When the build started. + */ + private TimedElement buildElement = null; + + /** 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"); + } + } + + /** + * Constructs a new BuildListener that logs build events to an XML file. + */ + public XmlLogger() { + } + + /** + * 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, DefaultLogger.formatTime(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); + synchronizedAppend(buildElement.element, stacktrace); + } + String outFilename = getProperty(event, "XmlLogger.file", "log.xml"); + String xslUri = getProperty(event, "ant.XmlLogger.stylesheet.uri", "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); + } finally { + FileUtils.close(out); + } + buildElement = null; + } + + private String getProperty(BuildEvent event, String propertyName, String defaultValue) { + String rv = defaultValue; + if (event != null && event.getProject() != null && event.getProject().getProperty(propertyName) != null) { + rv = event.getProject().getProperty(propertyName); + } + return rv; + } + + /** + * Returns the stack of timed elements for the current thread. + * @return the stack of timed elements for the current thread + */ + private Stack<TimedElement> getStack() { + Stack<TimedElement> threadStack = threadStacks.get(Thread.currentThread()); + if (threadStack == null) { + threadStack = new Stack<TimedElement>(); + 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) { + Target target = event.getTarget(); + TimedElement targetElement = new TimedElement(); + targetElement.startTime = System.currentTimeMillis(); + targetElement.element = doc.createElement(TARGET_TAG); + targetElement.element.setAttribute(NAME_ATTR, target.getName()); + targets.put(target, targetElement); + getStack().push(targetElement); + } + + /** + * 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) { + Target target = event.getTarget(); + TimedElement targetElement = targets.get(target); + if (targetElement != null) { + long totalTime = System.currentTimeMillis() - targetElement.startTime; + targetElement.element.setAttribute(TIME_ATTR, DefaultLogger.formatTime(totalTime)); + + TimedElement parentElement = null; + Stack<TimedElement> threadStack = getStack(); + if (!threadStack.empty()) { + TimedElement poppedStack = threadStack.pop(); + if (poppedStack != targetElement) { + throw new RuntimeException("Mismatch - popped element = " + poppedStack + + " finished target element = " + targetElement); + } + if (!threadStack.empty()) { + parentElement = threadStack.peek(); + } + } + if (parentElement == null) { + synchronizedAppend(buildElement.element, targetElement.element); + } else { + synchronizedAppend(parentElement.element, + targetElement.element); + } + } + targets.remove(target); + } + + /** + * 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) { + TimedElement taskElement = new TimedElement(); + taskElement.startTime = System.currentTimeMillis(); + taskElement.element = doc.createElement(TASK_TAG); + + Task task = event.getTask(); + String name = event.getTask().getTaskName(); + if (name == null) { + name = ""; + } + taskElement.element.setAttribute(NAME_ATTR, name); + taskElement.element.setAttribute(LOCATION_ATTR, event.getTask().getLocation().toString()); + tasks.put(task, taskElement); + getStack().push(taskElement); + } + + /** + * 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) { + Task task = event.getTask(); + TimedElement taskElement = tasks.get(task); + if (taskElement == null) { + throw new RuntimeException("Unknown task " + task + " not in " + tasks); + } + long totalTime = System.currentTimeMillis() - taskElement.startTime; + taskElement.element.setAttribute(TIME_ATTR, DefaultLogger.formatTime(totalTime)); + Target target = task.getOwningTarget(); + TimedElement targetElement = null; + if (target != null) { + targetElement = targets.get(target); + } + if (targetElement == null) { + synchronizedAppend(buildElement.element, taskElement.element); + } else { + synchronizedAppend(targetElement.element, taskElement.element); + } + Stack<TimedElement> threadStack = getStack(); + if (!threadStack.empty()) { + TimedElement poppedStack = threadStack.pop(); + if (poppedStack != taskElement) { + throw new RuntimeException("Mismatch - popped element = " + poppedStack + + " finished task element = " + taskElement); + } + } + tasks.remove(task); + } + + /** + * 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 = tasks.get(task); + if (element != null) { + return element; + } + for (Enumeration<Task> e = tasks.keys(); e.hasMoreElements();) { + Task key = e.nextElement(); + if (key instanceof UnknownElement) { + if (((UnknownElement) key).getTask() == task) { + return 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 (priority) { + 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); + synchronizedAppend(buildElement.element, 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 = targets.get(target); + } + if (parentElement != null) { + synchronizedAppend(parentElement.element, messageElement); + } else { + synchronizedAppend(buildElement.element, 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) { + } + + private void synchronizedAppend(Node parent, Node child) { + synchronized(parent) { + parent.appendChild(child); + } + } + +} |