From f0bf1ccdaa227588d497594e4962993e4e2fde2c Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 30 Nov 2009 20:41:05 +0000
Subject: [PATCH] Copy OpenDS build tools to sdk.

---
 sdk/build-tools/org/opends/build/tools/PrepTestNG.java            |  337 ++++
 sdk/build-tools/org/opends/build/tools/CreateVersionString.java   |   98 +
 sdk/build-tools/org/opends/build/tools/ValidJavaVersion.java      |   68 
 sdk/build-tools/org/opends/build/tools/CoverageDiff.java          |  969 +++++++++++++
 sdk/build-tools/org/opends/build/tools/CheckPrecommit.java        |  402 +++++
 sdk/build-tools/org/opends/build/tools/GenerateMessageFile.java   |  995 ++++++++++++++
 sdk/build-tools/org/opends/build/tools/MessagePropertyKey.java    |  267 +++
 sdk/build-tools/org/opends/build/tools/GetSubversionUrlRepo.java  |  145 ++
 sdk/build-tools/org/opends/build/tools/package-info.java          |   36 
 sdk/build-tools/org/opends/build/tools/Utilities.java             |  159 ++
 sdk/build-tools/org/opends/build/tools/ConcatSchema.java          |  272 +++
 sdk/build-tools/org/opends/build/tools/GenerateRpm.java           |  251 +++
 sdk/build-tools/org/opends/build/tools/GetSubversionRevision.java |  143 ++
 13 files changed, 4,142 insertions(+), 0 deletions(-)

