mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

matthew_swift
03.04.2009 39db72786ec179e67e3c1c0c71a2e93672999ea5
sdk/build-tools/org/opends/build/tools/GenerateMessageFile.java
@@ -26,230 +26,194 @@
 */
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 static org.opends.build.tools.Utilities.*;
import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
/**
 * Generates a Java class containing representations of messages
 * found in a properties file.
 * Generates a Java class containing representations of messages found
 * in a properties file.
 */
public class GenerateMessageFile extends Task {
public class GenerateMessageFile extends Task
{
  /**
   * The maximum number of arguments that can be handled by a specific
   * subclass. If you define more subclasses be sure to increment this
   * number appropriately.
   */
  static public final int DESCRIPTOR_MAX_ARG_HANDLER = 11;
  /**
   * The base name of the specific argument handling subclasses defined
   * below. The class names consist of the base name followed by a
   * number indicating the number of arguments that they handle when
   * creating messages or the letter "N" meaning any number of
   * arguments.
   */
  public static final String DESCRIPTOR_CLASS_BASE_NAME = "Arg";
  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";
  static private final String MESSAGES_FILE_STUB = "resource/Messages.java.stub";
  /**
   * 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);
  static
  {
    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%])";
  static private final String SPECIFIER_REGEX = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])";
  private final Pattern SPECIFIER_PATTERN = Pattern.compile(SPECIFIER_REGEX);
  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();
  static
  {
    KEY_FORM_MSG = new StringBuilder().append(
        ".\n\nOpenDS message property keys must be of the form\n\n")
        .append("\t\'[DESCRIPTION]_[ORDINAL]\'\n\n")
        .append("where\n\n").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")
        .toString();
  }
  /*
   * ISO_LANGUAGES contains all official supported languages for i18n
   */
  private static final List<String> ISO_LANGUAGES =
                                        Arrays.asList(Locale.getISOLanguages());
  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());
  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>_<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");
  private static final Pattern LANGUAGE_MATCHER = Pattern
      .compile("(.*)_([a-z]{2}).properties");
  /**
   * Representation of a format specifier (for example %s).
   */
  private class FormatSpecifier {
  private class FormatSpecifier
  {
    private String[] sa;
    /**
     * Creates a new specifier.
     * @param sa specifier components
     *
     * @param sa
     *          specifier components
     */
    FormatSpecifier(String[] sa) {
    FormatSpecifier(String[] sa)
    {
      this.sa = sa;
    }
    /**
     * Indicates whether or not the specifier uses arguement
     * indexes (for example 2$).
     * Indicates whether or not the specifier uses arguement indexes
     * (for example 2$).
     *
     * @return boolean true if this specifier uses indexing
     */
    public boolean specifiesArgumentIndex() {
    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.
     * 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;
    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)) {
      if ("t".equals(sa4))
      {
        c = Calendar.class;
      } else if (
              "b".equals(sa5)) {
      }
      else if ("b".equals(sa5))
      {
        c = Boolean.class;
      } else if (
              "h".equals(sa5)) {
      }
      else if ("h".equals(sa5))
      {
        c = Integer.class;
      } else if (
              "s".equals(sa5)) {
      }
      else if ("s".equals(sa5))
      {
        c = CharSequence.class;
      } else if (
              "c".equals(sa5)) {
      }
      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)) {
      }
      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)) {
      }
      else if ("n".equals(sa5) || "%".equals(sa5))
      {
        // ignore literals
      }
      return c;
