buildframework/helium/sf/java/legacy/src/com/nokia/ant/listener/CoverageRecorderEntry.java
changeset 628 7c4a911dc066
parent 588 c7c26511138f
equal deleted inserted replaced
588:c7c26511138f 628:7c4a911dc066
    18 
    18 
    19 /* Portion Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiary(-ies). All rights reserved. */
    19 /* Portion Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiary(-ies). All rights reserved. */
    20 
    20 
    21 package com.nokia.ant.listener;
    21 package com.nokia.ant.listener;
    22 
    22 
    23 import java.io.*;
    23 import java.io.FileOutputStream;
       
    24 import java.io.IOException;
       
    25 import java.io.OutputStream;
       
    26 import java.io.OutputStreamWriter;
       
    27 import java.io.PrintStream;
       
    28 import java.io.Writer;
       
    29 import java.util.Hashtable;
       
    30 import java.util.Stack;
       
    31 
       
    32 import javax.xml.parsers.DocumentBuilder;
       
    33 import javax.xml.parsers.DocumentBuilderFactory;
       
    34 import javax.xml.parsers.ParserConfigurationException;
       
    35 
       
    36 import org.apache.tools.ant.BuildEvent;
       
    37 import org.apache.tools.ant.BuildException;
       
    38 import org.apache.tools.ant.BuildLogger;
       
    39 import org.apache.tools.ant.Project;
       
    40 import org.apache.tools.ant.SubBuildListener;
       
    41 import org.apache.tools.ant.Target;
       
    42 import org.apache.tools.ant.Task;
       
    43 import org.apache.tools.ant.UnknownElement;
    24 import org.apache.tools.ant.util.DOMElementWriter;
    44 import org.apache.tools.ant.util.DOMElementWriter;
    25 import org.apache.tools.ant.util.DateUtils;
    45 import org.apache.tools.ant.util.DateUtils;
    26 import javax.xml.parsers.DocumentBuilder;
       
    27 import javax.xml.parsers.DocumentBuilderFactory;
       
    28 import org.w3c.dom.Document;
    46 import org.w3c.dom.Document;
    29 import org.w3c.dom.Element;
    47 import org.w3c.dom.Element;
    30 import org.apache.tools.ant.*;
       
    31 import java.util.*;
       
    32 import javax.xml.parsers.ParserConfigurationException;
       
    33 
    48 
    34 /**
    49 /**
    35  * This is a class that represents a XML recorder. This is the listener to the
    50  * This is a class that represents a XML recorder. This is the listener to the build process.
    36  * build process.
    51  * 
    37  *
       
    38  */
    52  */
    39 public class CoverageRecorderEntry implements BuildLogger, SubBuildListener {
    53 public class CoverageRecorderEntry implements BuildLogger, SubBuildListener {
    40 
       
    41     //////////////////////////////////////////////////////////////////////
       
    42     // ATTRIBUTES
       
    43     
       
    44     /** XML element name for a build. */
    54     /** XML element name for a build. */
    45     private static final String BUILD_TAG = "build";
    55     private static final String BUILD_TAG = "build";
    46     /** XML element name for a target. */
    56     /** XML element name for a target. */
    47     private static final String TARGET_TAG = "target";
    57     private static final String TARGET_TAG = "target";
    48     /** XML element name for a task. */
    58     /** XML element name for a task. */
    51     private static final String NAME_ATTR = "name";
    61     private static final String NAME_ATTR = "name";
    52     /** XML attribute name for a time. */
    62     /** XML attribute name for a time. */
    53     private static final String TIME_ATTR = "time";
    63     private static final String TIME_ATTR = "time";
    54     /** XML attribute name for a file location. */
    64     /** XML attribute name for a file location. */
    55     private static final String LOCATION_ATTR = "location";
    65     private static final String LOCATION_ATTR = "location";
    56     
    66 
    57     /** DocumentBuilder to use when creating the document to start with. */
    67     /** DocumentBuilder to use when creating the document to start with. */
    58     private static DocumentBuilder builder = getDocumentBuilder();
    68     private static DocumentBuilder builder = getDocumentBuilder();
    59     
    69 
    60     private String recordTaskName;
    70     private String recordTaskName;
    61     
    71 
    62     /** The name of the file associated with this recorder entry.  */
    72     /** The name of the file associated with this recorder entry. */
    63     private String filename;
    73     private String filename;
    64     /** The state of the recorder (recorder on or off).  */
    74     /** The state of the recorder (recorder on or off). */
    65     private boolean record = true;
    75     private boolean record = true;
    66     /** The current verbosity level to record at.  */
    76     /** The current verbosity level to record at. */
    67     private int loglevel = Project.MSG_INFO;
    77     private int loglevel = Project.MSG_INFO;
    68     /** The output PrintStream to record to.  */
    78     /** The output PrintStream to record to. */
    69     private PrintStream outStream;
    79     private PrintStream outStream;
    70     /** The start time of the last know target.  */
    80     /** The start time of the last know target. */
    71     private long targetStartTime;
    81     private long targetStartTime;
    72     /** project instance the recorder is associated with */
    82     /** project instance the recorder is associated with */
    73     private Project project;
    83     private Project project;
    74 
    84 
    75     /** The complete log document for this build. */
    85     /** The complete log document for this build. */
    77     /** Mapping for when tasks started (Task to TimedElement). */
    87     /** Mapping for when tasks started (Task to TimedElement). */
    78     private Hashtable<Task, TimedElement> tasks = new Hashtable<Task, TimedElement>();
    88     private Hashtable<Task, TimedElement> tasks = new Hashtable<Task, TimedElement>();
    79     /** Mapping for when targets started (Task to TimedElement). */
    89     /** Mapping for when targets started (Task to TimedElement). */
    80     private Hashtable<Target, TimedElement> targets = new Hashtable<Target, TimedElement>();
    90     private Hashtable<Target, TimedElement> targets = new Hashtable<Target, TimedElement>();
    81     /**
    91     /**
    82      * Mapping of threads to stacks of elements
    92      * Mapping of threads to stacks of elements (Thread to Stack of TimedElement).
    83      * (Thread to Stack of TimedElement).
       
    84      */
    93      */
    85     private Hashtable<Thread, Stack<TimedElement>> threadStacks = new Hashtable<Thread, Stack<TimedElement>>();
    94     private Hashtable<Thread, Stack<TimedElement>> threadStacks = new Hashtable<Thread, Stack<TimedElement>>();
    86     /**
    95     /**
    87      * When the build started.
    96      * When the build started.
    88      */
    97      */
    89     private TimedElement buildElement;
    98     private TimedElement buildElement;
    90     
       
    91     //////////////////////////////////////////////////////////////////////
       
    92     // CONSTRUCTORS / INITIALIZERS
       
    93 
    99 
    94     /**
   100     /**
    95      * @param name The name of this recorder (used as the filename).
   101      * @param name The name of this recorder (used as the filename).
    96      */
   102      */
    97     public CoverageRecorderEntry(String name, String recordTaskName) {
   103     public CoverageRecorderEntry(String name, String recordTaskName) {
    98         filename = name;
   104         filename = name;
    99         this.recordTaskName = recordTaskName;
   105         this.recordTaskName = recordTaskName;
   100         startBuild();
   106         startBuild();
   101     }
   107     }
   102     
   108 
   103     /**
   109     /**
   104      * Returns a default DocumentBuilder instance or throws an
   110      * Returns a default DocumentBuilder instance or throws an ExceptionInInitializerError if it
   105      * ExceptionInInitializerError if it can't be created.
   111      * can't be created.
   106      *
   112      * 
   107      * @return a default DocumentBuilder instance.
   113      * @return a default DocumentBuilder instance.
   108      */
   114      */
   109     protected static DocumentBuilder getDocumentBuilder() {
   115     protected static DocumentBuilder getDocumentBuilder() {
   110         try {
   116         try {
   111             return DocumentBuilderFactory.newInstance().newDocumentBuilder();
   117             return DocumentBuilderFactory.newInstance().newDocumentBuilder();
   112         } catch (ParserConfigurationException exc) {
   118         }
       
   119         catch (ParserConfigurationException exc) {
   113             throw new ExceptionInInitializerError(exc.getMessage());
   120             throw new ExceptionInInitializerError(exc.getMessage());
   114         }
   121         }
   115     }
   122     }
   116     
   123 
   117     /** Utility class representing the time an element started. */
   124     /** Utility class representing the time an element started. */
   118     protected static class TimedElement {
   125     protected static class TimedElement {
   119         /**
   126         /**
   120          * Start time in milliseconds
   127          * Start time in milliseconds (as returned by <code>System.currentTimeMillis()</code>).
   121          * (as returned by <code>System.currentTimeMillis()</code>).
       
   122          */
   128          */
   123         private long startTime;
   129         private long startTime;
   124         /** Element created at the start time. */
   130         /** Element created at the start time. */
   125         private Element element;
   131         private Element element;
       
   132 
   126         public String toString() {
   133         public String toString() {
   127             return element.getTagName() + ":" + element.getAttribute("name");
   134             return element.getTagName() + ":" + element.getAttribute("name");
   128         }
   135         }
   129     }
   136     }
   130     
   137 
   131     /**
   138     /**
   132      * Returns the stack of timed elements for the current thread.
   139      * Returns the stack of timed elements for the current thread.
       
   140      * 
   133      * @return the stack of timed elements for the current thread
   141      * @return the stack of timed elements for the current thread
   134      */
   142      */
   135     protected Stack<TimedElement> getStack() {
   143     protected Stack<TimedElement> getStack() {
   136         Stack<TimedElement> threadStack = threadStacks.get(Thread.currentThread());
   144         Stack<TimedElement> threadStack = threadStacks.get(Thread.currentThread());
   137         if (threadStack == null) {
   145         if (threadStack == null) {
   138             threadStack = new Stack<TimedElement>();
   146             threadStack = new Stack<TimedElement>();
   139             threadStacks.put(Thread.currentThread(), threadStack);
   147             threadStacks.put(Thread.currentThread(), threadStack);
   140         }
   148         }
   141         /* For debugging purposes uncomment:
   149         /*
   142         org.w3c.dom.Comment s = doc.createComment("stack=" + threadStack);
   150          * For debugging purposes uncomment: org.w3c.dom.Comment s = doc.createComment("stack=" +
   143         buildElement.element.appendChild(s);
   151          * threadStack); buildElement.element.appendChild(s);
   144          */
   152          */
   145         return threadStack;
   153         return threadStack;
   146     }
   154     }
   147     
       
   148     //////////////////////////////////////////////////////////////////////
       
   149     // ACCESSOR METHODS
       
   150 
   155 
   151     /**
   156     /**
   152      * @return the name of the file the output is sent to.
   157      * @return the name of the file the output is sent to.
   153      */
   158      */
   154     public String getFilename() {
   159     public String getFilename() {
   155         return filename;
   160         return filename;
   156     }
   161     }
   157 
   162 
   158     /**
   163     /**
   159      * Turns off or on this recorder.
   164      * Turns off or on this recorder.
   160      *
   165      * 
   161      * @param state true for on, false for off, null for no change.
   166      * @param state true for on, false for off, null for no change.
   162      */
   167      */
   163     public void setRecordState(Boolean state) {
   168     public void setRecordState(Boolean state) {
   164         if (state != null) {
   169         if (state != null) {
   165             record = state.booleanValue();
   170             record = state.booleanValue();
   169     /**
   174     /**
   170      * @see org.apache.tools.ant.BuildListener#buildStarted(BuildEvent)
   175      * @see org.apache.tools.ant.BuildListener#buildStarted(BuildEvent)
   171      */
   176      */
   172     /** {@inheritDoc}. */
   177     /** {@inheritDoc}. */
   173     public void buildStarted(BuildEvent event) {
   178     public void buildStarted(BuildEvent event) {
   174         
   179 
   175     }
   180     }
   176 
   181 
   177     /**
   182     /**
   178      * @see org.apache.tools.ant.BuildListener#buildFinished(BuildEvent)
   183      * @see org.apache.tools.ant.BuildListener#buildFinished(BuildEvent)
   179      */
   184      */
   181     public void buildFinished(BuildEvent event) {
   186     public void buildFinished(BuildEvent event) {
   182         cleanup();
   187         cleanup();
   183     }
   188     }
   184 
   189 
   185     /**
   190     /**
   186      * Cleans up any resources held by this recorder entry at the end
   191      * Cleans up any resources held by this recorder entry at the end of a subbuild if it has been
   187      * of a subbuild if it has been created for the subbuild's project
   192      * created for the subbuild's project instance.
   188      * instance.
   193      * 
   189      *
       
   190      * @param event the buildFinished event
   194      * @param event the buildFinished event
   191      *
   195      * 
   192      * @since Ant 1.6.2
   196      * @since Ant 1.6.2
   193      */
   197      */
   194     public void subBuildFinished(BuildEvent event) {
   198     public void subBuildFinished(BuildEvent event) {
   195         if (event.getProject() == project) {
   199         if (event.getProject() == project) {
   196             cleanup();
   200             cleanup();
   197         }
   201         }
   198     }
   202     }
   199 
   203 
   200     /**
   204     /**
   201      * Empty implementation to satisfy the BuildListener interface.
   205      * Empty implementation to satisfy the BuildListener interface.
   202      *
   206      * 
   203      * @param event the buildStarted event
   207      * @param event the buildStarted event
   204      *
   208      * 
   205      * @since Ant 1.6.2
   209      * @since Ant 1.6.2
   206      */
   210      */
   207     public void subBuildStarted(BuildEvent event) {
   211     public void subBuildStarted(BuildEvent event) {
   208     }
   212     }
   209 
   213 
   227     /** {@inheritDoc}. */
   231     /** {@inheritDoc}. */
   228     public void targetFinished(BuildEvent event) {
   232     public void targetFinished(BuildEvent event) {
   229         Target target = event.getTarget();
   233         Target target = event.getTarget();
   230         TimedElement targetElement = (TimedElement) targets.get(target);
   234         TimedElement targetElement = (TimedElement) targets.get(target);
   231         if (targetElement != null) {
   235         if (targetElement != null) {
   232             long totalTime
   236             long totalTime = System.currentTimeMillis() - targetElement.startTime;
   233                     = System.currentTimeMillis() - targetElement.startTime;
   237             targetElement.element.setAttribute(TIME_ATTR, DateUtils.formatElapsedTime(totalTime));
   234             targetElement.element.setAttribute(TIME_ATTR,
       
   235                     DateUtils.formatElapsedTime(totalTime));
       
   236 
   238 
   237             TimedElement parentElement = null;
   239             TimedElement parentElement = null;
   238             Stack<TimedElement> threadStack = getStack();
   240             Stack<TimedElement> threadStack = getStack();
   239             if (!threadStack.empty()) {
   241             if (!threadStack.empty()) {
   240                 threadStack.pop();
   242                 threadStack.pop();
   241 //                if (poppedStack != targetElement) {
   243                 // if (poppedStack != targetElement) {
   242 //                    throw new RuntimeException("Mismatch - popped element = "
   244                 // throw new RuntimeException("Mismatch - popped element = "
   243 //                            + poppedStack
   245                 // + poppedStack
   244 //                            + " finished target element = "
   246                 // + " finished target element = "
   245 //                            + targetElement);
   247                 // + targetElement);
   246 //                }
   248                 // }
   247                 if (!threadStack.empty()) {
   249                 if (!threadStack.empty()) {
   248                     parentElement = threadStack.peek();
   250                     parentElement = threadStack.peek();
   249                 }
   251                 }
   250             }
   252             }
   251             if (parentElement == null) {
   253             if (parentElement == null) {
   252                 buildElement.element.appendChild(targetElement.element);
   254                 buildElement.element.appendChild(targetElement.element);
   253             } else {
   255             }
       
   256             else {
   254                 parentElement.element.appendChild(targetElement.element);
   257                 parentElement.element.appendChild(targetElement.element);
   255             }
   258             }
   256         }
   259         }
   257         targets.remove(target);
   260         targets.remove(target);
   258     }
   261     }
   270         String name = event.getTask().getTaskName();
   273         String name = event.getTask().getTaskName();
   271         if (name == null) {
   274         if (name == null) {
   272             name = "";
   275             name = "";
   273         }
   276         }
   274         taskElement.element.setAttribute(NAME_ATTR, name);
   277         taskElement.element.setAttribute(NAME_ATTR, name);
   275         taskElement.element.setAttribute(LOCATION_ATTR,
   278         taskElement.element.setAttribute(LOCATION_ATTR, event.getTask().getLocation().toString());
   276                 event.getTask().getLocation().toString());
       
   277         tasks.put(task, taskElement);
   279         tasks.put(task, taskElement);
   278         getStack().push(taskElement);
   280         getStack().push(taskElement);
   279     }
   281     }
   280 
   282 
   281     /**
   283     /**
   282      * @see org.apache.tools.ant.BuildListener#taskFinished(BuildEvent)
   284      * @see org.apache.tools.ant.BuildListener#taskFinished(BuildEvent)
   283      */
   285      */
   284     /** {@inheritDoc}. */
   286     /** {@inheritDoc}. */
   285     public void taskFinished(BuildEvent event) {
   287     public void taskFinished(BuildEvent event) {
   286         
   288 
   287 //        if (event.getTask().getTaskName() != recordTaskName) {
   289         // if (event.getTask().getTaskName() != recordTaskName) {
   288         Task task = event.getTask();
   290         Task task = event.getTask();
   289         TimedElement taskElement = tasks.get(task);
   291         TimedElement taskElement = tasks.get(task);
   290         if (taskElement != null) {
   292         if (taskElement != null) {
   291             long totalTime = System.currentTimeMillis() - taskElement.startTime;
   293             long totalTime = System.currentTimeMillis() - taskElement.startTime;
   292             taskElement.element.setAttribute(TIME_ATTR,
   294             taskElement.element.setAttribute(TIME_ATTR, DateUtils.formatElapsedTime(totalTime));
   293                     DateUtils.formatElapsedTime(totalTime));
       
   294             Target target = task.getOwningTarget();
   295             Target target = task.getOwningTarget();
   295             TimedElement targetElement = null;
   296             TimedElement targetElement = null;
   296             if (target != null) {
   297             if (target != null) {
   297                 targetElement = (TimedElement) targets.get(target);
   298                 targetElement = (TimedElement) targets.get(target);
   298             }
   299             }
   299             if (targetElement == null) {
   300             if (targetElement == null) {
   300                 buildElement.element.appendChild(taskElement.element);
   301                 buildElement.element.appendChild(taskElement.element);
   301             } else {
   302             }
       
   303             else {
   302                 targetElement.element.appendChild(taskElement.element);
   304                 targetElement.element.appendChild(taskElement.element);
   303             }
   305             }
   304             Stack<TimedElement> threadStack = getStack();
   306             Stack<TimedElement> threadStack = getStack();
   305             if (!threadStack.empty()) {
   307             if (!threadStack.empty()) {
   306                 threadStack.pop();
   308                 threadStack.pop();
   307 //                    if (poppedStack != taskElement) {
   309                 // if (poppedStack != taskElement) {
   308 //                        throw new RuntimeException("Mismatch - popped element = "
   310                 // throw new RuntimeException("Mismatch - popped element = "
   309 //                                + poppedStack + " finished task element = "
   311                 // + poppedStack + " finished task element = "
   310 //                                + taskElement);
   312                 // + taskElement);
   311 //                    }
   313                 // }
   312             }
   314             }
   313             tasks.remove(task);
   315             tasks.remove(task);
   314 //            } else {
   316             // } else {
   315 //                throw new RuntimeException("Unknown task " + task + " not in " + tasks);
   317             // throw new RuntimeException("Unknown task " + task + " not in " + tasks);
   316 //            }
   318             // }
   317         }
   319         }
   318     }
   320     }
   319     
   321 
   320     /**
   322     /**
   321      * Get the TimedElement associated with a task.
   323      * Get the TimedElement associated with a task.
   322      *
   324      * 
   323      * Where the task is not found directly, search for unknown elements which
   325      * Where the task is not found directly, search for unknown elements which may be hiding the
   324      * may be hiding the real task
   326      * real task
   325      */
   327      */
   326     protected TimedElement getTaskElement(Task task) {
   328     protected TimedElement getTaskElement(Task task) {
   327         TimedElement element = (TimedElement) tasks.get(task);
   329         TimedElement element = (TimedElement) tasks.get(task);
   328         if (element != null) {
   330         if (element != null) {
   329             return element;
   331             return element;
   336                 }
   338                 }
   337             }
   339             }
   338         }
   340         }
   339         return null;
   341         return null;
   340     }
   342     }
   341     
   343 
   342     /**
   344     /**
   343      * @see org.apache.tools.ant.BuildListener#messageLogged(BuildEvent)
   345      * @see org.apache.tools.ant.BuildListener#messageLogged(BuildEvent)
   344      */
   346      */
   345     /** {@inheritDoc}. */
   347     /** {@inheritDoc}. */
   346     public void messageLogged(BuildEvent event) {
   348     public void messageLogged(BuildEvent event) {
   347         
   349 
   348     }
   350     }
   349 
   351 
   350     /**
   352     /**
   351      * @see BuildLogger#setMessageOutputLevel(int)
   353      * @see BuildLogger#setMessageOutputLevel(int)
   352      */
   354      */
   363     /** {@inheritDoc}. */
   365     /** {@inheritDoc}. */
   364     public void setOutputPrintStream(PrintStream output) {
   366     public void setOutputPrintStream(PrintStream output) {
   365         outStream = output;
   367         outStream = output;
   366     }
   368     }
   367 
   369 
   368 
       
   369     /**
   370     /**
   370      * @see BuildLogger#setErrorPrintStream(PrintStream)
   371      * @see BuildLogger#setErrorPrintStream(PrintStream)
   371      */
   372      */
   372     /** {@inheritDoc}. */
   373     /** {@inheritDoc}. */
   373     public void setErrorPrintStream(PrintStream err) {
   374     public void setErrorPrintStream(PrintStream err) {
   374         setOutputPrintStream(err);
   375         setOutputPrintStream(err);
   375     }
   376     }
   376 
   377 
   377     /**
   378     /**
   378      * Set the project associated with this recorder entry.
   379      * Set the project associated with this recorder entry.
   379      *
   380      * 
   380      * @param project the project instance
   381      * @param project the project instance
   381      *
   382      * 
   382      * @since 1.6.2
   383      * @since 1.6.2
   383      */
   384      */
   384     public void setProject(Project project) {
   385     public void setProject(Project project) {
   385         this.project = project;
   386         this.project = project;
   386         if (project != null) {
   387         if (project != null) {
   387             project.addBuildListener(this);
   388             project.addBuildListener(this);
   388         }
   389         }
   389     }
   390     }
   390     
   391 
   391     /**
   392     /**
   392      * @since 1.6.2
   393      * @since 1.6.2
   393      */
   394      */
   394     public void cleanup() {
   395     public void cleanup() {
   395         closeFile();
   396         closeFile();
   396         if (project != null) {
   397         if (project != null) {
   397             project.removeBuildListener(this);
   398             project.removeBuildListener(this);
   398         }
   399         }
   399         project = null;
   400         project = null;
   400     }
   401     }
   401     
   402 
   402     /**
   403     /**
   403      * Closes the file associated with this recorder.
   404      * Closes the file associated with this recorder. Used by Recorder.
   404      * Used by Recorder.
   405      * 
   405      * @since 1.6.3
   406      * @since 1.6.3
   406      */
   407      */
   407     void closeFile() {
   408     void closeFile() {
   408         finishBuild();
   409         finishBuild();
   409         Writer out = null;
   410         Writer out = null;
   416             }
   417             }
   417             out = new OutputStreamWriter(stream, "UTF8");
   418             out = new OutputStreamWriter(stream, "UTF8");
   418             out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
   419             out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
   419             (new DOMElementWriter()).write(buildElement.element, out, 0, "\t");
   420             (new DOMElementWriter()).write(buildElement.element, out, 0, "\t");
   420             out.flush();
   421             out.flush();
   421         } catch (IOException exc) {
   422         }
       
   423         catch (IOException exc) {
   422             throw new BuildException("Unable to write log file " + exc.getMessage(), exc);
   424             throw new BuildException("Unable to write log file " + exc.getMessage(), exc);
   423         } finally {
   425         }
       
   426         finally {
   424             if (out != null) {
   427             if (out != null) {
   425                 try {
   428                 try {
   426                     out.close();
   429                     out.close();
   427                 } catch (IOException e) {
   430                 }
       
   431                 catch (IOException e) {
   428                     e = null; // ignore
   432                     e = null; // ignore
   429                 }
   433                 }
   430             }
   434             }
   431         }
   435         }
   432         buildElement = null;
   436         buildElement = null;
   433     }
   437     }
   434     
   438 
   435     void startBuild() {
   439     void startBuild() {
   436         buildElement = new TimedElement();
   440         buildElement = new TimedElement();
   437         buildElement.startTime = System.currentTimeMillis();
   441         buildElement.startTime = System.currentTimeMillis();
   438         buildElement.element = doc.createElement(BUILD_TAG);
   442         buildElement.element = doc.createElement(BUILD_TAG);
   439     }
   443     }
   440     
   444 
   441     void finishBuild() {
   445     void finishBuild() {
   442         long totalTime = System.currentTimeMillis() - buildElement.startTime;
   446         long totalTime = System.currentTimeMillis() - buildElement.startTime;
   443         buildElement.element.setAttribute(TIME_ATTR,
   447         buildElement.element.setAttribute(TIME_ATTR, DateUtils.formatElapsedTime(totalTime));
   444                 DateUtils.formatElapsedTime(totalTime));
       
   445     }
   448     }
   446 
   449 
   447     @Override
   450     @Override
   448     public void setEmacsMode(boolean emacsMode) {
   451     public void setEmacsMode(boolean emacsMode) {
   449         // Not needed.
   452         // Not needed.