diff --git a/sdk/build-tools/org/opends/build/tools/CheckPrecommit.java b/sdk/build-tools/org/opends/build/tools/CheckPrecommit.java
new file mode 100644
index 0000000..257b534
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/CheckPrecommit.java
@@ -0,0 +1,402 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.LinkedList;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.wc.SVNPropertyData;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.wc.ISVNStatusHandler;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNStatus;
+import org.tmatesoft.svn.core.wc.SVNWCClient;
+
+
+
+/**
+ * This class provides an implementation of an Ant task that may be used to
+ * perform various checks to deteermine whether a file is suitable to be
+ * committed.  This includes:
+ * <UL>
+ *   <LI>Make sure that the file has the correct "svn:eol-style" property
+ *       value.</LI>
+ *   <LI>If a file contains a line that appears to be a comment and includes the
+ *       word "copyright", then it should contain the current year.</LI>
+ * </UL>
+ */
+public class CheckPrecommit
+       extends Task
+       implements ISVNStatusHandler
+{
+  /**
+   * The name of the system property that may be used to prevent copyright date
+   * problems from failing the build.
+   */
+  public static final String IGNORE_COPYRIGHT_ERRORS_PROPERTY =
+       "org.opends.server.IgnoreCopyrightDateErrors";
+
+
+
+  /**
+   * The name of the system property that may be used to prevent svn eol-style
+   * problems from failing the build.
+   */
+  public static final String IGNORE_EOLSTYLE_ERRORS_PROPERTY =
+       "org.opends.server.IgnoreEOLStyleErrors";
+
+
+
+  /**
+   *
+   */
+  public static final HashSet<String> CHECKED_EXTENSIONS =
+       new HashSet<String>();
+  static
+  {
+    CHECKED_EXTENSIONS.add("java");
+    CHECKED_EXTENSIONS.add("xml");
+    CHECKED_EXTENSIONS.add("xsd");
+    CHECKED_EXTENSIONS.add("xsl");
+    CHECKED_EXTENSIONS.add("html");
+    CHECKED_EXTENSIONS.add("sh");
+    CHECKED_EXTENSIONS.add("bat");
+    CHECKED_EXTENSIONS.add("ldif");
+    CHECKED_EXTENSIONS.add("txt");
+    CHECKED_EXTENSIONS.add("c");
+    CHECKED_EXTENSIONS.add("h");
+    CHECKED_EXTENSIONS.add("mc");
+    CHECKED_EXTENSIONS.add("Makefile");
+  }
+
+
+
+  // The path to the directory that is the base of the workspace.
+  private File workspacePath;
+
+  // The set of files that appear to have problems with the EOL style.
+  private LinkedList<String> eolStyleProblemFiles = new LinkedList<String>();
+
+  // The set of files that appear to have problems with the copyright date.
+  private LinkedList<String> copyrightProblemFiles = new LinkedList<String>();
+
+  // The path to the root of the Subversion workspace to check.
+  private String workspace = null;
+
+  // The string representation of the current year.
+  private String yearString;
+
+  // The overall SVN Client Manager. required with svnkit 1.2.x
+  private static SVNClientManager ourClientManager =
+          SVNClientManager.newInstance();
+  // The property client used to look at file properties.
+  private SVNWCClient propertyClient;
+
+
+
+  /**
+   * Specifies the path to the root of the Subversion workspace for which to
+   * retrieve the revision number.
+   *
+   * @param  workspace  The path to the root of the Subversion workspace for
+   *                    which to retrieve the revision number.
+   */
+  public void setWorkspace(String workspace)
+  {
+    this.workspace = workspace;
+  }
+
+
+
+  /**
+   * Performs the appropriate processing needed for this task.  In this case,
+   * it uses SVNKit to identify all modified files in the current workspace.
+   * For all source files, look for comment lines containing the word
+   * "copyright" and make sure at least one of them contains the current year.
+   */
+  @Override()
+  public void execute()
+  {
+    if ((workspace == null) || (workspace.length() == 0))
+    {
+      workspacePath = getProject().getBaseDir();
+    }
+    else
+    {
+      workspacePath = new File(workspace);
+    }
+
+
+    // Get the year to use in the determination.
+    GregorianCalendar calendar = new GregorianCalendar();
+    int year = calendar.get(GregorianCalendar.YEAR);
+    yearString = String.valueOf(year);
+
+
+    // Process the base directory and all of its subdirectories.
+    propertyClient = ourClientManager.getWCClient();
+
+    try
+    {
+      long status = ourClientManager.getStatusClient().doStatus(workspacePath, SVNRevision.WORKING, 
+              SVNDepth.INFINITY, false, false, false, false, this, null);
+    }
+    catch (Exception e)
+    {
+      e.printStackTrace();
+      System.err.println("WARNING:  Encountered an error while examining " +
+                         "Subversion status:  " + e);
+      System.err.println("No further checks will be performed.");
+      return;
+    }
+
+    boolean fail = false;
+
+    if (! eolStyleProblemFiles.isEmpty())
+    {
+      System.err.println("WARNING:  Potential svn:eol-style updates needed " +
+                         "for the following files:");
+      for (String filename : eolStyleProblemFiles)
+      {
+        System.err.println("     " + filename);
+      }
+
+      String ignoreProp =
+           getProject().getProperty(IGNORE_EOLSTYLE_ERRORS_PROPERTY);
+      if ((ignoreProp == null) || (! ignoreProp.equalsIgnoreCase("true")))
+      {
+        fail = true;
+        System.err.println("Fix svn:eol-style problems before proceeding, or " +
+                           "use '-D" + IGNORE_EOLSTYLE_ERRORS_PROPERTY +
+                           "=true' to ignore svn eol-style warnings.");
+      }
+    }
+
+    if (! copyrightProblemFiles.isEmpty())
+    {
+      System.err.println("WARNING:  Potential copyright year updates needed " +
+                         "for the following files:");
+      for (String filename : copyrightProblemFiles)
+      {
+        System.err.println("     " + filename);
+      }
+
+      String ignoreProp =
+           getProject().getProperty(IGNORE_COPYRIGHT_ERRORS_PROPERTY);
+      if ((ignoreProp == null) || (! ignoreProp.equalsIgnoreCase("true")))
+      {
+        fail = true;
+        System.err.println("Fix copyright date problems before proceeding, " +
+                           "or use '-D" + IGNORE_COPYRIGHT_ERRORS_PROPERTY +
+                           "=true' to ignore copyright warnings.");
+      }
+    }
+
+    if (fail)
+    {
+      throw new BuildException();
+    }
+  }
+
+
+
+  /**
+   * Examines the provided status item to determine whether the associated file
+   * is acceptable.
+   *
+   * @param  status  The SVN status information for the file of interest.
+   */
+  public void handleStatus(SVNStatus status)
+  {
+    File file = status.getFile();
+    if ((! file.exists()) || (! file.isFile()))
+    {
+      // The file doesn't exist (which probably means it's been deleted) or
+      // isn't a regular file, so we'll ignore it.
+      return;
+    }
+
+    String fileName = file.getName();
+    int lastPeriodPos = fileName.lastIndexOf('.');
+    if (lastPeriodPos > 0)
+    {
+      String extension = fileName.substring(lastPeriodPos+1);
+      if (! CHECKED_EXTENSIONS.contains(extension.toLowerCase()))
+      {
+        // The file doesn't have an extension that we care about, so skip it.
+        return;
+      }
+    }
+    else
+    {
+      // The file doesn't have an extension.  We'll still want to check it if
+      // it's in a resource/bin directory.
+      File parentDirectory = file.getParentFile();
+      if ((parentDirectory == null) ||
+          (! parentDirectory.getName().equals("bin")))
+      {
+        return;
+      }
+
+      parentDirectory = parentDirectory.getParentFile();
+      if ((parentDirectory == null) ||
+          (! parentDirectory.getName().equals("resource")))
+      {
+        return;
+      }
+    }
+
+
+    String filePath = file.getAbsolutePath();
+    if (filePath.startsWith(workspacePath.getPath() + "/"))
+    {
+      filePath = filePath.substring(workspacePath.getPath().length() + 1);
+    }
+
+
+    // Check to make sure that the file has the correct EOL style.
+    try
+    {
+      SVNPropertyData propertyData =
+           propertyClient.doGetProperty(file, "svn:eol-style",
+                                        SVNRevision.BASE,
+                                        SVNRevision.WORKING);
+      if ((propertyData == null) ||
+          (! propertyData.getValue().getString().equals("native")))
+      {
+        eolStyleProblemFiles.add(filePath);
+      }
+    }
+    catch (SVNException se)
+    {
+      // This could happen if the file isn't under version control.  If so, then
+      // we can't check the eol-style but we should at least be able to check
+      // the copyright dates, so keep going.
+    }
+
+
+    // Check to see whether the file has a comment line containing a copyright
+    // without the current year.
+    BufferedReader reader = null;
+    try
+    {
+      boolean copyrightFound   = false;
+      boolean correctYearFound = false;
+      reader = new BufferedReader(new FileReader(file));
+      String line = reader.readLine();
+      while (line != null)
+      {
+        String lowerLine = line.toLowerCase().trim();
+        if (isCommentLine(lowerLine))
+        {
+          int copyrightPos = lowerLine.indexOf("copyright");
+          if (copyrightPos > 0)
+          {
+            copyrightFound = true;
+            if (lowerLine.indexOf(yearString) > 0)
+            {
+              correctYearFound = true;
+              break;
+            }
+          }
+        }
+
+        line = reader.readLine();
+      }
+
+      if (copyrightFound && (! correctYearFound))
+      {
+        copyrightProblemFiles.add(filePath);
+      }
+    }
+    catch (IOException ioe)
+    {
+      System.err.println("ERROR:  Could not read file " + filePath +
+                         " to check copyright date.");
+      System.err.println("No further copyright date checking will be " +
+                         "performed.");
+      throw new RuntimeException();
+    }
+    finally
+    {
+      try
+      {
+        if (reader != null)
+        {
+          reader.close();
+        }
+      } catch (Exception e) {}
+    }
+  }
+
+
+
+  /**
+   * Indicates whether the provided line appears to be a comment line.  It will
+   * check for a number of common comment indicators in Java source files,
+   * shell scripts, XML files, and LDIF files.
+   *
+   * @param  lowerLine  The line to be checked.  It should have been coverted to
+   *                    all lowercase characters and any leading spaces
+   *                    removed.
+   *
+   * @return  {@code true} if it appears that the line is a comment line, or
+   *          {@code false} if not.
+   */
+  private static boolean isCommentLine(String lowerLine)
+  {
+    if (lowerLine.startsWith("/*") ||
+        lowerLine.startsWith("*") ||
+        lowerLine.startsWith("//") ||
+        lowerLine.startsWith("#") ||
+        lowerLine.startsWith("rem") ||
+        lowerLine.startsWith("<!--") ||
+        lowerLine.startsWith("!"))
+    {
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+}
+
diff --git a/sdk/build-tools/org/opends/build/tools/ConcatSchema.java b/sdk/build-tools/org/opends/build/tools/ConcatSchema.java
new file mode 100644
index 0000000..3137504
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/ConcatSchema.java
@@ -0,0 +1,272 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.util.LinkedList;
+import java.util.TreeSet;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+
+
+/**
+ * This class provides an implementation of an Ant task that concatenates the
+ * contents of the files in the schema directory to create a base schema that
+ * may be used during the upgrade process.  Each element will also include the
+ * X-SCHEMA-FILE extension to indicate the source schema file.
+ */
+public class ConcatSchema
+       extends Task
+{
+  // The path to the directory containing the schema files.
+  private String schemaDirectory;
+
+  // The path to the concatenated schema file to create.
+  private String toFile;
+
+
+
+  /**
+   * Specifies the path to the directory containing the schema files.
+   *
+   * @param  schemaDirectory  The path to the directory containing the schema
+   *                          files.
+   */
+  public void setSchemaDirectory(String schemaDirectory)
+  {
+    this.schemaDirectory = schemaDirectory;
+  }
+
+
+
+  /**
+   * Specifies the path to the file to create containing the concatenated schema
+   * elements.
+   *
+   * @param  toFile  The path to the file containing the concatenated schema
+   *                 elements.
+   */
+  public void setToFile(String toFile)
+  {
+    this.toFile = toFile;
+  }
+
+
+
+  /**
+   * Performs the appropriate processing needed for this task.  In this case,
+   * it uses SVNKit to identify all modified files in the current workspace.
+   * For all source files, look for comment lines containing the word
+   * "copyright" and make sure at least one of them contains the current year.
+   */
+  @Override()
+  public void execute()
+  {
+    // Get a sorted list of the files in the schema directory.
+    TreeSet<String> schemaFileNames = new TreeSet<String>();
+    for (File f : new File(schemaDirectory).listFiles())
+    {
+      if (f.isFile())
+      {
+        schemaFileNames.add(f.getName());
+      }
+    }
+
+
+    // Create a set of lists that will hold the schema elements read from the
+    // files.
+    LinkedList<String> attributeTypes    = new LinkedList<String>();
+    LinkedList<String> objectClasses     = new LinkedList<String>();
+    LinkedList<String> nameForms         = new LinkedList<String>();
+    LinkedList<String> ditContentRules   = new LinkedList<String>();
+    LinkedList<String> ditStructureRules = new LinkedList<String>();
+    LinkedList<String> matchingRuleUses  = new LinkedList<String>();
+
+
+    // Open each of the files in order and read the elements that they contain,
+    // appending them to the appropriate lists.
+    for (String name : schemaFileNames)
+    {
+      // Read the contents of the file into a list with one schema element per
+      // list element.
+      LinkedList<StringBuilder> lines = new LinkedList<StringBuilder>();
+      try
+      {
+        BufferedReader reader = new BufferedReader(new FileReader(
+                                         new File(schemaDirectory, name)));
+
+        while (true)
+        {
+          String line = reader.readLine();
+          if (line == null)
+          {
+            break;
+          }
+          else if (line.startsWith("#") || (line.length() == 0))
+          {
+            continue;
+          }
+          else if (line.startsWith(" "))
+          {
+            lines.getLast().append(line.substring(1));
+          }
+          else
+          {
+            lines.add(new StringBuilder(line));
+          }
+        }
+
+        reader.close();
+      }
+      catch (Exception e)
+      {
+        throw new BuildException("Error while reading schema file " + name +
+                                 ":  " + e, e);
+      }
+
+
+      // Iterate through each line in the list.  Find the colon and get the
+      // attribute name at the beginning.  If it's someting that we don't
+      // recognize, then skip it.  Otherwise, add the X-SCHEMA-FILE extension
+      // and add it to the appropriate schema element list.
+      for (StringBuilder buffer : lines)
+      {
+        // Get the line and add the X-SCHEMA-FILE extension to the end of it.
+        // All of them should end with " )" but some might have the parenthesis
+        // crammed up against the last character so deal with that as well.
+        String line = buffer.toString().trim();
+        if (line.endsWith(" )"))
+        {
+         line = line.substring(0, line.length()-1) + "X-SCHEMA-FILE '" + name +
+            "' )";
+        }
+        else if (line.endsWith(")"))
+        {
+         line = line.substring(0, line.length()-1) + " X-SCHEMA-FILE '" + name +
+            "' )";
+        }
+        else
+        {
+          continue;
+        }
+
+        String lowerLine = line.toLowerCase();
+        if (lowerLine.startsWith("attributetypes:"))
+        {
+          attributeTypes.add(line);
+        }
+        else if (lowerLine.startsWith("objectclasses:"))
+        {
+          objectClasses.add(line);
+        }
+        else if (lowerLine.startsWith("nameforms:"))
+        {
+          nameForms.add(line);
+        }
+        else if (lowerLine.startsWith("ditcontentrules:"))
+        {
+          ditContentRules.add(line);
+        }
+        else if (lowerLine.startsWith("ditstructurerules:"))
+        {
+          ditStructureRules.add(line);
+        }
+        else if (lowerLine.startsWith("matchingruleuse:"))
+        {
+          matchingRuleUses.add(line);
+        }
+      }
+    }
+
+
+    // Write the resulting output to the merged schema file.
+    try
+    {
+      BufferedWriter writer = new BufferedWriter(new FileWriter(toFile));
+      writer.write("dn: cn=schema");
+      writer.newLine();
+      writer.write("objectClass: top");
+      writer.newLine();
+      writer.write("objectClass: ldapSubentry");
+      writer.newLine();
+      writer.write("objectClass: subschema");
+      writer.newLine();
+
+      for (String line : attributeTypes)
+      {
+        writer.write(line);
+        writer.newLine();
+      }
+
+      for (String line : objectClasses)
+      {
+        writer.write(line);
+        writer.newLine();
+      }
+
+      for (String line : nameForms)
+      {
+        writer.write(line);
+        writer.newLine();
+      }
+
+      for (String line : ditContentRules)
+      {
+        writer.write(line);
+        writer.newLine();
+      }
+
+      for (String line : ditStructureRules)
+      {
+        writer.write(line);
+        writer.newLine();
+      }
+
+      for (String line : matchingRuleUses)
+      {
+        writer.write(line);
+        writer.newLine();
+      }
+
+      writer.close();
+    }
+    catch (Exception e)
+    {
+      throw new BuildException("Error while writing concatenated schema file " +
+                               toFile + ":  " + e, e);
+    }
+  }
+}
+
diff --git a/sdk/build-tools/org/opends/build/tools/CoverageDiff.java b/sdk/build-tools/org/opends/build/tools/CoverageDiff.java
new file mode 100644
index 0000000..2e456de
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/CoverageDiff.java
@@ -0,0 +1,969 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+import com.vladium.emma.report.*;
+import com.vladium.emma.report.html.doc.*;
+import com.vladium.emma.data.*;
+import com.vladium.util.IntObjectMap;
+
+import java.io.*;
+import java.util.*;
+
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.BuildException;
+
+import org.tmatesoft.svn.core.SVNDepth;
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNDiffClient;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+
+public class CoverageDiff extends Task {
+
+  private static SVNClientManager ourClientManager =
+          SVNClientManager.newInstance();
+  private static final String EOL = System.getProperty("line.separator");
+
+  private boolean verbose = false;
+  private boolean enabled = true;
+
+  private final int COVERED_MOD_EXE_LINES = 0;
+  private final int MOD_EXE_LINES = 1;
+  private final int MOD_LINES = 2;
+  private final int DEL_LINES = 3;
+
+  private final String ENCODING = "ISO-8859-1";
+  private final int IO_BUF_SIZE = 32 * 1024;
+  private final LinkedHashMap<String, SrcFileItem> emmaSrcMap =
+      new LinkedHashMap<String, SrcFileItem>();
+  private final LinkedHashMap<String, Double[]> modCoverageMap =
+      new LinkedHashMap<String, Double[]>();
+
+  private final String CSS = "TABLE,TD,TH {border-style:solid; border-color:black;} " +
+            "TD,TH {background:white;margin:0;line-height:100%;padding-left:0.5em;padding-right:0.5em;} " +
+            "TD {border-width:0 1px 0 0;} TH {border-width:1px 1px 1px 0;} " +
+            "TR TD.h {color:red;} " +
+            "TABLE {border-spacing:0; border-collapse:collapse;border-width:0 0 1px 1px;} " +
+            "P,H1,H2,H3,TH {font-family:verdana,arial,sans-serif;font-size:10pt;} " +
+            "TD {font-family:courier,monospace;font-size:10pt;} " +
+            "TABLE.hdft {border-spacing:0;border-collapse:collapse;border-style:none;} " +
+            "TABLE.hdft TH,TABLE.hdft TD {border-style:none;line-height:normal;} " +
+            "TABLE.hdft TH.tl,TABLE.hdft TD.tl {background:#6699CC;color:white;} " +
+            "TABLE.hdft TD.nv {background:#6633DD;color:white;} " +
+            ".nv A:link {color:white;} .nv A:visited {color:white;} " +
+            ".nv A:active {color:yellow;} " +
+            "TABLE.hdft A:link {color:white;} " +
+            "TABLE.hdft A:visited {color:white;} " +
+            "TABLE.hdft A:active {color:yellow;} " +
+            ".in {color:#356085;} " +
+            "TABLE.s TD {padding-left:0.25em;padding-right:0.25em;} " +
+            "TABLE.s TD.ddt {padding-left:0.25em;padding-right:0.25em;color:#AAAAAA;}" +
+            "TABLE.s TD.ds {padding-left:0.25em;padding-right:0.25em;text-align:right;background:#F0F0F0;} " +
+            "TABLE.s TD.dm {padding-left:0.25em;padding-right:0.25em;text-align:right;background:#BCCFF9;} " +
+            "TABLE.s TD.dd {padding-left:0.25em;padding-right:0.25em;text-align:right;background:#AAAAAA;color:#FFFFFF} " +
+            "TABLE.s TH {padding-left:0.25em;padding-right:0.25em;text-align:left;background:#F0F0F0;} " +
+            "TABLE.s TD.cz {background:#FF9999;} " +
+            "TABLE.s TD.cp {background:#FFFF88;} " +
+            "TABLE.s TD.cc {background:#CCFFCC;} " +
+            "A:link {color:#0000EE;text-decoration:none;} " +
+            "A:visited {color:#0000EE;text-decoration:none;} " +
+            "A:hover {color:#0000EE;text-decoration:underline;} " +
+            "TABLE.cn {border-width:0 0 1px 0;} " +
+            "TABLE.s {border-width:1px 0 1px 1px;} " +
+            "TD.h {color:red;border-width:0 1px 0 0;} " +
+            "TD.f {border-width:0 1px 0 1px;} " +
+            "TD.hf {color:red;border-width:0 1px 0 1px;} " +
+            "TH.f {border-width:1px 1px 1px 1px;} " +
+            "TR.cis TD {background:#F0F0F0;} " +
+            "TR.cis TD {border-width:1px 1px 1px 0;} " +
+            "TR.cis TD.h {color:red;border-width:1px 1px 1px 0;} " +
+            "TR.cis TD.f {border-width:1px 1px 1px 1px;} " +
+            "TR.cis TD.hf {color:red;border-width:1px 1px 1px 1px;} " +
+            "TD.b {border-style:none;background:transparent;line-height:50%;}  " +
+            "TD.bt {border-width:1px 0 0 0;background:transparent;line-height:50%;} " +
+            "TR.o TD {background:#F0F0F0;}" +
+            "TABLE.it {border-style:none;}" +
+            "TABLE.it TD,TABLE.it TH {border-style:none;}";
+
+  private File emmaDataPath;
+  private File outputPath;
+  private String diffPath;
+
+  //   The SVN revision to perform the diff against when calculating
+  //   the coverage diff.  It can be a revision number, a timestamp,
+  //   or a revision keyword (BASE, COMMITTED, and PREV make the
+  //   most sense).  The primary use case for this setting is to do
+  //   a coverage diff against the previous revision when there are
+  //   no changes in the working copy.  It defaults to BASE.
+  private String fromRevision;
+
+  public void setEmmaDataPath(String file)
+  {
+    emmaDataPath = new File(file);
+  }
+
+  public void setOutputPath(String file)
+  {
+    outputPath = new File(file);
+  }
+
+  public void setDiffPath(String diffArgs)
+  {
+    diffPath = diffArgs;
+  }
+
+  public void setVerbose(String bol)
+  {
+    verbose = bol.toLowerCase().equals("true");
+  }
+
+  public void setEnabled(String bol)
+  {
+    enabled = bol.toLowerCase().equals("true");
+  }
+
+  public void setFromRevision(String fromRevision)
+  {
+    this.fromRevision = fromRevision;
+  }
+
+  public void execute() throws BuildException {
+    try {
+      innerExecute();
+    } catch (BuildException e) {
+      throw e;
+    } catch (Throwable e) {
+      e.printStackTrace();
+    }
+  }
+
+  private void innerExecute() throws BuildException, SVNException
+  {
+    long start = System.currentTimeMillis();
+    verboseOut("Starting to execute coveragediff.");
+    verboseOut("diffPath='" + diffPath +"'");
+    if(emmaDataPath == null)
+    {
+      throw new BuildException("emmaDataPath attribute is not set. It must be set to the path of the EMMA data directory");
+    }
+    if(outputPath == null)
+    {
+      throw new BuildException("outputPath attribute is not set. It must be set to a valid directory where the report will be generated");
+    }
+    if(fromRevision == null)
+    {
+      throw new BuildException("fromRevision attribute is not set. It must be set to the revision from which the diff is generated (e.g. BASE).");
+    }
+
+    if(!enabled)
+    {
+      return;
+    }
+
+    // So we can go over http:// and https:// when diff'ing against previous versions
+    DAVRepositoryFactory.setup();
+    
+    IReportDataView emmaDataView = null;
+    try
+    {
+        emmaDataView = loadEmmaData(emmaDataPath);
+        verboseOut("Loaded EMMA data.");
+    }
+    catch(IOException ie)
+    {
+      System.out.println("WARNING: An error occurred while loading EMMA " +
+          "data. Report will not contain any coverage information.");
+    }
+
+    try
+    {
+      processDiffOutput(getDiffOutputReader(), emmaDataView);
+    }
+    catch(IOException ie)
+    {
+      System.out.println("ERROR: An error occurred while processing diff output: " + ie.toString() + " Quitting...");
+      ie.printStackTrace();
+      return;
+    }
+    System.out.println("Coverage diff completed in " + (System.currentTimeMillis() - start) + " ms.");
+  }
+
+
+  private IReportDataView loadEmmaData(File emmaCoverageDataDir) throws IOException
+  {
+    if(emmaCoverageDataDir == null)
+    {
+      throw new IOException("Emma Converage Data Directory is null");
+    }
+
+    File[] emmaCoverageDataFiles = emmaCoverageDataDir.listFiles();
+    int emmaCoverageDataFileCount = 0;
+    IReportDataView m_view;
+    IMetaData mdata = null;
+    ICoverageData cdata = null;
+
+    if(emmaCoverageDataFiles == null || emmaCoverageDataFiles.length <= 0)
+    {
+      throw new IOException("No EMMA data files found");
+    }
+
+    verboseOut("processing input files ...");
+
+    final long start = System.currentTimeMillis();
+
+    // merge all data files:
+
+    for (final File dataFile : emmaCoverageDataFiles) {
+      verboseOut("processing input file [" + dataFile.getAbsolutePath() + "] ...");
+
+      final IMergeable[] fileData = DataFactory.load(dataFile);
+
+      final IMetaData _mdata = (IMetaData) fileData[DataFactory.TYPE_METADATA];
+      if (_mdata != null) {
+        verboseOut("  loaded " + _mdata.size() + " metadata entries");
+
+        if (mdata == null)
+          mdata = _mdata;
+        else
+          mdata = (IMetaData) mdata.merge(_mdata); // note: later datapath entries override earlier ones
+      }
+
+      final ICoverageData _cdata = (ICoverageData) fileData[DataFactory.TYPE_COVERAGEDATA];
+      if (_cdata != null) {
+        verboseOut("  loaded " + _cdata.size() + " coverage data entries");
+
+        if (cdata == null)
+          cdata = _cdata;
+        else
+          cdata = (ICoverageData) cdata.merge(_cdata); // note: later datapath entries override earlier ones
+      }
+
+      ++emmaCoverageDataFileCount;
+    }
+
+    verboseOut(emmaCoverageDataFileCount + " file(s) read and merged in " + (System.currentTimeMillis() - start) + " ms");
+
+    if ((mdata == null) || mdata.isEmpty()) {
+      System.out.println("nothing to do: no metadata found in any of the data files");
+      return null;
+    }
+
+    if (cdata == null) {
+      System.out.println("nothing to do: no runtime coverage data found in any of the data files");
+      return null;
+    }
+
+    if (cdata.isEmpty()) {
+      System.out.println("no collected coverage data found in any of the data files [Diff output will not include coverage data]");
+      return null;
+    }
+    if (!mdata.hasLineNumberData() || !mdata.hasSrcFileData()) {
+      System.out.println("no collected line coverage data found in any of the data files [Diff output will not include coverage data]");
+      return null;
+    }
+
+    final IReportDataModel model = IReportDataModel.Factory.create (mdata, cdata);
+    m_view = model.getView (IReportDataView.HIER_SRC_VIEW);
+
+    verboseOut("  merged metadata contains " + mdata.size() + " entries");
+    verboseOut("  merged coverage data contains " + cdata.size() + " entries");
+
+    return m_view;
+  }
+
+  private BufferedReader getDiffOutputReader()
+          throws IOException, SVNException {
+    File workspaceRoot = getProject().getBaseDir();
+
+    File diffFile = new File(outputPath, "svn.diff");
+
+    // Most often this will be 'BASE' but it could also be 'PREVIOUS'
+    SVNRevision baseRevision = SVNRevision.parse(fromRevision);
+    System.out.println("Doing coverage diff from revision: " + baseRevision.toString());
+
+    ourClientManager.getDiffClient().doDiff(workspaceRoot, baseRevision, 
+            workspaceRoot, SVNRevision.WORKING, SVNDepth.INFINITY, false,
+            new FileOutputStream(diffFile), null);
+
+    return new BufferedReader(new InputStreamReader(new FileInputStream(
+                                                             diffFile)));
+  }
+
+  private void processDiffOutput(BufferedReader diffOutput,
+                                        IReportDataView emmaDataView)
+      throws IOException {
+
+    File file = new File(outputPath, "index.html");
+    BufferedWriter writer =
+        new BufferedWriter (new OutputStreamWriter (
+            new FileOutputStream (file), ENCODING), IO_BUF_SIZE);
+    HTMLWriter out = new HTMLWriter(writer);
+
+    System.out.println("Writing report to [" + file.toString() + "]");
+
+    String title = "Coverage Diff Report (generated ";
+    title = title + new Date(System.currentTimeMillis ());
+    title = title + " )";
+
+    HTMLDocument page = new HTMLDocument (title, ENCODING);
+    page.addStyle (CSS);
+
+    String line = diffOutput.readLine();
+    ArrayList<String> diffOutputFile = new ArrayList<String>();
+
+    while(line != null)
+    {
+      //Diffed file
+      if(line.length() >6 && line.substring(0, 6).equals("Index:"))
+      {
+        processDiffOutputFile(page, diffOutputFile, emmaDataView);
+        diffOutputFile = new ArrayList<String>();
+        diffOutputFile.add(line);
+      }
+      else
+      {
+        diffOutputFile.add(line);
+      }
+
+      line = diffOutput.readLine();
+    }
+    processDiffOutputFile(page, diffOutputFile, emmaDataView);
+
+    IElementList overallStats = new ElementList();
+
+    final IElement statTitle = IElement.Factory.create (Tag.Hs[1]);
+    statTitle.setText("OVERALL STATS SUMMARY", true);
+
+    overallStats.add(statTitle);
+
+    final HTMLTable statsTable = new HTMLTable (null, null, null, "0");
+    statsTable.setClass ("it");
+    {
+      HTMLTable.IRow row = statsTable.newRow ();
+      row.newCell ().setText ("svn diff arg(s):", true);
+      row.newCell ().setText ("" + diffPath.toString(), true);
+
+      row = statsTable.newRow ();
+      row.newCell ().setText ("total files modified:", true);
+      row.newCell ().setText ("" + emmaSrcMap.keySet().size(), false);
+
+      Double[] overallModCoverage = new Double[4];
+      overallModCoverage[COVERED_MOD_EXE_LINES] = 0.0;
+      overallModCoverage[MOD_EXE_LINES] = 0.0;
+      overallModCoverage[MOD_LINES] = 0.0;
+      overallModCoverage[DEL_LINES] = 0.0;
+
+      Double[] modCoverage;
+      for (Double[] doubles : modCoverageMap.values()) {
+        modCoverage = doubles;
+
+        if (modCoverage != null) {
+          overallModCoverage[COVERED_MOD_EXE_LINES] += modCoverage[COVERED_MOD_EXE_LINES];
+          overallModCoverage[MOD_EXE_LINES] += modCoverage[MOD_EXE_LINES];
+          overallModCoverage[MOD_LINES] += modCoverage[MOD_LINES];
+          overallModCoverage[DEL_LINES] += modCoverage[DEL_LINES];
+        }
+      }
+      String modCoverageStr = "";
+      if(overallModCoverage[MOD_EXE_LINES] > 0)
+      {
+        modCoverageStr = String.format("%d%% (%.1f/%.1f)",
+            (int)(overallModCoverage[COVERED_MOD_EXE_LINES]/overallModCoverage[MOD_EXE_LINES]*100),
+            overallModCoverage[COVERED_MOD_EXE_LINES],
+            overallModCoverage[MOD_EXE_LINES]);
+      }
+      else
+      {
+        modCoverageStr = String.format("%d%% (%.1f/%.1f)", 100,
+            overallModCoverage[COVERED_MOD_EXE_LINES],
+            overallModCoverage[MOD_EXE_LINES]);
+      }
+
+      row = statsTable.newRow ();
+      row.newCell ().setText ("total lines modified:", true);
+      row.newCell ().setText ("" + overallModCoverage[MOD_LINES].intValue(), true);
+      row = statsTable.newRow ();
+      row.newCell ().setText ("total lines removed:", true);
+      row.newCell ().setText ("" + overallModCoverage[DEL_LINES].intValue(), true);
+      row = statsTable.newRow ();
+      row.newCell ().setText ("coverage for modified executable lines:", true);
+      row.newCell ().setText ("" + modCoverageStr, true);
+    }
+
+    overallStats.add(statsTable);
+
+    final IElement coverageTitle = IElement.Factory.create (Tag.Hs[1]);
+    statTitle.setText("OVERALL DIFF SUMMARY", true);
+
+    overallStats.add(coverageTitle);
+
+    HTMLTable summaryTable = new HTMLTable ("100%", null, null, "0");
+    if(emmaDataView != null)
+    {
+      addHeaderRow(emmaDataView.getRoot(), summaryTable, true);
+    }
+    else
+    {
+      addHeaderRow(null, summaryTable, true);
+    }
+
+    Set<Map.Entry<String, SrcFileItem>> items = emmaSrcMap.entrySet();
+    boolean odd = true;
+    int count = 0;
+
+    for (Map.Entry<String, SrcFileItem> item : items) {
+
+      if (item != null) {
+        final String fileName = item.getKey();
+        final SrcFileItem srcFileItem = item.getValue();
+        final Double[] modCoverage = modCoverageMap.get(fileName);
+
+        addItemRow(fileName, srcFileItem, modCoverage, odd, summaryTable,
+            "s" + count, true, true);
+
+        odd = !odd;
+        count++;
+      }
+    }
+
+    overallStats.add(summaryTable);
+
+    page.setHeader(overallStats);
+
+    page.emit(out);
+    out.flush();
+  }
+
+  private void processDiffOutputFile(HTMLDocument html,
+                                            ArrayList<String> diffFile,
+                                            IReportDataView emmaDataView)
+      throws IOException
+  {
+    if(diffFile.size() <= 0)
+    {
+      return;
+    }
+
+    Double[] modCoverage = new Double[4];
+    modCoverage[COVERED_MOD_EXE_LINES] = 0.0;
+    modCoverage[MOD_EXE_LINES] = 0.0;
+    modCoverage[MOD_LINES] = 0.0;
+    modCoverage[DEL_LINES] = 0.0;
+
+    String fileHeader = diffFile.get(0);
+    verboseOut("fileHeader: " + diffFile);
+
+    //Try to get the package information if its a Java file
+    File srcFilePath = new File(fileHeader.substring(7));
+    SrcFileItem emmaSourceItem = null;
+    if(srcFilePath.isFile())
+    {
+      FileInputStream srcFile = new FileInputStream(srcFilePath);
+      String srcFilePackage = parseJavaPackage(srcFile);
+      if(emmaDataView != null)
+      {
+        emmaSourceItem = getEmmaSrcItem(emmaDataView.getRoot(),
+                  srcFilePackage, srcFilePath.getName());
+      }
+    }
+
+
+    //Figure out the flag for the working copy.
+    String workingCopyFlag = null;
+    String otherCopyFlag = null;
+
+    String firstFileLine = diffFile.get(2);
+    String secondFileLine = diffFile.get(3);
+    verboseOut("firstFileLine=" + firstFileLine);
+    verboseOut("secondFileLine=" + secondFileLine);
+    String revisionStr = "unknown";
+
+    // Skip over binary files
+    if (firstFileLine.contains("Cannot display")) {
+      return;
+    }
+
+    HTMLTable srcTable = null;
+
+    if(firstFileLine.endsWith("(working copy)"))
+    {
+      workingCopyFlag = firstFileLine.substring(0, 1);
+    }
+    else
+    {
+      otherCopyFlag = firstFileLine.substring(0, 1);
+      revisionStr = firstFileLine.substring(firstFileLine.lastIndexOf("("));
+    }
+
+    if(secondFileLine.endsWith("(working copy)"))
+    {
+      workingCopyFlag = secondFileLine.substring(0, 1);
+    }
+    else
+    {
+      otherCopyFlag = secondFileLine.substring(0, 1);
+      revisionStr = secondFileLine.substring(secondFileLine.lastIndexOf("("));
+    }
+
+    if(firstFileLine.endsWith("(revision 0)") ||
+        secondFileLine.endsWith("(revision 0)"))
+    {
+      workingCopyFlag = "+";
+      otherCopyFlag = "-";
+    }
+
+    if(workingCopyFlag == null || otherCopyFlag == null)
+    {
+      throw new IOException("Error occurred while parsing diff output." + EOL +
+        "firstFileLine= '" + firstFileLine + "'" + EOL +
+        "secondFileLine= '" + secondFileLine + "'");
+    }
+    else
+    {
+      srcTable = new HTMLTable ("100%", null, null, "0");
+      srcTable.setClass("s");
+
+      ArrayList<String> diffOutputChunk = new ArrayList<String>();
+      Double[] chunkModCoverage;
+
+      for(int i = 4; i < diffFile.size(); i++)
+      {
+        //Found a chunk indicator.
+        if(diffFile.get(i).startsWith("@@"))
+        {
+          chunkModCoverage = processDiffOutputFileChunk(srcTable, diffOutputChunk, workingCopyFlag,
+                otherCopyFlag, emmaSourceItem);
+
+          if(chunkModCoverage != null)
+          {
+            modCoverage[COVERED_MOD_EXE_LINES] += chunkModCoverage[COVERED_MOD_EXE_LINES];
+            modCoverage[MOD_EXE_LINES] += chunkModCoverage[MOD_EXE_LINES];
+            modCoverage[MOD_LINES] += chunkModCoverage[MOD_LINES];
+            modCoverage[DEL_LINES] += chunkModCoverage[DEL_LINES];
+          }
+
+          diffOutputChunk = new ArrayList<String>();
+          diffOutputChunk.add(diffFile.get(i));
+        }
+        //Not any of the above so this line must be diffed text
+        else
+        {
+          diffOutputChunk.add(diffFile.get(i));
+        }
+      }
+
+      //Finishing process whatever we have queued up
+      chunkModCoverage = processDiffOutputFileChunk(srcTable, diffOutputChunk, workingCopyFlag,
+          otherCopyFlag, emmaSourceItem);
+      if(chunkModCoverage != null)
+      {
+        modCoverage[COVERED_MOD_EXE_LINES] += chunkModCoverage[COVERED_MOD_EXE_LINES];
+        modCoverage[MOD_EXE_LINES] += chunkModCoverage[MOD_EXE_LINES];
+        modCoverage[MOD_LINES] += chunkModCoverage[MOD_LINES];
+        modCoverage[DEL_LINES] += chunkModCoverage[DEL_LINES];
+      }
+    }
+
+    final IElement a = IElement.Factory.create (Tag.A);
+    a.getAttributes ().set (Attribute.NAME, "s" + emmaSrcMap.keySet().size());
+
+    html.add(a);
+
+    final IElement itemname = IElement.Factory.create (Tag.SPAN);
+    {
+      itemname.setText (srcFilePath.toString(), true);
+      itemname.setClass ("in");
+    }
+
+    final IElementList title = new ElementList ();
+    {
+      title.add (new Text ("DIFF SUMMARY FOR SOURCE FILE [", true));
+      title.add (itemname);
+      title.add (new Text ("] against ", true));
+      title.add (new Text (revisionStr, true));
+    }
+
+    html.addH (1, title, null);
+
+    if(emmaSourceItem != null)
+    {
+      final HTMLTable coverageTable = new HTMLTable ("100%", null, null, "0");
+      addHeaderRow(emmaSourceItem, coverageTable, false);
+      addItemRow(srcFilePath.toString(), emmaSourceItem, modCoverage, false, coverageTable, null, false, false);
+
+      html.add(coverageTable);
+
+      html.addEmptyP();
+    }
+    else
+    {
+      html.addH(2, "Coverage Information Not Available (e.g. file is not in src/, is not java, is an interface, or was deleted)", null);
+    }
+
+    if(srcTable != null)
+    {
+      html.add(srcTable);
+    }
+
+    emmaSrcMap.put(srcFilePath.toString(), emmaSourceItem);
+    modCoverageMap.put(srcFilePath.toString(), modCoverage);
+  }
+
+  private Double[] processDiffOutputFileChunk(HTMLTable table,
+                                                 ArrayList<String> diffChunk,
+                                                 String workingCopyFlag,
+                                                 String otherCopyFlag,
+                                                 SrcFileItem emmaSrcItem)
+  {
+
+    if(diffChunk.size() <= 0)
+    {
+      return null;
+    }
+
+    int workingCopyBegin;
+    int workingCopyRange;
+    int otherCopyBegin;
+    int otherCopyRange;
+
+    Double[] modCoverage = new Double[4];
+    modCoverage[COVERED_MOD_EXE_LINES] = 0.0;
+    modCoverage[MOD_EXE_LINES] = 0.0;
+    modCoverage[MOD_LINES] = 0.0;
+    modCoverage[DEL_LINES] = 0.0;
+
+    IntObjectMap lineCoverageMap = null;
+    if(emmaSrcItem != null)
+    {
+      lineCoverageMap = emmaSrcItem.getLineCoverage ();
+    }
+
+    String chunkHeader = diffChunk.get(0);
+
+    int workingCopyBeginIdx = chunkHeader.indexOf(workingCopyFlag);
+    int workingCopyCommaIdx = chunkHeader.indexOf(",", workingCopyBeginIdx);
+    int workingCopyEndIdx = chunkHeader.indexOf(" ", workingCopyCommaIdx);
+    int otherCopyBeginIdx = chunkHeader.indexOf(otherCopyFlag);
+    int otherCopyCommaIdx = chunkHeader.indexOf(",", otherCopyBeginIdx);
+    int otherCopyEndIdx = chunkHeader.indexOf(" ", otherCopyCommaIdx);
+    workingCopyBegin = Integer.parseInt(
+        chunkHeader.substring(workingCopyBeginIdx + 1, workingCopyCommaIdx));
+    workingCopyRange = Integer.parseInt(
+        chunkHeader.substring(workingCopyCommaIdx + 1, workingCopyEndIdx));
+    otherCopyBegin = Integer.parseInt(
+        chunkHeader.substring(otherCopyBeginIdx + 1, otherCopyCommaIdx));
+    otherCopyRange = Integer.parseInt(
+        chunkHeader.substring(otherCopyCommaIdx + 1, otherCopyEndIdx));
+
+    String chunkLine;
+    SrcFileItem.LineCoverageData lCoverageData = null;
+    int workingCopyLine = workingCopyBegin;
+    int otherCopyLine = otherCopyBegin;
+
+    final HTMLTable.IRow chunkRow = table.newTitleRow();
+    final HTMLTable.ICell chunkCell = chunkRow.newCell();
+    chunkCell.setColspan(2);
+    chunkCell.setText("Lines " + workingCopyBegin + " - " +
+        String.valueOf(workingCopyLine + workingCopyRange), true);
+
+    for(int i = 1; i < diffChunk.size(); i++)
+    {
+      chunkLine = diffChunk.get(i);
+
+      if(lineCoverageMap != null)
+      {
+        lCoverageData = (SrcFileItem.LineCoverageData) lineCoverageMap.get (workingCopyLine);
+      }
+
+      final HTMLTable.IRow srcRow = table.newRow();
+      final HTMLTable.ICell lineNumCell = srcRow.newCell();
+      final HTMLTable.ICell lineTxtCell = srcRow.newCell();
+
+      if (chunkLine.length() == 0) {
+        lineTxtCell.setText(" ", true);
+      } else {
+        lineTxtCell.setText(chunkLine.substring(1), true);
+      }
+
+      //This line is either a modified line or a unchanged line
+      if(!chunkLine.startsWith(otherCopyFlag))
+      {
+        lineNumCell.setText(String.valueOf(workingCopyLine), true);
+
+        //Determine if this line is a modified line or a unchange line
+        if(chunkLine.startsWith(workingCopyFlag))
+        {
+          lineNumCell.setClass("dm");
+          modCoverage[MOD_LINES] ++;
+
+          if(lCoverageData != null)
+          {
+            modCoverage[MOD_EXE_LINES] ++;
+            switch(lCoverageData.m_coverageStatus)
+            {
+              case SrcFileItem.LineCoverageData.LINE_COVERAGE_ZERO:
+                lineTxtCell.setClass ("cz");
+                break;
+
+              case SrcFileItem.LineCoverageData.LINE_COVERAGE_PARTIAL:
+                lineTxtCell.setClass ("cp");
+                modCoverage[COVERED_MOD_EXE_LINES] += 0.5;
+                break;
+
+              case SrcFileItem.LineCoverageData.LINE_COVERAGE_COMPLETE:
+                lineTxtCell.setClass ("cc");
+                modCoverage[COVERED_MOD_EXE_LINES] ++;
+                break;
+              default:
+            }
+          }
+        }
+        else
+        {
+          lineNumCell.setClass("ds");
+        }
+
+      }
+      else
+      {
+        lineNumCell.setClass("dd");
+        lineNumCell.setText(String.valueOf(otherCopyLine), true);
+        lineTxtCell.setClass("ddt");
+        modCoverage[DEL_LINES] ++;
+      }
+
+      if(!chunkLine.startsWith(otherCopyFlag))
+      {
+        workingCopyLine++;
+      }
+      if(!chunkLine.startsWith(workingCopyFlag))
+      {
+        otherCopyLine++;
+      }
+    }
+
+    return modCoverage;
+  }
+
+  private String parseJavaPackage(FileInputStream srcFile)
+      throws IOException {
+
+    BufferedReader srcFileReader = new BufferedReader(
+        new InputStreamReader(srcFile));
+
+    String line = srcFileReader.readLine();
+    while(line != null)
+    {
+      int beginIdx = line.indexOf("package");
+      if(beginIdx > -1)
+      {
+        int endIdx = line.indexOf(";", beginIdx);
+        if(endIdx > -1)
+        {
+          return line.substring(beginIdx + 7, endIdx).trim();
+        }
+      }
+      line = srcFileReader.readLine();
+    }
+
+    return null;
+  }
+
+  private  SrcFileItem getEmmaSrcItem(IItem rootItem,
+                                      String srcPackageName,
+                                      String srcFileName)
+  {
+    if(rootItem == null || srcPackageName == null || srcFileName == null)
+    {
+      return null;
+    }
+
+    for(Iterator packages = rootItem.getChildren(); packages.hasNext();)
+    {
+      IItem packageItem = (IItem)packages.next();
+      if(packageItem.getName().equals(srcPackageName))
+      {
+        for(Iterator sources = packageItem.getChildren(); sources.hasNext();)
+        {
+          SrcFileItem sourceItem = (SrcFileItem)sources.next();
+          if(sourceItem.getName().equals(srcFileName))
+          {
+            return sourceItem;
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+  private void addHeaderRow (final IItem item, final HTMLTable table, boolean includeName)
+  {
+
+    // header row:
+    final HTMLTable.IRow header = table.newTitleRow ();
+
+    if(includeName)
+    {
+      final HTMLTable.ICell nameCell = header.newCell();
+      nameCell.setText("File", true);
+    }
+
+    for (int c = 1; c <= 4; ++ c)
+    {
+      IItemAttribute attr = null;
+
+      if(item != null)
+      {
+        attr = item.getAttribute (c, 0);
+      }
+
+      if (attr != null)
+      {
+        final HTMLTable.ICell cell = header.newCell ();
+
+        cell.setText (attr.getName (), true);
+      }
+      else
+      {
+        final HTMLTable.ICell cell = header.newCell ();
+        cell.setText (" ", true);
+      }
+
+    }
+
+    if(item != null)
+    {
+      final HTMLTable.ICell cell = header.newCell();
+      cell.setText("mod lines, %", true);
+    }
+    else
+    {
+      final HTMLTable.ICell cell = header.newCell ();
+      cell.setText (" ", true);
+    }
+
+  }
+
+  /*
+     * No header row, just data rows.
+     */
+  private void addItemRow (final String fileName,
+                           final IItem item,
+                           final Double[] modCoverage,
+                           final boolean odd,
+                           final HTMLTable table,
+                           final String nameHREF,
+                           final boolean anchor,
+                           final boolean includeName)
+  {
+    final HTMLTable.IRow row = table.newRow ();
+    if (odd) row.setClass ("o");
+
+    if(includeName)
+    {
+      final HTMLTable.ICell nameCell = row.newCell();
+      if(nameHREF != null)
+      {
+        final String fullHREFName = anchor ? "#".concat (nameHREF) : nameHREF;
+        nameCell.add(new HyperRef(fullHREFName, fileName, true));
+      }
+      else
+      {
+        nameCell.setText(fileName, true);
+      }
+    }
+
+    final StringBuffer buf = new StringBuffer (11);
+
+    for (int c = 1; c <=4; ++ c)
+    {
+      IItemAttribute attr = null;
+
+      if(item != null)
+      {
+        attr = item.getAttribute (c, 0);
+      }
+
+      if (attr != null)
+      {
+        final HTMLTable.ICell cell = row.newCell ();
+
+
+        //final boolean fail = (m_metrics [attrID] > 0) && ! attr.passes (item, m_metrics [attrID]);
+
+        buf.setLength (0);
+        attr.format (item, buf);
+
+        cell.setText (buf.toString (), true);
+        //if (fail) cell.setClass (CSS_DATA_HIGHLIGHT);
+
+      }
+      else
+      {
+
+        final HTMLTable.ICell cell = row.newCell ();
+        cell.setText (" ", true);
+      }
+    }
+
+    if(item != null && modCoverage != null)
+    {
+      String modCoverageStr = "";
+      if(modCoverage[1] > 0)
+      {
+        modCoverageStr = String.format("%d%% (%.1f/%.1f)",
+            (int)(modCoverage[COVERED_MOD_EXE_LINES]/modCoverage[MOD_EXE_LINES]*100),
+            modCoverage[COVERED_MOD_EXE_LINES], modCoverage[MOD_EXE_LINES]);
+      }
+      else
+      {
+        modCoverageStr = String.format("%d%% (%.1f/%.1f)", 100,
+            modCoverage[COVERED_MOD_EXE_LINES],
+            modCoverage[MOD_EXE_LINES]);
+      }
+
+      final HTMLTable.ICell cell = row.newCell();
+      cell.setText(modCoverageStr, true);
+    }
+    else
+    {
+      final HTMLTable.ICell cell = row.newCell ();
+      cell.setText (" ", true);
+    }
+  }
+
+  // Enable this with -Dtest.diff.verbose=true from the commandline
+  private void verboseOut(Object msg)
+  {
+    if (verbose)
+    {
+      System.out.println(msg.toString());
+    }
+  }
+}
diff --git a/sdk/build-tools/org/opends/build/tools/CreateVersionString.java b/sdk/build-tools/org/opends/build/tools/CreateVersionString.java
new file mode 100644
index 0000000..b23b2da
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/CreateVersionString.java
@@ -0,0 +1,98 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+
+
+import java.text.DecimalFormat;
+
+import org.apache.tools.ant.Task;
+
+
+
+/**
+ * This class provides an implemenation of an Ant task that may be used to
+ * construct the full version number string that the Directory Server should
+ * use.  The value of the version number string will be stored in an Ant
+ * property.
+ */
+public class CreateVersionString
+       extends Task
+{
+  // The name of the property in which the revision number should be set.
+  private String propertyName = null;
+
+
+
+  /**
+   * Specifies the name of the Ant property into which the Subversion revision
+   * number will be stored.
+   *
+   * @param  propertyName  The name of the Ant property into which the
+   *                       Subversion revision number will be stored.
+   */
+  public void setProperty(String propertyName)
+  {
+    this.propertyName = propertyName;
+  }
+
+
+
+  /**
+   * Performs the appropriate processing needed for this task.  In this case,
+   * it uses SVNKit to identify the current revision number for the local
+   * workspace and store it in a specified property.
+   */
+  @Override()
+  public void execute()
+  {
+    StringBuilder versionString = new StringBuilder();
+
+    versionString.append(getProject().getProperty("MAJOR_VERSION"));
+    versionString.append(".");
+    versionString.append(getProject().getProperty("MINOR_VERSION"));
+    versionString.append(".");
+    versionString.append(getProject().getProperty("POINT_VERSION"));
+
+    String versionQualifier = getProject().getProperty("VERSION_QUALIFIER");
+    versionString.append(versionQualifier);
+
+    try
+    {
+      int buildNumber =
+           Integer.parseInt(getProject().getProperty("BUILD_NUMBER"));
+      if (buildNumber > 0)
+      {
+        versionString.append("-build");
+        versionString.append(new DecimalFormat("000").format(buildNumber));
+      }
+    } catch (NumberFormatException nfe) {}
+
+    getProject().setNewProperty(propertyName, versionString.toString());
+  }
+}
+
diff --git a/sdk/build-tools/org/opends/build/tools/GenerateMessageFile.java b/sdk/build-tools/org/opends/build/tools/GenerateMessageFile.java
new file mode 100644
index 0000000..dd3371e
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/GenerateMessageFile.java
@@ -0,0 +1,995 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Location;
+import static org.opends.build.tools.Utilities.*;
+import org.opends.messages.Category;
+import org.opends.messages.Severity;
+import org.opends.messages.MessageDescriptor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.UnknownFormatConversionException;
+import java.util.Calendar;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.EnumSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Generates a Java class containing representations of messages
+ * found in a properties file.
+ */
+public class GenerateMessageFile extends Task {
+
+  private File source;
+  private File dest;
+  private boolean overwrite;
+
+  static private final String MESSAGES_FILE_STUB =
+          "resource/Messages.java.stub";
+
+  /*
+   * The registry filename is the result of the concatenation of the
+   * location of where the source are generated, the package name and the
+   * DESCRIPTORS_REG value.
+   */
+  static private String REGISTRY_FILE_NAME;
+
+  static private final String DESCRIPTORS_REG = "descriptors.reg";
+
+  /**
+   * Used to set a category for all messages in the property file.
+   * If set, the category for each message need not be encoded in
+   * the message's property file key.
+   */
+  static private final String GLOBAL_CATEGORY = "global.category";
+
+  /**
+   * Used to set a severity for all messages in the property file.
+   * If set, the severity for each message need not be encoded in
+   * the message's property file key.
+   */
+  static private final String GLOBAL_SEVERITY = "global.severity";
+
+  /**
+   * Used to set a category mask for all messages in the property
+   * file.  If set, the category will automatically be assigned
+   * USER_DEFINED and the value of <code>GLOBAL_CATEGORY</code>
+   * will be ignored.
+   */
+  static private final String GLOBAL_CATEGORY_MASK = "global.mask";
+
+  /**
+   * When true generates messages that have no ordinals.
+   */
+  static private final String GLOBAL_ORDINAL = "global.ordinal";
+
+  /**
+   * When true and if the Java Web Start property is set use the class loader of
+   * the jar where the MessageDescriptor is contained to retrieve the
+   * ResourceBundle.
+   */
+  static private final String GLOBAL_USE_MESSAGE_JAR_IF_WEBSTART =
+    "global.use.message.jar.if.webstart";
+
+  static private final Set<String> DIRECTIVE_PROPERTIES = new HashSet<String>();
+  static {
+    DIRECTIVE_PROPERTIES.add(GLOBAL_CATEGORY);
+    DIRECTIVE_PROPERTIES.add(GLOBAL_CATEGORY_MASK);
+    DIRECTIVE_PROPERTIES.add(GLOBAL_SEVERITY);
+    DIRECTIVE_PROPERTIES.add(GLOBAL_ORDINAL);
+    DIRECTIVE_PROPERTIES.add(GLOBAL_USE_MESSAGE_JAR_IF_WEBSTART);
+  }
+
+  static private final String SPECIFIER_REGEX =
+          "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
+
+  private final Pattern SPECIFIER_PATTERN = Pattern.compile(SPECIFIER_REGEX);
+
+  /**
+   * Message giving formatting rules for string keys.
+   */
+  static public String KEY_FORM_MSG;
+
+  static {
+    KEY_FORM_MSG = new StringBuilder()
+            .append(".\n\nOpenDS message property keys must be of the form\n\n")
+            .append("\t\'[CATEGORY]_[SEVERITY]_[DESCRIPTION]_[ORDINAL]\'\n\n")
+            .append("where\n\n")
+            .append("CATEGORY is one of ")
+            .append(EnumSet.allOf(Category.class))
+            .append("\n\nSEVERITY is one of ")
+            .append(Severity.getPropertyKeyFormSet().toString())
+            .append("\n\nDESCRIPTION is a descriptive string composed ")
+            .append("of uppercase character, digits and underscores ")
+            .append("describing the purpose of the message ")
+            .append("\n\nORDINAL is an integer between 0 and 65535 that is ")
+            .append("unique to other messages defined in this file.\n\n")
+            .append("You can relax the mandate for including the CATEGORY, ")
+            .append("SEVERITY, and/or ORDINAL by including one or more ")
+            .append("of the following respective property directives in your ")
+            .append("properties file:  ")
+            .append(GLOBAL_CATEGORY)
+            .append(", ")
+            .append(GLOBAL_SEVERITY)
+            .append(", ")
+            .append(GLOBAL_ORDINAL)
+            .append("and setting their value appropriately.")
+            .toString();
+  }
+
+  /*
+   * ISO_LANGUAGES contains all official supported languages for i18n
+   */
+  private static final List<String> ISO_LANGUAGES =
+                                        Arrays.asList(Locale.getISOLanguages());
+  /*
+   * ISO_COUNTRIES contains all official supported countries for i18n
+   */
+  private static final List<String> ISO_COUNTRIES =
+                                        Arrays.asList(Locale.getISOCountries());
+
+  /*
+   * A Pattern instance that matches "<label>_<language>_<country>.properties"
+   * where <label> can be anything including '_'
+   *       <language> a two characters code contained in the ISO_LANGUAGES list
+   *       <country> a two characters code contained in the ISO_COUNTRIES list
+   */  
+  private static final Pattern LANGUAGE_COUNTRY_MATCHER =
+                       Pattern.compile("(.*)_([a-z]{2})_([A-Z]{2}).properties");
+  /*
+   * A Pattern instance that matches "<label>_<language>.properties"
+   * where <label> and <language> have same definition as above.
+   */
+  private static final Pattern LANGUAGE_MATCHER =
+                       Pattern.compile("(.*)_([a-z]{2}).properties");
+
+  /**
+   * Representation of a format specifier (for example %s).
+   */
+  private class FormatSpecifier {
+
+    private String[] sa;
+
+    /**
+     * Creates a new specifier.
+     * @param sa specifier components
+     */
+    FormatSpecifier(String[] sa) {
+      this.sa = sa;
+    }
+
+    /**
+     * Indicates whether or not the specifier uses arguement
+     * indexes (for example 2$).
+     * @return boolean true if this specifier uses indexing
+     */
+    public boolean specifiesArgumentIndex() {
+      return this.sa[0] != null;
+    }
+
+    /**
+     * Returns a java class associated with a particular formatter
+     * based on the conversion type of the specifier.
+     * @return Class for representing the type of arguement used
+     *         as a replacement for this specifier.
+     */
+    public Class getSimpleConversionClass() {
+      Class c = null;
+      String sa4 = sa[4] != null ? sa[4].toLowerCase() : null;
+      String sa5 = sa[5] != null ? sa[5].toLowerCase() : null;
+      if ("t".equals(sa4)) {
+        c = Calendar.class;
+      } else if (
+              "b".equals(sa5)) {
+        c = Boolean.class;
+      } else if (
+              "h".equals(sa5)) {
+        c = Integer.class;
+      } else if (
+              "s".equals(sa5)) {
+        c = CharSequence.class;
+      } else if (
+              "c".equals(sa5)) {
+        c = Character.class;
+      } else if (
+              "d".equals(sa5) ||
+              "o".equals(sa5) ||
+              "x".equals(sa5) ||
+              "e".equals(sa5) ||
+              "f".equals(sa5) ||
+              "g".equals(sa5) ||
+              "a".equals(sa5)) {
+        c = Number.class;
+      } else if (
+              "n".equals(sa5) ||
+              "%".equals(sa5)) {
+        // ignore literals
+      }
+      return c;
+    }
+
+  }
+
+  /**
+   * Represents a message to be written into the messages files.
+   */
+  private class MessageDescriptorDeclaration {
+
+    private MessagePropertyKey key;
+    private String formatString;
+    private List<FormatSpecifier> specifiers;
+    private List<Class> classTypes;
+    private String[] constructorArgs;
+
+    /**
+     * Creates a parameterized instance.
+     * @param key of the message
+     * @param formatString of the message
+     */
+    public MessageDescriptorDeclaration(MessagePropertyKey key,
+                                     String formatString) {
+      this.key = key;
+      this.formatString = formatString;
+      this.specifiers = parse(formatString);
+      this.classTypes = new ArrayList<Class>();
+      for (FormatSpecifier f : specifiers) {
+        Class c = f.getSimpleConversionClass();
+        if (c != null) {
+          classTypes.add(c);
+        }
+      }
+    }
+
+    /**
+     * Gets the name of the Java class that will be used to represent
+     * this message's type.
+     * @return String representing the Java class name
+     */
+    public String getDescriptorClassDeclaration() {
+      StringBuilder sb = new StringBuilder();
+      if (useGenericMessageTypeClass()) {
+        sb.append(getShortClassName(MessageDescriptor.class));
+        sb.append(".");
+        sb.append(MessageDescriptor.DESCRIPTOR_CLASS_BASE_NAME);
+        sb.append("N");
+      } else {
+        sb.append(getShortClassName(MessageDescriptor.class));
+        sb.append(".");
+        sb.append(MessageDescriptor.DESCRIPTOR_CLASS_BASE_NAME);
+        sb.append(classTypes.size());
+        sb.append(getClassTypeVariables());
+      }
+      return sb.toString();
+    }
+
+    /**
+     * Gets a string representing the message type class' variable
+     * information (for example '<String,Integer>') that is based on
+     * the type of arguments specified  by the specifiers in this message.
+     * @return String representing the message type class parameters
+     */
+    public String getClassTypeVariables() {
+      StringBuilder sb = new StringBuilder();
+      if (classTypes.size() > 0) {
+        sb.append("<");
+        for (int i = 0; i < classTypes.size(); i++) {
+          Class c = classTypes.get(i);
+          if (c != null) {
+            sb.append(getShortClassName(c));
+            if (i < classTypes.size() - 1) {
+              sb.append(",");
+            }
+          }
+        }
+        sb.append(">");
+      }
+      return sb.toString();
+    }
+
+    /**
+     * Gets the javadoc comments that will appear above the messages declaration
+     * in the messages file.
+     * @return String comment
+     */
+    public String getComment() {
+      StringBuilder sb = new StringBuilder();
+      sb.append(indent(1)).append("/**").append(EOL);
+
+      // Unwrapped so that you can search through the descriptor
+      // file for a message and not have to worry about line breaks
+      String ws = formatString; // wrapText(formatString, 70);
+
+      String[] sa = ws.split(EOL);
+      for (String s : sa) {
+        sb.append(indent(1)).append(" * ").append(s).append(EOL);
+      }
+      sb.append(indent(1)).append(" */").append(EOL);
+      return sb.toString();
+    }
+
+    /**
+     * Sets the arguments that will be supplied in the declaration
+     * of the message.
+     * @param s array of string arguments that will be passed
+     *        in the constructor
+     */
+    public void setConstructorArguments(String... s) {
+      this.constructorArgs = s;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString() {
+      StringBuilder sb = new StringBuilder();
+      sb.append(getComment());
+      sb.append(indent(1));
+      sb.append("public static final ");
+      sb.append(getDescriptorClassDeclaration());
+      sb.append(" ");
+      sb.append(key.getMessageDescriptorName());
+      sb.append(" =");
+      sb.append(EOL);
+      sb.append(indent(5));
+      sb.append("new ");
+      sb.append(getDescriptorClassDeclaration());
+      sb.append("(");
+      if (constructorArgs != null) {
+        for (int i = 0; i < constructorArgs.length; i++) {
+          sb.append(constructorArgs[i]);
+          if (i < constructorArgs.length - 1) {
+            sb.append(",");
+          }
+        }
+        sb.append(", ");
+      }
+      sb.append("getClassLoader()");
+      sb.append(");");
+      return sb.toString();
+    }
+
+    /**
+     * Indicates whether the generic message type class should
+     * be used.  In general this is when a format specifier is
+     * more complicated than we support or when the number of
+     * arguments exceeeds the number of specific message type
+     * classes (MessageType0, MessageType1 ...) that are defined.
+     * @return boolean indicating
+     */
+    private boolean useGenericMessageTypeClass() {
+      if (specifiers.size() > MessageDescriptor.DESCRIPTOR_MAX_ARG_HANDLER) {
+        return true;
+      } else if (specifiers != null) {
+        for (FormatSpecifier s : specifiers) {
+          if (s.specifiesArgumentIndex()) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    /**
+     * Look for format specifiers in the format string.
+     * @param s format string
+     * @return list of format specifiers
+     */
+    private List<FormatSpecifier> parse(String s) {
+      List<FormatSpecifier> sl = new ArrayList<FormatSpecifier>();
+      Matcher m = SPECIFIER_PATTERN.matcher(s);
+      int i = 0;
+      while (i < s.length()) {
+        if (m.find(i)) {
+          // Anything between the start of the string and the beginning
+          // of the format specifier is either fixed text or contains
+          // an invalid format string.
+          if (m.start() != i) {
+            // Make sure we didn't miss any invalid format specifiers
+            checkText(s.substring(i, m.start()));
+            // Assume previous characters were fixed text
+            //al.add(new FixedString(s.substring(i, m.start())));
+          }
+
+          // Expect 6 groups in regular expression
+          String[] sa = new String[6];
+          for (int j = 0; j < m.groupCount(); j++) {
+            sa[j] = m.group(j + 1);
+          }
+          sl.add(new FormatSpecifier(sa));
+          i = m.end();
+        } else {
+          // No more valid format specifiers.  Check for possible invalid
+          // format specifiers.
+          checkText(s.substring(i));
+          // The rest of the string is fixed text
+          //al.add(new FixedString(s.substring(i)));
+          break;
+        }
+      }
+      return sl;
+    }
+
+    private void checkText(String s) {
+      int idx;
+      // If there are any '%' in the given string, we got a bad format
+      // specifier.
+      if ((idx = s.indexOf('%')) != -1) {
+        char c = (idx > s.length() - 2 ? '%' : s.charAt(idx + 1));
+        throw new UnknownFormatConversionException(String.valueOf(c));
+      }
+    }
+
+  }
+
+  /**
+   * Sets the source of the messages.
+   * @param source File representing the properties
+   *        file containing messages
+   */
+  public void setSourceProps(File source) {
+    this.source = source;
+  }
+
+  /**
+   * Sets the file that will be generated containing
+   * declarations of messages from <code>source</code>.
+   * @param dest File destination
+   */
+  public void setDestJava(File dest) {
+    this.dest = dest;
+
+    /*
+     * Set the descriptors.reg pathname to the same directory as the one used
+     * to generate files and ensure all messages are generated in one place.
+     */
+    String projectBase = null;
+    try {
+      projectBase = getProject().getBaseDir().getCanonicalPath();
+    } catch( java.io.IOException e) {
+      throw new BuildException("Error processing " + dest +
+            ": unable to retrieve project's directory of ant's project (" +
+            e + ")");
+    }
+
+    String registry = dest.getAbsolutePath();
+    // strip project directory prefix and replace properties filename with
+    // $DESCRIPTORS_REG
+    registry = registry.substring(projectBase.length()+1,
+                                 registry.lastIndexOf(File.separator)+1)
+                       .concat(DESCRIPTORS_REG);
+
+    if ( REGISTRY_FILE_NAME == null ) {
+      REGISTRY_FILE_NAME = registry;
+    } else {
+      if ( ! REGISTRY_FILE_NAME.equals(registry) ) {
+        // multiple messages are generated in several packages
+        StringBuilder sb = new StringBuilder();
+        // full pathname of $REGISTRY_FILE_NAME
+        sb.append(projectBase)
+          .append(File.separator)
+          .append(REGISTRY_FILE_NAME);
+        // change from generated directory to properties files directory
+        sb.replace(0,
+                   getProject().getProperty("msg.javagen.dir").length(),
+                   getProject().getProperty("msg.dir"));
+        // replace properties filename with source filename
+        sb.replace(sb.lastIndexOf(File.separator)+1,
+                   sb.length(),
+                   source.getName());
+        throw new BuildException("Error processing " + dest +
+              ": all messages must be located in the same package thus " +
+              "name of the source file should be " + sb);
+
+      }
+    }
+  }
+
+  /**
+   * Indicates when true that an existing destination
+   * file will be overwritten.
+   * @param o boolean where true means overwrite
+   */
+  public void setOverwrite(boolean o) {
+    this.overwrite = o;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void execute() throws BuildException {
+
+    if ( this.dest == null ) {
+      // this is an example-plugin call:
+      // - check the source file is not a localization
+      // - guess the destination filename from source filename
+      String sourcefilename = source.getAbsolutePath();
+      int filenameIndex = sourcefilename.lastIndexOf(File.separator)+1;
+      String pathname = sourcefilename.substring(0, filenameIndex);
+      String filename = sourcefilename.substring(filenameIndex);
+
+      /*
+       * Make sure only <label>.properties are generated thus avoiding to
+       * generate messages for localized properties files.
+       */
+      Matcher matcher = LANGUAGE_COUNTRY_MATCHER.matcher(filename);
+      if ( matcher.find() ) {
+        if ( ISO_LANGUAGES.contains(matcher.group(2))
+          && ISO_COUNTRIES.contains(matcher.group(3)) ) {
+          // do not generate message for <label>_<language>_<country>.properties
+          return;
+        }
+      }
+
+      matcher = LANGUAGE_MATCHER.matcher(filename);
+      if ( matcher.find() ) {
+        if ( ISO_LANGUAGES.contains(matcher.group(2)) ) {
+          // do not generate message for <label>_<language>.properties
+          return;
+        }
+      }
+      // filename without ".properties"
+      filename = filename.substring(0, filename.length()-11);
+      // change to src-generated directory keeping package name
+      pathname = pathname.replace(getProject().getProperty("msg.dir"),
+                                  getProject().getProperty("msg.javagen.dir"));
+
+      // append characters from filename to pathname starting with an uppercase
+      // letter, ignoring '_' and uppering all characters prefixed with "_" 
+      StringBuilder sb = new StringBuilder(pathname);
+      boolean upperCaseNextChar = true;
+      for(char c : filename.toCharArray()) {
+        if ( c == '_' ) {
+          upperCaseNextChar = true;
+          continue;
+        }
+        if ( upperCaseNextChar ) {
+          sb.append(Character.toUpperCase(c));
+          upperCaseNextChar = false;
+        } else {
+          sb.append(c);
+        }
+      }
+      sb.append("Messages.java");
+
+      setDestJava(new File(sb.toString()));
+    }
+
+    BufferedReader stubReader = null;
+    PrintWriter destWriter = null;
+    try {
+
+      // Decide whether to generate messages based on modification
+      // times and print status messages.
+      if (!source.exists()) {
+        throw new BuildException("file " + source.getName() +
+                " does not exist");
+      }
+      if (dest.exists()) {
+        if (this.overwrite || source.lastModified() > dest.lastModified()) {
+          dest.delete();
+          log("Regenerating " + dest.getName() + " from " + source.getName());
+        } else {
+          log(dest.getName() + " is up to date");
+          return;
+        }
+      } else {
+        File javaGenDir = dest.getParentFile();
+        if (!javaGenDir.exists()) {
+          javaGenDir.mkdirs();
+        }
+        log("Generating " + dest.getName() + " from " + source.getName());
+      }
+
+      stubReader = new BufferedReader(new InputStreamReader(getStubFile(),
+                                                            "UTF-8"));
+      destWriter = new PrintWriter(dest, "UTF-8");
+      String stubLine;
+      Properties properties = new Properties();
+      properties.load(new FileInputStream(source));
+      while (null != (stubLine = stubReader.readLine())) {
+        if (stubLine.contains("${MESSAGES}")) {
+          Integer globalOrdinal = null;
+          String go = properties.getProperty(GLOBAL_ORDINAL);
+          if (go != null) {
+            globalOrdinal = new Integer(go);
+          }
+
+          // Determine the value of the global category/mask if set
+          Integer  globalMask = null;
+          Category globalCategory = null;
+          String gms = properties.getProperty(GLOBAL_CATEGORY_MASK);
+          if (gms != null) {
+            globalMask = Integer.parseInt(gms);
+            globalCategory = Category.USER_DEFINED;
+          } else {
+            String gcs = properties.getProperty(GLOBAL_CATEGORY);
+            if (gcs != null) {
+              globalCategory = Category.valueOf(gcs);
+            }
+          }
+
+          // Determine the value of the global severity
+          Severity globalSeverity = null;
+          String gss = properties.getProperty(GLOBAL_SEVERITY);
+          if (gss != null) {
+            globalSeverity = Severity.parseString(gss);
+          }
+
+          Map<MessagePropertyKey,String> keyMap =
+                  new TreeMap<MessagePropertyKey,String>();
+
+          for (Object propO : properties.keySet()) {
+            String propKey = propO.toString();
+            try {
+              if (!DIRECTIVE_PROPERTIES.contains(propKey)) {
+                MessagePropertyKey key =
+                        MessagePropertyKey.parseString(
+                                propKey,
+                                globalCategory == null,
+                                globalSeverity == null,
+                                globalOrdinal == null);
+                String formatString = properties.getProperty(propKey);
+                keyMap.put(key, formatString);
+              }
+            } catch (IllegalArgumentException iae) {
+              throw new BuildException(
+                      "ERROR: invalid property key " + propKey +
+                      ": " + iae.getMessage() +
+                      KEY_FORM_MSG);
+            }
+          }
+
+          int usesOfGenericDescriptor = 0;
+
+          Category firstCategory = null;
+          Set<Integer> usedOrdinals = new HashSet<Integer>();
+          for (MessagePropertyKey key : keyMap.keySet()) {
+            String formatString = keyMap.get(key);
+            MessageDescriptorDeclaration message =
+                    new MessageDescriptorDeclaration(key, formatString);
+
+            Category c = (globalCategory != null ?
+                    globalCategory : key.getCategory());
+
+            // Check that this category is the same as all the
+            // others in this file.  Maybe this should be an error?
+            if (firstCategory != null) {
+              if (!firstCategory.equals(c)) {
+                log("WARNING: multiple categories defined in " + source);
+              }
+            } else {
+              firstCategory = c;
+            }
+
+            Severity s = (globalSeverity != null ?
+                    globalSeverity : key.getSeverity());
+
+            if (c == null) {
+              throw new BuildException(
+                      "No category could be assigned to message " +
+                              key + ".  The category " +
+                              "must either be encoded in the property key or " +
+                              "or must be set by including the property " +
+                              GLOBAL_CATEGORY + " in the properties file" +
+                              KEY_FORM_MSG);
+            }
+
+            if (c == null) {
+              throw new BuildException(
+                      "No severity could be assigned to message " +
+                              key + ".  The severity " +
+                              "must either be encoded in the property key or " +
+                              "or must be set by including the property " +
+                              GLOBAL_SEVERITY + " in the properties file" +
+                              KEY_FORM_MSG);
+            }
+
+            if (globalOrdinal == null) {
+              Integer ordinal = key.getOrdinal();
+              if (usedOrdinals.contains(ordinal)) {
+                throw new BuildException(
+                        "The ordinal value \'" + ordinal + "\' in key " +
+                                key + " has been previously defined in " +
+                                source + KEY_FORM_MSG);
+              } else {
+                usedOrdinals.add(ordinal);
+              }
+            }
+
+            message.setConstructorArguments(
+                    "BASE",
+                    quote(key.toString()),
+                    globalMask != null ? globalMask.toString() : c.name(),
+                    s.name(),
+                    globalOrdinal != null ?
+                            globalOrdinal.toString() :
+                            key.getOrdinal().toString()
+            );
+            destWriter.println(message.toString());
+            destWriter.println();
+
+            // Keep track of when we use the generic descriptor
+            // so that we can report it later
+            if (message.useGenericMessageTypeClass()) {
+              usesOfGenericDescriptor++;
+            }
+          }
+
+          log("  Message Generated:" + keyMap.size(), Project.MSG_VERBOSE);
+          log("  MessageDescriptor.ArgN:" + usesOfGenericDescriptor,
+                  Project.MSG_VERBOSE);
+
+        } else {
+          stubLine = stubLine.replace("${PACKAGE}", getPackage());
+          stubLine = stubLine.replace("${CLASS_NAME}",
+                  dest.getName().substring(0, dest.getName().length() -
+                          ".java".length()));
+          stubLine = stubLine.replace("${BASE}", getBase());
+
+          String useMessageJarIfWebstart =
+            properties.getProperty(GLOBAL_USE_MESSAGE_JAR_IF_WEBSTART);
+          if ((useMessageJarIfWebstart != null) &&
+              ("true".equalsIgnoreCase(useMessageJarIfWebstart) ||
+              "on".equalsIgnoreCase(useMessageJarIfWebstart) ||
+              "true".equalsIgnoreCase(useMessageJarIfWebstart)))
+          {
+            useMessageJarIfWebstart = "true";
+          }
+          else
+          {
+            useMessageJarIfWebstart = "false";
+          }
+          stubLine = stubLine.replace("${USE_MESSAGE_JAR_IF_WEBSTART}",
+              useMessageJarIfWebstart);
+          destWriter.println(stubLine);
+        }
+      }
+
+      registerMessageDescriptor(getMessageDescriptorFullClassName());
+
+      stubReader.close();
+      destWriter.close();
+
+    } catch (Exception e) {
+      // Don't leave a malformed file laying around. Delete
+      // it so it will be forced to be regenerated.
+      if (dest.exists()) {
+        dest.deleteOnExit();
+      }
+      e.printStackTrace();
+      throw new BuildException("Error processing " + source +
+              ":  " + e.getMessage());
+    } finally {
+      if (stubReader != null) {
+        try {
+          stubReader.close();
+        } catch (Exception e){
+          // ignore
+        }
+      }
+      if (destWriter != null) {
+        try {
+          destWriter.close();
+        } catch (Exception e){
+          // ignore
+        }
+      }
+    }
+  }
+
+  private String getMessageDescriptorFullClassName() {
+    return getPackage() + "." + getMessageDescriptorClassName();
+  }
+
+  private String getMessageDescriptorClassName() {
+    return dest.getName().substring(
+            0, dest.getName().length() - ".java".length());
+  }
+
+  private String getBase() {
+    String srcPath = unixifyPath(source.getAbsolutePath());
+    String base = srcPath.substring(srcPath.lastIndexOf("/") + 1,
+                                    srcPath.length() - ".properties".length());
+    return base;
+  }
+
+  private String getPackage() {
+    String destPath = unixifyPath(dest.getAbsolutePath());
+    String msgJavaGenDir = unixifyPath(
+                                   getProject().getProperty("msg.javagen.dir"));
+    String c = destPath.substring(msgJavaGenDir.length()+1);
+    c = c.replace('/', '.');
+    c = c.substring(0, c.lastIndexOf(".")); // strip .java
+    c = c.substring(0, c.lastIndexOf(".")); // strip class name
+    return c;
+  }
+
+  static private String indent(int indent) {
+    char[] blankArray = new char[2 * indent];
+    Arrays.fill(blankArray, ' ');
+    return new String(blankArray);
+  }
+
+  static private String quote(String s) {
+    return new StringBuilder()
+            .append("\"")
+            .append(s)
+            .append("\"")
+            .toString();
+  }
+
+  static private String getShortClassName(Class c) {
+    String name;
+    String fqName = c.getName();
+    int i = fqName.lastIndexOf('.');
+    if (i > 0) {
+      name = fqName.substring(i + 1);
+    } else {
+      name = fqName;
+    }
+    return name;
+  }
+
+  /**
+   * Writes a record in the messages registry for the specifed
+   * class name.
+   * @param descClassName name of the message descriptor class
+   * @return true if the class was acutally added to the registry;
+   *         false indicates that the class was already present.
+   * @throws IOException if there is a problem with the file I/O
+   */
+  private boolean registerMessageDescriptor(String descClassName)
+          throws IOException
+  {
+    boolean classAdded = false;
+    File registry = getRegistryFile();
+    if (!isDescriptorRegistered(descClassName)) {
+      FileOutputStream file = new FileOutputStream(registry,true);
+      DataOutputStream out   = new DataOutputStream(file);
+      out.writeBytes(descClassName);
+      out.writeBytes("\n");
+      out.flush();
+      out.close();
+    }
+    return classAdded;
+  }
+
+  private boolean isDescriptorRegistered(String descClassName)
+          throws IOException
+  {
+    boolean isRegistered = false;
+    BufferedReader reader = new BufferedReader(
+            new FileReader(getRegistryFile()));
+    String line;
+    while(null != (line = reader.readLine())) {
+      if (line.trim().equals(descClassName.trim())) {
+        isRegistered = true;
+        break;
+      }
+    }
+    return isRegistered;
+  }
+
+  private File getRegistryFile() throws IOException {
+    File registry = new File(getProjectBase(), REGISTRY_FILE_NAME);
+    if (!registry.exists()) {
+      File parent = registry.getParentFile();
+      if (!parent.exists()) {
+        parent.mkdirs();
+      }
+      registry.createNewFile();
+    }
+    return registry;
+  }
+
+  private File getProjectBase() {
+    File projectBase;
+
+    // Get the path to build.xml and return the parent
+    // directory else just return the working directory.
+    Location l = getLocation();
+    String fileName = l.getFileName();
+    if (fileName != null) {
+      File f = new File(fileName);
+      projectBase = f.getParentFile();
+    } else {
+      projectBase = new File(System.getProperty("user.dir"));
+    }
+
+    return projectBase;
+  }
+
+  private String unixifyPath(String path) {
+    return path.replace("\\", "/");
+  }
+
+  /*
+   * Returns the stub file ("resource/Messages.java.stub") from the appropriate
+   * location: ant or jar file.
+   */
+  private InputStream getStubFile() {
+    InputStream result = null;
+
+    File stub = new File(getProjectBase(), MESSAGES_FILE_STUB);
+    if ( stub.exists() ) {
+      // this is the OpenDS's ant project calling
+      // Stub is located at OPENDS_ROOT/resource/Messages.java.stub
+      try {
+        result = new FileInputStream(stub);
+      } catch (FileNotFoundException e) {
+        // should neven happen
+        throw new BuildException("Unable to load template " +
+              MESSAGES_FILE_STUB + ":  " + e.getMessage());
+      }
+    } else {
+      // this is the example plugin's ant project calling
+      // Stub is located at build-tools.jar:resource/Messages.java.stub
+      result = getClass().getResourceAsStream(MESSAGES_FILE_STUB);
+    }
+
+    return result;
+  }
+
+  /**
+   * For testing.
+   * @param args from command line
+   */
+  public static void main(String[] args) {
+    File source = new File("src/messages/messages/tools.properties");
+    File dest = new File("/tmp/org/opends/XXX.java");
+    GenerateMessageFile gmf = new GenerateMessageFile();
+    gmf.setOverwrite(true);
+    gmf.setDestJava(dest);
+    gmf.setSourceProps(source);
+    gmf.execute();
+  }
+
+}
diff --git a/sdk/build-tools/org/opends/build/tools/GenerateRpm.java b/sdk/build-tools/org/opends/build/tools/GenerateRpm.java
new file mode 100644
index 0000000..d839f85
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/GenerateRpm.java
@@ -0,0 +1,251 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2007-2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.BuildException;
+
+import static org.opends.build.tools.Utilities.*;
+
+import java.io.File;
+import java.io.PrintWriter;
+
+/**
+ * Generates an RPM spec file.
+ */
+public class GenerateRpm extends Task {
+
+  private File topDir;
+  private String topDirAbsolutePath;
+  private String sourceDirName;
+  private File destFile;
+  private String prefix;
+  private String version;
+  private String release;
+  private boolean overwrite;
+  private StringBuilder sb;
+
+  private final String filePrefix="%{_prefix}";
+  private final String dirPrefix="%dir %{_prefix}";
+
+
+  /**
+   * Sets the top directory for the rpm build.
+   * @param topDir File representing top directory for rpm build directory
+   */
+  public void setTopDir(File topDir) {
+    this.topDir = topDir;
+    topDirAbsolutePath = topDir.getAbsolutePath();
+  }
+
+  /**
+   * Sets the prefix for the RPM.
+   * @param prefix Used for package relocation
+   */
+  public void setPrefix(String prefix) {
+    this.prefix = prefix;
+  }
+
+  /**
+   * Sets the name of the source directory.
+   * @param sourceDirName name of the source directory.
+   */
+  public void setSourceDirName(String sourceDirName) {
+    this.sourceDirName = sourceDirName;
+  }
+
+
+  /**
+   * Sets the RPM spec file that will be generated.
+   * @param dest The spec file
+   *
+   */
+  public void setSpecFileName(File dest) {
+    this.destFile = dest;
+  }
+
+  /**
+   * Sets the version number.
+   * @param version The version number
+   *
+   */
+  public void setVersion(String version) {
+    this.version = version;
+  }
+
+  /**
+   * Sets the release number.
+   * @param release The release number
+   *
+   */
+  public void setRelease(String release) {
+    this.release = release;
+  }
+
+  /**
+   * Indicates when true that an existing destination
+   * file will be overwritten.
+   * @param o boolean where true means overwrite
+   */
+  public void setOverwrite(boolean o) {
+    this.overwrite = o;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void execute() throws BuildException {
+
+    try {
+      if (!topDir.exists()) {
+        throw new BuildException("directory " + topDir.getName()
+            + " does not exist");
+      }
+      if (!topDir.isDirectory()) {
+        throw new BuildException(topDir.getName() + " is not a directory");
+      }
+
+      if (destFile.exists()) {
+        if (this.overwrite) {
+          destFile.delete();
+          log("Regenerating " + destFile.getName() + " from "
+              + topDir.getName());
+        } else {
+          log(destFile.getName() + " has not been regenerated");
+        }
+      }
+
+      sb = new StringBuilder();
+      File rootDir = new File(sourceDirName);
+      String opendsDir = rootDir.getName();
+
+      // Generate the package information
+      sb.append("Summary            : OpenDS Directory Server"           + EOL);
+      sb.append("Name               : opends"                            + EOL);
+      sb.append("Version            : " + version                        + EOL);
+      sb.append("Release            : " + release                        + EOL);
+      sb.append("License            : CDDL"                              + EOL);
+      sb.append("Group              : Applications/Network"              + EOL);
+      sb.append("URL                : https://opends.org"                + EOL);
+      sb.append(                                                           EOL);
+      sb.append("BuildArchitectures : noarch"                            + EOL);
+      sb.append("BuildRoot          : " + topDirAbsolutePath + "/SOURCES"+ EOL);
+      sb.append("Prefix             : " + prefix                         + EOL);
+      sb.append(                                                           EOL);
+      sb.append("%define _prefix " + prefix                              + EOL);
+      sb.append(                                                           EOL);
+      sb.append("%Description"                                           + EOL);
+      sb.append("OpenDS Directory Server"                                + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# ========================="                            + EOL);
+      sb.append("# pre/post installation"                                + EOL);
+      sb.append("# ========================="                            + EOL);
+      sb.append("# The order is:"                                        + EOL);
+      sb.append("#     1. %pre new"                                      + EOL);
+      sb.append("#     2. install new"                                   + EOL);
+      sb.append("#     3. %post new"                                     + EOL);
+      sb.append("#     4. %preun old"                                    + EOL);
+      sb.append("#     5. delete old"                                    + EOL);
+      sb.append("#     6. %postun old"                                   + EOL);
+      sb.append("# Note: \"$1 equals \"1\" it means \"fresh install\""   + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# PRE INSTALL"                                          + EOL);
+      sb.append("%pre"                                                   + EOL);
+      sb.append("if [ \"$1\" != \"1\" ]; then"                           + EOL);
+      sb.append("echo \"  This version of the OpenDS RPM does not work\""+ EOL);
+      sb.append("echo \"  with the standard RPM upgrade mechanism\""     + EOL);
+      sb.append("echo \"  (rpm -U or rpm -F).\""                         + EOL);
+      sb.append("echo \"  To perform an upgrade, use the OpenDS upgrade\""+ EOL);
+      sb.append("echo \"  tool included in the package delivery.\""      + EOL);
+      sb.append("echo \"  For more information about the upgrade process\""+ EOL);
+      sb.append("echo \"  with RPM see https://www.opends.org/wiki//page/OpendsRPM.\""+ EOL);
+      sb.append("exit 1"                                                 + EOL);
+      sb.append("fi"                                                     + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# POST INSTALL"                                         + EOL);
+      sb.append("%post"                                                  + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# PRE UNINSTALL"                                        + EOL);
+      sb.append("%preun"                                                 + EOL);
+      sb.append("${RPM_INSTALL_PREFIX}/OpenDS-1.0.0/bin/stop-ds"         + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# POST UNINSTALL"                                       + EOL);
+      sb.append("%postun"                                                + EOL);
+      sb.append("rm -rf ${RPM_INSTALL_PREFIX}/" + opendsDir              + EOL);
+      sb.append("rmdir --ignore-fail-on-non-empty ${RPM_INSTALL_PREFIX}" + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# ========================="                            + EOL);
+      sb.append("# Prepare, Build, Install"                              + EOL);
+      sb.append("# ========================="                            + EOL);
+      sb.append("%prep"                                                  + EOL);
+      sb.append("cd "+ topDirAbsolutePath +"/SOURCES" + prefix           +
+          " ; cp -r " + sourceDirName + " ."                             + EOL);
+      sb.append("%build"                                                 + EOL);
+      sb.append("%install"                                               + EOL);
+      sb.append(                                                           EOL);
+      sb.append("# ========================="                            + EOL);
+      sb.append("# FILES LAYOUT"                                         + EOL);
+      sb.append("# ========================="                            + EOL);
+      sb.append("%files"                                                 + EOL);
+      sb.append(dirPrefix                                                + EOL);
+      generatedLevel("", rootDir);
+
+      // flush the spec file.
+      PrintWriter destWriter = new PrintWriter(destFile);
+      destWriter.print(sb.toString());
+      destWriter.close();
+    } catch (Exception e) {
+      // Don't leave a malformed file laying around. Delete
+      // it so it will be forced to be regenerated.
+      if (destFile.exists()) {
+        destFile.deleteOnExit();
+      }
+      e.printStackTrace();
+      throw new BuildException("Error processing " + topDir + ":  "
+          + e.getMessage());
+    }
+  }
+
+  private void generatedLevel (String parent, File source )
+  {
+    if (source.isDirectory())
+    {
+      sb.append(dirPrefix + parent + "/" +source.getName());
+      sb.append(EOL);
+      for (File child : source.listFiles())
+      {
+        generatedLevel( parent + "/" +source.getName(), child);
+      }
+    }
+    else
+    {
+      sb.append(filePrefix + parent + "/" +source.getName());
+      sb.append(EOL);
+    }
+  }
+}
diff --git a/sdk/build-tools/org/opends/build/tools/GetSubversionRevision.java b/sdk/build-tools/org/opends/build/tools/GetSubversionRevision.java
new file mode 100644
index 0000000..5469b10
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/GetSubversionRevision.java
@@ -0,0 +1,143 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+
+
+import java.io.File;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+
+
+
+/**
+ * This class provides an implementation of an Ant task that may be used to
+ * determine the current Subversion revision number of the current working
+ * copy.  The value of the revision number will be stored in an Ant property.
+ */
+public class GetSubversionRevision
+       extends Task
+{
+  // The name of the property in which the revision number should be set.
+  private String propertyName = null;
+
+  // The path to the root of the Subversion workspace for which to retrieve the
+  // revision number.
+  private String workspace = null;
+
+  // The svn client manager. Required by svnkit 1.2.x
+  private static SVNClientManager ourClientManager =
+          SVNClientManager.newInstance();
+
+  /**
+   * Specifies the name of the Ant property into which the Subversion revision
+   * number will be stored.
+   *
+   * @param  propertyName  The name of the Ant property into which the
+   *                       Subversion revision number will be stored.
+   */
+  public void setProperty(String propertyName)
+  {
+    this.propertyName = propertyName;
+  }
+
+
+
+  /**
+   * Specifies the path to the root of the Subversion workspace for which to
+   * retrieve the revision number.
+   *
+   * @param  workspace  The path to the root of the Subversion workspace for
+   *                    which to retrieve the revision number.
+   */
+  public void setWorkspace(String workspace)
+  {
+    this.workspace = workspace;
+  }
+
+
+
+  /**
+   * Performs the appropriate processing needed for this task.  In this case,
+   * it uses SVNKit to identify the current revision number for the local
+   * workspace and store it in a specified property.
+   */
+  @Override()
+  public void execute()
+  {
+    if ((propertyName == null) || (propertyName.length() == 0))
+    {
+      throw new BuildException("ERROR:  No property was specified for " +
+                               "storing the revision number value.");
+    }
+
+    File workspacePath;
+    if ((workspace == null) || (workspace.length() == 0))
+    {
+      workspacePath = getProject().getBaseDir();
+    }
+    else
+    {
+      workspacePath = new File(workspace);
+    }
+
+
+    try
+    {
+      SVNInfo svnInfo = ourClientManager.getWCClient().doInfo(workspacePath, SVNRevision.WORKING);
+      SVNRevision revision = svnInfo.getCommittedRevision();
+      
+  
+      if (revision == null)
+      {
+        System.err.println("WARNING:  Could not determine Subversion " +
+                           "revision number for current workspace.");
+        getProject().setNewProperty(propertyName, "-1");
+      }
+      else
+      {
+        getProject().setNewProperty(propertyName,
+                                    String.valueOf(revision.getNumber()));
+      }
+  
+    }
+    catch (SVNException svnException)
+    {
+      System.err.println("WARNING:  Could not determine Subversion " +
+                         "revision number for current workspace:  " +
+                         svnException);
+      getProject().setNewProperty(propertyName, "-1");
+    }
+  }
+}
+
diff --git a/sdk/build-tools/org/opends/build/tools/GetSubversionUrlRepo.java b/sdk/build-tools/org/opends/build/tools/GetSubversionUrlRepo.java
new file mode 100644
index 0000000..ed4aad0
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/GetSubversionUrlRepo.java
@@ -0,0 +1,145 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+
+
+import java.io.File;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+
+import org.tmatesoft.svn.core.SVNException;
+import org.tmatesoft.svn.core.wc.SVNClientManager;
+import org.tmatesoft.svn.core.wc.SVNInfo;
+import org.tmatesoft.svn.core.wc.SVNRevision;
+import org.tmatesoft.svn.core.SVNURL;
+
+
+
+/**
+ * This class provides an implementation of an Ant task that may be used to
+ * determine the current Subversion revision number of the current working
+ * copy.  The value of the revision number will be stored in an Ant property.
+ */
+public class GetSubversionUrlRepo
+       extends Task
+{
+  // The name of the property in which the revision number should be set.
+  private String propertyName = null;
+
+  // The path to the root of the Subversion workspace for which to retrieve the
+  // revision number.
+  private String workspace = null;
+
+  // The svn client manager. Required by svnkit 1.2.x
+  private static SVNClientManager ourClientManager =
+          SVNClientManager.newInstance();
+
+  /**
+   * Specifies the name of the Ant property into which the Subversion revision
+   * number will be stored.
+   *
+   * @param  propertyName  The name of the Ant property into which the
+   *                       Subversion revision number will be stored.
+   */
+  public void setProperty(String propertyName)
+  {
+    this.propertyName = propertyName;
+  }
+
+
+
+  /**
+   * Specifies the path to the root of the Subversion workspace for which to
+   * retrieve the revision number.
+   *
+   * @param  workspace  The path to the root of the Subversion workspace for
+   *                    which to retrieve the revision number.
+   */
+  public void setWorkspace(String workspace)
+  {
+    this.workspace = workspace;
+  }
+
+
+
+  /**
+   * Performs the appropriate processing needed for this task.  In this case,
+   * it uses SVNKit to identify the current revision number for the local
+   * workspace and store it in a specified property.
+   */
+  @Override()
+  public void execute()
+  {
+    if ((propertyName == null) || (propertyName.length() == 0))
+    {
+      throw new BuildException("ERROR:  No property was specified for " +
+                               "storing the revision number value.");
+    }
+
+    File workspacePath;
+    if ((workspace == null) || (workspace.length() == 0))
+    {
+      workspacePath = getProject().getBaseDir();
+    }
+    else
+    {
+      workspacePath = new File(workspace);
+    }
+
+
+    try
+    {
+      SVNInfo svnInfo = ourClientManager.getWCClient().doInfo(workspacePath, SVNRevision.WORKING);
+      SVNURL url_repo = svnInfo.getURL();
+      
+  
+      if (url_repo == null)
+      {
+        System.err.println("WARNING:  Could not determine Subversion URL Repository " +
+                           "for current workspace.");
+        getProject().setNewProperty(propertyName, "-1");
+      }
+      else
+      {
+        getProject().setNewProperty(propertyName,
+                                    String.valueOf(url_repo));
+
+      }
+  
+    }
+    catch (SVNException svnException)
+    {
+      System.err.println("WARNING:  Could not determine Subversion " +
+                         "URL repository for current workspace:  " +
+                         svnException);
+      getProject().setNewProperty(propertyName, "-1");
+    }
+  }
+}
+
diff --git a/sdk/build-tools/org/opends/build/tools/MessagePropertyKey.java b/sdk/build-tools/org/opends/build/tools/MessagePropertyKey.java
new file mode 100644
index 0000000..93ee9e5
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/MessagePropertyKey.java
@@ -0,0 +1,267 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.build.tools;
+
+import org.opends.messages.Category;
+import org.opends.messages.Severity;
+
+import java.util.EnumSet;
+
+/**
+ * OpenDS messages defined in properties files must be defined
+ * with the ordinal and in most cases category and severity encoded
+ * in the message key.  This class helps with generating and
+ * parsing of these keys.
+ *
+ * Keys must be of the form
+ *
+ *   CATEGORY_SEVERITY_DESCRIPTION_ORDINAL
+ *
+ * where:
+ * <ul>
+ * <li>
+ * CATEGORY is the string representation of one of the
+ * <code>Category</code> enums.
+ * </li>
+ * <li>
+ * SEVERITY is the long or abbreviated form of one of
+ * the <code>Severity</code> enums.
+ * </li>
+ * <li>
+ * DESCRIPTION is an uppercase string containing characters
+ * and the underscore character for describing the purpose
+ * of the message.
+ * </li>
+ * <li>
+ * ORDINAL is an integer that makes the message unique witin
+ * the property file.
+ * </li>
+ * </ul>
+ *
+ */
+// TODO: move this class to GenerateMessageFile when DirectoryServer
+// no longer needs to support dumpMessages()
+public class MessagePropertyKey
+        implements Comparable<MessagePropertyKey> {
+
+  private Category category;
+
+  private Severity severity;
+
+  private String description;
+
+  private Integer ordinal;
+
+  /**
+   * Creates a message property key from a string value.
+   * @param keyString from properties file
+   * @param includesCategory when true expects ordinals to be encoded
+   *        in the keystring; when false the mandate is relaxed
+   * @param includesSeverity when true expects ordinals to be encoded
+   *        in the keystring; when false the mandate is relaxed
+   * @param includesOrdinal when true expects ordinals to be encoded
+   *        in the keystring; when false the mandate is relaxed
+   * @return MessagePropertyKey created from string
+   */
+  static public MessagePropertyKey parseString(
+          String keyString,
+          boolean includesCategory,
+          boolean includesSeverity,
+          boolean includesOrdinal) {
+
+    Category category = null;
+    Severity severity = null;
+    String description;
+    Integer ordinal = null;
+
+    String k = keyString;
+    for (Category c : EnumSet.allOf(Category.class)) {
+      String cName = c.name();
+      if (k.startsWith(cName)) {
+        category = c;
+        if ('_' != k.charAt(cName.length())) {
+          throw new IllegalArgumentException(
+                  "Error processing " + keyString + ".  Category must be " +
+                          "separated from the rest of the " +
+                          "key with an '_' character");
+        }
+        k = k.substring(cName.length() + 1);
+        break;
+      }
+    }
+    if (category == null && includesCategory) {
+      throw new IllegalArgumentException("Category not included in key " +
+              keyString);
+    }
+
+    for (Severity s : EnumSet.allOf(Severity.class)) {
+      String sName = s.propertyKeyFormName();
+      if (k.startsWith(sName)) {
+        severity = s;
+        if ('_' != k.charAt(sName.length())) {
+          throw new IllegalArgumentException(
+                  "Error processing " + keyString + ".  Severity must be " +
+                          "separated from the rest of the " +
+                          "key with an '_' character");
+        }
+        k = k.substring(sName.length() + 1);
+        break;
+      }
+    }
+    if (severity == null && includesSeverity) {
+      throw new IllegalArgumentException("Severity not included in key " +
+              keyString);
+    }
+
+    if (includesOrdinal) {
+      int li = k.lastIndexOf("_");
+      if (li != -1) {
+        description = k.substring(0, li).toUpperCase();
+      } else {
+        throw new IllegalArgumentException(
+                "Incorrectly formatted key " + keyString);
+      }
+
+      try {
+        String ordString = k.substring(li + 1);
+        ordinal = Integer.parseInt(ordString);
+      } catch (Exception nfe) {
+        throw new IllegalArgumentException("Error parsing ordinal for key " +
+                keyString);
+      }
+    } else {
+      description = k;
+    }
+    return new MessagePropertyKey(category, severity, description, ordinal);
+  }
+
+  /**
+   * Creates a parameterized instance.
+   * @param category of this key
+   * @param severity of this key
+   * @param description of this key
+   * @param ordinal of this key
+   */
+  public MessagePropertyKey(Category category, Severity severity,
+                           String description, Integer ordinal) {
+    this.category = category;
+    this.severity = severity;
+    this.description = description;
+    this.ordinal = ordinal;
+  }
+
+  /**
+   * Gets the category of this key.
+   * @return Category of this key
+   */
+  public Category getCategory() {
+    return this.category;
+  }
+
+  /**
+   * Gets the severity of this key.
+   * @return Severity of this key
+   */
+  public Severity getSeverity() {
+    return this.severity;
+  }
+
+  /**
+   * Gets the description of this key.
+   * @return description of this key
+   */
+  public String getDescription() {
+    return this.description;
+  }
+
+  /**
+   * Gets the ordinal of this key.
+   * @return ordinal of this key
+   */
+  public Integer getOrdinal() {
+    return this.ordinal;
+  }
+
+  /**
+   * Gets the name of the MessageDescriptor as it should appear
+   * in the messages file.
+   * @return name of message descriptor
+   */
+  public String getMessageDescriptorName() {
+    return new StringBuilder()
+            .append(this.severity.messageDesciptorName())
+            .append("_")
+            .append(this.description).toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString() {
+    return getPropertyKeyName(true, true, true);
+  }
+
+  /**
+   * Gets the name of the key as it would appear in a properties file.
+   * @param includeCategory in the name
+   * @param includeSeverity in the name
+   * @param includeOrdinal in the name
+   * @return string representing the property key
+   */
+  public String getPropertyKeyName(boolean includeCategory,
+                                   boolean includeSeverity,
+                                   boolean includeOrdinal) {
+    StringBuilder sb = new StringBuilder();
+    if (category != null && includeCategory) {
+      sb.append(category.name());
+      sb.append("_");
+    }
+    if (severity != null && includeSeverity) {
+      sb.append(severity.propertyKeyFormName());
+      sb.append("_");
+    }
+    sb.append(description);
+    if (ordinal != null && includeOrdinal) {
+      sb.append("_");
+      sb.append(ordinal);
+    }
+    return sb.toString();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(MessagePropertyKey k) {
+    if (ordinal == k.ordinal) {
+      return description.compareTo(k.description);
+    } else {
+      return ordinal.compareTo(k.ordinal);
+    }
+  }
+
+}
diff --git a/sdk/build-tools/org/opends/build/tools/PrepTestNG.java b/sdk/build-tools/org/opends/build/tools/PrepTestNG.java
new file mode 100644
index 0000000..51cf38e
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/PrepTestNG.java
@@ -0,0 +1,337 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.build.tools;
+
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.util.Arrays;
+
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.BuildException;
+
+public class PrepTestNG extends Task
+{
+
+  /** Template for inserting children elements of default test tag */
+  static private final String DEFAULT_TAGS_TEMPLATE =
+    "<!-- DO NOT REMOVE! - GENERATED DEFAULT TAGS (see PrepTestNG class) -->";
+
+  /** Template for inserting global children elements of run tags */
+  static private final String GLOBAL_RUN_TAGS_TEMPLATE =
+    "<!-- DO NOT REMOVE! - GENERATED GLOBAL RUN TAGS (see PrepTestNG class) -->";
+
+  /** Indentation used in testng.xml */
+  static private final int INDENT = 4;
+
+  private String file;
+  private String toFile;
+  private String groupList;
+  private String packageList;
+  private String classList;
+  private String methodList;
+
+  public void setFile(String file)
+  {
+    this.file = file;
+  }
+
+  public void setToFile(String toFile)
+  {
+    this.toFile = toFile;
+  }
+
+  public void setGroupList(String groupList)
+  {
+    this.groupList = groupList;
+  }
+
+  public void setPackageList(String packageList)
+  {
+    this.packageList = packageList;
+  }
+
+  public void setClassList(String classList)
+  {
+    this.classList = classList;
+  }
+
+  public void setMethodList(String methodList)
+  {
+    this.methodList = methodList;
+  }
+
+  public void execute() throws BuildException
+  {
+    if(file == null)
+    {
+      throw new BuildException("Attribute file must be set to the orginal " +
+          "TestNG XML file");
+    }
+
+    if(toFile == null)
+    {
+      throw new BuildException("Attribute toFile must be set to the modified " +
+          "TestNG XML file");
+    }
+
+    BufferedReader reader;
+    FileOutputStream outFile;
+    PrintStream writer;
+    String line;
+    String[] groups;
+    String[] packages;
+    String[] classes;
+    String[] methods;
+    String[] groupLine;
+    String[] methodLine;
+    String methodClass;
+    String methodName;
+    int methodNameStartIdx;
+    int groupCount = 0;
+    int packageCount = 0;
+    int classCount = 0;
+    int methodCount = 0;
+
+    try
+    {
+      reader = new BufferedReader(new FileReader(file));
+      outFile = new FileOutputStream(toFile);
+
+      writer = new PrintStream(outFile);
+
+      line = reader.readLine();
+
+      if(groupList != null && !groupList.trim().equals("") &&
+          !groupList.startsWith("${"))
+      {
+        groups = groupList.split(",");
+      }
+      else
+      {
+        groups = new String[0];
+      }
+      if(packageList != null && !packageList.trim().equals("") &&
+          !packageList.startsWith("${"))
+      {
+        packages = packageList.split(",");
+      }
+      else
+      {
+        packages = new String[0];
+      }
+
+      if(classList != null && !classList.trim().equals("") &&
+          !classList.startsWith("${"))
+      {
+        classes = classList.split(",");
+      }
+      else
+      {
+        classes = new String[0];
+      }
+
+      if(methodList != null && !methodList.trim().equals("") &&
+          !methodList.startsWith("${"))
+      {
+        methods = methodList.split(";");
+      }
+      else
+      {
+        methods = new String[0];
+      }
+
+      while(line != null)
+      {
+        if(line.indexOf(DEFAULT_TAGS_TEMPLATE) >= 0)
+        {
+          int level = 2;
+          if(groups.length > 0)
+          {
+            boolean windowsClause = false;
+            println(writer, level, "<groups>");
+            println(writer, ++level,   "<run>");
+            level++;
+            for(String group : groups)
+            {
+              groupLine = group.split("=");
+              if(groupLine.length == 2)
+              {
+                String inc_exc = groupLine[0].trim();
+                if (inc_exc == null ||
+                        !("include".equals(inc_exc.toLowerCase()) ||
+                                "exclude".equals(inc_exc.toLowerCase()))) {
+                  System.out.println("Error:  illegal group clause " + group);
+                } else {
+                  String gr = groupLine[1].trim();
+                  println(writer, level, "<" +inc_exc +" "+
+                          "name=\""+gr+ "\" />");
+                  windowsClause |= "windows".equals(gr);
+                  groupCount++;
+                }
+              }
+            }
+
+            // Exclude windows specific tests if the user has not provided
+            // an explicit windows clause and we're not on windows.
+            if (!windowsClause && !isWindows()) {
+              println(writer, level, "<exclude name=\"windows\"/>");
+              groupCount++;
+            }
+
+            println(writer, --level,   "</run>");
+            println(writer, --level, "</groups>");
+          } else {
+
+            // No explicit groups have been specified so see if we need
+            // to exclude the windows tests.
+            if (!isWindows()) {
+              println(writer, level,   "<groups>");
+              println(writer, ++level,   "<run>");
+              println(writer, ++level,     "<exclude name=\"windows\"/>");
+              println(writer, --level,   "</run>");
+              println(writer, --level, "</groups>");
+              groupCount++;
+            }
+          }
+
+          if(packages.length > 0)
+          {
+            println(writer, level, "<packages>");
+            level++;
+            for(String pkg : packages)
+            {
+              println(writer, level, "<package name=\"" + pkg.trim() + "\" />");
+              packageCount++;
+            }
+            println(writer, --level, "</packages>");
+          }
+
+          if(classes.length > 0 || methods.length > 0)
+          {
+            println(writer, level, "<classes>");
+
+            if(classes.length > 0)
+            {
+              level++;
+              for(String cls : classes)
+              {
+                println(writer, level, "<class name=\"" + cls.trim() + "\" />");
+                classCount++;
+              }
+            }
+
+            if(methods.length > 0)
+            {
+              level++;
+              for(String mhd : methods)
+              {
+                methodLine = mhd.split(",");
+                if(methodLine.length > 0)
+                {
+                  // Allow class.method or class#method
+                  methodNameStartIdx = methodLine[0].lastIndexOf("#");
+                  if (methodNameStartIdx == -1)
+                  {
+                    methodNameStartIdx = methodLine[0].lastIndexOf(".");
+                  }
+                  methodClass = methodLine[0].substring(0,
+                                  methodNameStartIdx);
+                  methodName = methodLine[0].substring(methodNameStartIdx + 1,
+                                methodLine[0].length());
+                  println(writer, level, "<class name=\"" +
+                      methodClass.trim() + "\" >");
+                  println(writer, ++level, "<methods>");
+                  println(writer, ++level, "<include name=\"" +
+                      methodName.trim() + "\" />");
+                  methodCount++;
+                  classCount++;
+                  for(int i = 1; i < methodLine.length; i ++)
+                  {
+                    println(writer, level, "<include name=\"" +
+                      methodLine[i].trim() + "\" />");
+                    methodCount++;
+                  }
+                  println(writer, --level, "</methods>");
+                  println(writer, --level, "</class>");
+                }
+              }
+            }
+
+            println(writer, --level, "</classes>");
+          }
+        }
+        else if (line.indexOf(GLOBAL_RUN_TAGS_TEMPLATE) != -1)
+        {
+          if (!isWindows()) {
+            int index = line.indexOf(GLOBAL_RUN_TAGS_TEMPLATE);
+            println(writer, levelForIndex(index),
+                    "<exclude name=\"windows\"/>");
+          }
+        }
+        else
+        {
+          println(writer, 0, line);
+        }
+
+        line = reader.readLine();
+      }
+
+      System.out.println("Adding " + groupCount + " group tags, " +
+          packageCount + " package tags, " + classCount + " class tags, " +
+          methodCount + " method tags to " + toFile);
+    }
+    catch(Exception e)
+    {
+      throw new BuildException("File Error: " + e.toString());
+    }
+  }
+
+  static private boolean isWindows() {
+    String os = System.getProperty("os.name");
+    return (os != null && os.toLowerCase().indexOf("windows") != -1);
+  }
+
+  static private String indent(int indent) {
+    char[] blankArray = new char[indent];
+    Arrays.fill(blankArray, ' ');
+    return new String(blankArray);
+  }
+
+  static private void println(PrintStream writer, int level, String txt) {
+    writer.print(indent(INDENT * level));
+    writer.print(txt);
+    writer.print(System.getProperty("line.separator"));
+  }
+
+  static private int levelForIndex(int index) {
+    return index / INDENT;
+  }
+
+}
diff --git a/sdk/build-tools/org/opends/build/tools/Utilities.java b/sdk/build-tools/org/opends/build/tools/Utilities.java
new file mode 100644
index 0000000..2ad4f22
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/Utilities.java
@@ -0,0 +1,159 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+import org.opends.messages.Message;
+
+import java.util.StringTokenizer;
+
+/**
+ */
+public class Utilities {
+
+  /**
+   * The end-of-line character for this platform.
+   */
+  public static final String EOL = System.getProperty("line.separator");
+
+  /**
+   * Inserts line breaks into the provided buffer to wrap text at no more than
+   * the specified column width.  Wrapping will only be done at space boundaries
+   * and if there are no spaces within the specified width, then wrapping will
+   * be performed at the first space after the specified column.
+   *
+   * @param  text   The text to be wrapped.
+   * @param  width  The maximum number of characters to allow on a line if there
+   *                is a suitable breaking point.
+   *
+   * @return  The wrapped text.
+   */
+  public static String wrapText(String text, int width)
+  {
+    StringBuilder   buffer        = new StringBuilder();
+    StringTokenizer lineTokenizer = new StringTokenizer(text, "\r\n", true);
+    while (lineTokenizer.hasMoreTokens())
+    {
+      String line = lineTokenizer.nextToken();
+      if (line.equals("\r") || line.equals("\n"))
+      {
+        // It's an end-of-line character, so append it as-is.
+        buffer.append(line);
+      }
+      else if (line.length() < width)
+      {
+        // The line fits in the specified width, so append it as-is.
+        buffer.append(line);
+      }
+      else
+      {
+        // The line doesn't fit in the specified width, so it needs to be
+        // wrapped.  Do so at space boundaries.
+        StringBuilder   lineBuffer    = new StringBuilder();
+        StringBuilder   delimBuffer   = new StringBuilder();
+        StringTokenizer wordTokenizer = new StringTokenizer(line, " ", true);
+        while (wordTokenizer.hasMoreTokens())
+        {
+          String word = wordTokenizer.nextToken();
+          if (word.equals(" "))
+          {
+            // It's a space, so add it to the delim buffer only if the line
+            // buffer is not empty.
+            if (lineBuffer.length() > 0)
+            {
+              delimBuffer.append(word);
+            }
+          }
+          else if (word.length() > width)
+          {
+            // This is a long word that can't be wrapped, so we'll just have to
+            // make do.
+            if (lineBuffer.length() > 0)
+            {
+              buffer.append(lineBuffer);
+              buffer.append(EOL);
+              lineBuffer = new StringBuilder();
+            }
+            buffer.append(word);
+
+            if (wordTokenizer.hasMoreTokens())
+            {
+              // The next token must be a space, so remove it.  If there are
+              // still more tokens after that, then append an EOL.
+              wordTokenizer.nextToken();
+              if (wordTokenizer.hasMoreTokens())
+              {
+                buffer.append(EOL);
+              }
+            }
+
+            if (delimBuffer.length() > 0)
+            {
+              delimBuffer = new StringBuilder();
+            }
+          }
+          else
+          {
+            // It's not a space, so see if we can fit it on the curent line.
+            int newLineLength = lineBuffer.length() + delimBuffer.length() +
+                                word.length();
+            if (newLineLength < width)
+            {
+              // It does fit on the line, so add it.
+              lineBuffer.append(delimBuffer).append(word);
+
+              if (delimBuffer.length() > 0)
+              {
+                delimBuffer = new StringBuilder();
+              }
+            }
+            else
+            {
+              // It doesn't fit on the line, so end the current line and start
+              // a new one.
+              buffer.append(lineBuffer);
+              buffer.append(EOL);
+
+              lineBuffer = new StringBuilder();
+              lineBuffer.append(word);
+
+              if (delimBuffer.length() > 0)
+              {
+                delimBuffer = new StringBuilder();
+              }
+            }
+          }
+        }
+
+        // If there's anything left in the line buffer, then add it to the
+        // final buffer.
+        buffer.append(lineBuffer);
+      }
+    }
+
+    return buffer.toString();
+  }
+
+}
diff --git a/sdk/build-tools/org/opends/build/tools/ValidJavaVersion.java b/sdk/build-tools/org/opends/build/tools/ValidJavaVersion.java
new file mode 100644
index 0000000..aafc80b
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/ValidJavaVersion.java
@@ -0,0 +1,68 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.build.tools;
+
+import org.apache.tools.ant.taskdefs.condition.Condition;
+import org.apache.tools.ant.BuildException;
+
+/**
+ * Ant condition to check whether we have a minimum required Java version.
+ */
+public class ValidJavaVersion implements Condition
+{
+  // The minimum required Java version.
+  String minVersion;
+
+  /**
+   * Set the minVersion attribute.
+   * @param minVersion The minimum required Java version.
+   */
+  public void setMinVersion(String minVersion)
+  {
+    this.minVersion = minVersion;
+  }
+
+
+  /**
+   * Evaluate the condition.
+   */
+  public boolean eval() throws BuildException
+  {
+    if (minVersion == null)
+    {
+      return true;
+    }
+
+    String version = System.getProperty("java.version");
+    if (version == null)
+    {
+      return false;
+    }
+
+    return version.compareTo(minVersion) >= 0;
+  }
+}
diff --git a/sdk/build-tools/org/opends/build/tools/package-info.java b/sdk/build-tools/org/opends/build/tools/package-info.java
new file mode 100644
index 0000000..2c234a5
--- /dev/null
+++ b/sdk/build-tools/org/opends/build/tools/package-info.java
@@ -0,0 +1,36 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+
+
+
+/**
+ * This package contains source for a number of tools that are used in some way
+ * during the OpenDS build process.  This includes custom Ant tasks, utilities
+ * to format schema files, and to facilitate running server unit tests.
+ */
+package org.opends.build.tools;
+

--
Gitblit v1.10.0