@@ -257,73 +221,103 @@
  }
  /**
   * Represents a message to be written into the messages files.
   */
  private class MessageDescriptorDeclaration {
  private class MessageDescriptorDeclaration
  {
    private MessagePropertyKey key;
    private String formatString;
    private List<FormatSpecifier> specifiers;
    private List<Class> classTypes;
    private List<Class<?>> classTypes;
    private String[] constructorArgs;
    /**
     * Creates a parameterized instance.
     * @param key of the message
     * @param formatString of the message
     *
     * @param key
     *          of the message
     * @param formatString
     *          of the message
     */
    public MessageDescriptorDeclaration(MessagePropertyKey key,
                                     String formatString) {
        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) {
      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() {
    public String getDescriptorClassDeclaration()
    {
      StringBuilder sb = new StringBuilder();
      if (useGenericMessageTypeClass()) {
        sb.append(getShortClassName(MessageDescriptor.class));
      if (useGenericMessageTypeClass())
      {
        sb.append("MessageDescriptor");
        sb.append(".");
        sb.append(MessageDescriptor.DESCRIPTOR_CLASS_BASE_NAME);
        sb.append(DESCRIPTOR_CLASS_BASE_NAME);
        sb.append("N");
      } else {
        sb.append(getShortClassName(MessageDescriptor.class));
      }
      else
      {
        sb.append("MessageDescriptor");
        sb.append(".");
        sb.append(MessageDescriptor.DESCRIPTOR_CLASS_BASE_NAME);
        sb.append(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.
     * 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() {
    public String getClassTypeVariables()
    {
      StringBuilder sb = new StringBuilder();
      if (classTypes.size() > 0) {
      if (classTypes.size() > 0)
      {
        sb.append("<");
        for (int i = 0; i < classTypes.size(); i++) {
          Class c = classTypes.get(i);
          if (c != null) {
        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) {
            if (i < classTypes.size() - 1)
            {
              sb.append(",");
            }
          }
@@ -333,12 +327,16 @@
      return sb.toString();
    }
    /**
     * Gets the javadoc comments that will appear above the messages declaration
     * in the messages file.
     * Gets the javadoc comments that will appear above the messages
     * declaration in the messages file.
     *
     * @return String comment
     */
    public String getComment() {
    public String getComment()
    {
      StringBuilder sb = new StringBuilder();
      sb.append(indent(1)).append("/**").append(EOL);
@@ -347,27 +345,36 @@
      String ws = formatString; // wrapText(formatString, 70);
      String[] sa = ws.split(EOL);
      for (String s : sa) {
      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
     * 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) {
    public void setConstructorArguments(String... s)
    {
      this.constructorArgs = s;
    }
    /**
     * {@inheritDoc}
     */
    public String toString() {
    public String toString()
    {
      StringBuilder sb = new StringBuilder();
      sb.append(getComment());
      sb.append(indent(1));
@@ -381,10 +388,13 @@
      sb.append("new ");
      sb.append(getDescriptorClassDeclaration());
      sb.append("(");
      if (constructorArgs != null) {
        for (int i = 0; i < constructorArgs.length; i++) {
      if (constructorArgs != null)
      {
        for (int i = 0; i < constructorArgs.length; i++)
        {
          sb.append(constructorArgs[i]);
          if (i < constructorArgs.length - 1) {
          if (i < constructorArgs.length - 1)
          {
            sb.append(",");
          }
        }
@@ -395,20 +405,29 @@
      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.
     * 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) {
    private boolean useGenericMessageTypeClass()
    {
      if (specifiers.size() > DESCRIPTOR_MAX_ARG_HANDLER)
      {
        return true;
      } else if (specifiers != null) {
        for (FormatSpecifier s : specifiers) {
          if (s.specifiesArgumentIndex()) {
      }
      else if (specifiers != null)
      {
        for (FormatSpecifier s : specifiers)
        {
          if (s.specifiesArgumentIndex())
          {
            return true;
          }
        }
@@ -416,51 +435,66 @@
      return false;
    }
    /**
     * Look for format specifiers in the format string.
     * @param s format string
     *
     * @param s
     *          format string
     * @return list of format specifiers
     */
    private List<FormatSpecifier> parse(String s) {
    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)) {
      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) {
          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())));
            // 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++) {
          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
        }
        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)));
          // al.add(new FixedString(s.substring(i)));
          break;
        }
      }
      return sl;
    }
    private void checkText(String s) {
    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) {
      if ((idx = s.indexOf('%')) != -1)
      {
        char c = (idx > s.length() - 2 ? '%' : s.charAt(idx + 1));
        throw new UnknownFormatConversionException(String.valueOf(c));
      }
@@ -468,132 +502,116 @@
  }
  /**
   * Sets the source of the messages.
   * @param source File representing the properties
   *        file containing messages
   *
   * @param source
   *          File representing the properties file containing messages
   */
  public void setSourceProps(File source) {
  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
   * Sets the file that will be generated containing declarations of
   * messages from <code>source</code>.
   *
   * @param dest
   *          File destination
   */
  public void setDestJava(File dest) {
  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
   * Indicates when true that an existing destination file will be
   * overwritten.
   *
   * @param o
   *          boolean where true means overwrite
   */
  public void setOverwrite(boolean o) {
  public void setOverwrite(boolean o)
  {
    this.overwrite = o;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public void execute() throws BuildException {
  public void execute() throws BuildException
  {
    if ( this.dest == null ) {
    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;
      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.
       * 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
      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)) ) {
      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);
      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"));
      pathname = pathname.replace(getProject().getProperty("src.dir"),
          getProject().getProperty("srcgen.dir"));
      // append characters from filename to pathname starting with an uppercase
      // letter, ignoring '_' and uppering all characters prefixed with "_"
      // 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 == '_' ) {
      for (char c : filename.toCharArray())
      {
        if (c == '_')
        {
          upperCaseNextChar = true;
          continue;
        }
        if ( upperCaseNextChar ) {
        if (upperCaseNextChar)
        {
          sb.append(Character.toUpperCase(c));
          upperCaseNextChar = false;
        } else {
        }
        else
        {
          sb.append(c);
        }
      }
@@ -604,372 +622,296 @@
    BufferedReader stubReader = null;
    PrintWriter destWriter = null;
    try {
    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 (!source.exists())
      {
        throw new BuildException("file " + source.getName()
            + " does not exist");
      }
      if (dest.exists()) {
        if (this.overwrite || source.lastModified() > dest.lastModified()) {
      if (dest.exists())
      {
        if (this.overwrite
            || source.lastModified() > dest.lastModified())
        {
          dest.delete();
          log("Regenerating " + dest.getName() + " from " + source.getName());
        } else {
          log("Regenerating " + dest.getName() + " from "
              + source.getName());
        }
        else
        {
          log(dest.getName() + " is up to date");
          return;
        }
      } else {
      }
      else
      {
        File javaGenDir = dest.getParentFile();
        if (!javaGenDir.exists()) {
        if (!javaGenDir.exists())
        {
          javaGenDir.mkdirs();
        }
        log("Generating " + dest.getName() + " from " + source.getName());
        log("Generating " + dest.getName() + " from "
            + source.getName());
      }
      stubReader = new BufferedReader(new InputStreamReader(getStubFile(),
                                                            "UTF-8"));
      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}")) {
      while (null != (stubLine = stubReader.readLine()))
      {
        if (stubLine.contains("${MESSAGES}"))
        {
          Integer globalOrdinal = null;
          String go = properties.getProperty(GLOBAL_ORDINAL);
          if (go != null) {
          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);
            }
          }
          Map<MessagePropertyKey, String> keyMap = new TreeMap<MessagePropertyKey, String>();
          // 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()) {
          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);
            try
            {
              if (!DIRECTIVE_PROPERTIES.contains(propKey))
              {
                MessagePropertyKey key = MessagePropertyKey
                    .parseString(propKey, 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);
            }
            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()) {
          for (MessagePropertyKey key : keyMap.keySet())
          {
            String formatString = keyMap.get(key);
            MessageDescriptorDeclaration message =
                    new MessageDescriptorDeclaration(key, formatString);
            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) {
            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 {
              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()
            );
            message.setConstructorArguments("BASE", quote(key
                .toString()), 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()) {
            if (message.useGenericMessageTypeClass())
            {
              usesOfGenericDescriptor++;
            }
          }
          log("  Message Generated:" + keyMap.size(), Project.MSG_VERBOSE);
          log("  Message Generated:" + keyMap.size(),
              Project.MSG_VERBOSE);
          log("  MessageDescriptor.ArgN:" + usesOfGenericDescriptor,
                  Project.MSG_VERBOSE);
              Project.MSG_VERBOSE);
        } else {
        }
        else
        {
          stubLine = stubLine.replace("${PACKAGE}", getPackage());
          stubLine = stubLine.replace("${CLASS_NAME}",
                  dest.getName().substring(0, dest.getName().length() -
                          ".java".length()));
          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) {
    }
    catch (Exception e)
    {
      // Don't leave a malformed file laying around. Delete
      // it so it will be forced to be regenerated.
      if (dest.exists()) {
      if (dest.exists())
      {
        dest.deleteOnExit();
      }
      e.printStackTrace();
      throw new BuildException("Error processing " + source +
              ":  " + e.getMessage());
    } finally {
      if (stubReader != null) {
        try {
      throw new BuildException("Error processing " + source + ":  "
          + e.getMessage());
    }
    finally
    {
      if (stubReader != null)
      {
        try
        {
          stubReader.close();
        } catch (Exception e){
        }
        catch (Exception e)
        {
          // ignore
        }
      }
      if (destWriter != null) {
        try {
      if (destWriter != null)
      {
        try
        {
          destWriter.close();
        } catch (Exception e){
        }
        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() {
  private String getBase()
  {
    String srcPath = unixifyPath(source.getAbsolutePath());
    String base = srcPath.substring(srcPath.lastIndexOf("/") + 1,
                                    srcPath.length() - ".properties".length());
        srcPath.length() - ".properties".length());
    return base;
  }
  private String getPackage() {
  private String getPackage()
  {
    String destPath = unixifyPath(dest.getAbsolutePath());
    String msgJavaGenDir = unixifyPath(
                                   getProject().getProperty("msg.javagen.dir"));
    String c = destPath.substring(msgJavaGenDir.length()+1);
    String msgJavaGenDir = unixifyPath(getProject().getProperty(
        "srcgen.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) {
  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 quote(String s)
  {
    return new StringBuilder().append("\"").append(s).append("\"")
        .toString();
  }
  static private String getShortClassName(Class c) {
  static private String getShortClassName(Class<?> c)
  {
    String name;
    String fqName = c.getName();
    int i = fqName.lastIndexOf('.');
    if (i > 0) {
    if (i > 0)
    {
      name = fqName.substring(i + 1);
    } else {
    }
    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
  private File getProjectBase()
  {
    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) {
    if (fileName != null)
    {
      File f = new File(fileName);
      projectBase = f.getParentFile();
    } else {
    }
    else
    {
      projectBase = new File(System.getProperty("user.dir"));
    }
    return projectBase;
  }
  private String unixifyPath(String path) {
  private String unixifyPath(String path)
  {
    return path.replace("\\", "/");
  }
  /*
   * Returns the stub file ("resource/Messages.java.stub") from the appropriate
   * location: ant or jar file.
   * Returns the stub file ("resource/Messages.java.stub") from the
   * appropriate location: ant or jar file.
   */
  private InputStream getStubFile() {
  private InputStream getStubFile()
  {
    InputStream result = null;
    File stub = new File(getProjectBase(), MESSAGES_FILE_STUB);
    if ( stub.exists() ) {
    if (stub.exists())
    {
      // this is the OpenDS's ant project calling
      // Stub is located at OPENDS_ROOT/resource/Messages.java.stub
      try {
      try
      {
        result = new FileInputStream(stub);
      } catch (FileNotFoundException e) {
        // should neven happen
        throw new BuildException("Unable to load template " +
              MESSAGES_FILE_STUB + ":  " + e.getMessage());
      }
    } else {
      catch (FileNotFoundException e)
      {
        // should never 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);
@@ -978,18 +920,4 @@
    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();
  }
}