package tools;

import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Properties;

/** This class represents the build properties for a specific option tree.
    <br>
    <br>
    The properties held by this class are a combination of the following things:
    <ol>
      <li>
        The property <code>nmroot</code> is set to the path to the
        <code>$NMROOT</code> directory as determined by the system
        property <code>NMROOT</code>.
      </li>
      <li>
        (Deprecated since NeXtMidas 3.3.4 - NOT used in baseline)
        The property <code>nmlibpath</code> is a Java classpath that includes
        all <code>.jar</code> files in <code>$NMROOT/jars</code>,
        <code>$NMROOT/nxm/sys/jars</code> and <code>$NMROOT/jars</code> that do
        not begin with <code>nxm-</code> (the prefix for the NeXtMidas WebStart
        Jars).
      </li>
      <li>
        The properties file for a specified option tree (located
        at <code>$NMROOT/nxm/<i>option</i>/cfg/build.props</code> where
        <code><i>option</i></code> is the name of the appropriate option
        tree other than <code>SYS</code>). If no such file exists, it is
        ignored.
      </li>
      <li>
        The properties file for the <code>SYS</code> option tree (located
        at <code>$NMROOT/nxm/sys/cfg/build.props</code>).
      </li>
      <li>
        The system properties (via the {@link System#getProperties()} method).
      </li>
    </ol>
    If any of the above properties include duplicate definitions of the same
    property the highest ranked property (lowest number on above list) will
    be used. This enables an arbitrary option tree to override the properties
    defined in the <code>SYS</code> option tree.<br>
    <br>
    <b>Implementation note:</b> This class does not require access to any
    other classes outside of the <code>java.*</code> package. This permits
    the class to be compiled and run independent of other NeXtMidas classes.

    @author  J. Czechowski IV / Neon Ngo
    @since   NeXtMidas 1.9.2
 */
public final class BuildProps extends Properties {
  /** NOTE: this should be changed if the structure/fields of this class changes. */
  private static final long serialVersionUID = 2011051700292L;

  /** max number of nested levels allowed to expand variables in properties values.
      used to prevent StackOverflowError from recursive property variable expansion.
      @since NeXtMidas 3.3.1
   */
  private static final int MAX_NESTED_LEVEL = 10;

  private static final String ARCH_BIT = getArchType() + "bit"; // since 3.6.0
  /** prepend/append suffixes for use in main() to better match those in nxm.sys.lib.Build. Since NeXtMidas 3.6.0 */
  static final String[] PREPEND_SUFFIXES = { ".prepend." + ARCH_BIT, ".prepend" },
                        APPEND_SUFFIXES  = { ".append"             , ".append." + ARCH_BIT};

  /** The set of all currently loaded properties files.
      Properties files are cached in memory until {@link #clearCache()} or
      when the JVM exits.
      <pre>
        KEY   - The name of the option tree (in uppercase).
        VALUE - The instance of BuildProps for the specified option tree.
      </pre>
   */
  private static Hashtable<String,BuildProps> allBuildProps = new Hashtable<>();

  /** Last modified time of property file when it was loaded (used to reload file if it has changed) */
  private long lastModified = 0; // using zero, since File.lastModified() return that if file does not exists or on IO error
  /** Property file reference (used to reload file if it has changed since we last read it) */
  private File propertyFile;
  /** option tree name (in uppercase) for this BuildProps instance. Since NeXtMidas 3.6.0 */
  private final String optNameUC;

  /** This will instantiate the class for the specified option tree.

      @param opt The option tree to get the properties for (cannot be null).
      @param ref The reference object (a pointer to Shell if available).

      @throws NullPointerException If opt is null.
      @throws RuntimeException If the properties file cannot be read.
   */
  private BuildProps(String opt, Object ref) {
    super(opt.equalsIgnoreCase("SYS")? System.getProperties():
                                       BuildProps.getPropsFor("SYS")); // Use SYS BuildProps as defaults for all other option trees
    this.optNameUC = opt.toUpperCase();
    try {
      String fileName = BuildProps.getNmRoot()
                      + File.separator + "nxm"
                      + File.separator + opt.toLowerCase()
                      + File.separator + "cfg"
                      + File.separator + "build.props";

      if (ref != null) {
        Method getOptPathMethod = ref.getClass().getMethod("getOptPath", new Class<?>[] {String.class,String.class});
        fileName = "" + getOptPathMethod.invoke(ref, (Object[])new String[] { opt, "cfg" }) + "build.props";
      }

      propertyFile = new File(fileName);
//System.out.println("DEBUG: BuildProps(..) propertyFile = " + propertyFile + " [" + opt + "]");

      if (propertyFile.exists()) {
        lastModified = propertyFile.lastModified();
        this.load(new FileInputStream(propertyFile));
      }
//    else System.out.println("DEBUG: no build.props for " + opt + " at " + fileName);
    } catch (Exception exception) {
      throw new RuntimeException("Unable to load properties file for the [" + optNameUC + "] option tree.", exception);
    }

    this.put("nmroot",    BuildProps.getNmRoot());
    this.put("nmlibpath", BuildProps.getNmLibPath()); // <- deprecated since 3.3.4 (not used in baseline)

    allBuildProps.put(optNameUC, this);
  }

  /** Clear (reset) all cached properties files (build.props) from memory.
      @since NeXtMidas 2.7.0
   */
  public static void clearCache() {
    allBuildProps.clear();
  }

  /** This will attempt to determine the NMROOT variable.
      @return The NMROOT variable.
   */
  private static String getNmRoot() {
    String nmroot = System.getProperty("NMROOT");

    /* If nmroot is still null then NMROOT is probably not specified. This
       usually means the user started something from the command line (such as
       BaselineBuild) and did not specify the value of NMROOT. Therefore just
       output a message explaining this and exit.
     */
    if (nmroot == null) {
      String errmsg = "ERROR: The value of NMROOT is undefined. Try running "
                    + "this application again with '-DNMROOT=???' specified "
                    + "(where ??? is the path to your NMROOT).";
      System.err.println(errmsg);
      try { Thread.sleep(3000); }       // since 3.3.4: sleep a little bit so that Web Start can see this message (BUG 2763)
      catch (InterruptedException e) {}
      System.exit(-1);                  // TODO: move this call to later on (e.g. in main(..))
      throw new RuntimeException(errmsg);
    } else {
      return nmroot;
    }
  }

  /** This will attempt to determine the CLASSPATH variable that includes all of
      the NeXtMidas libraries.

      @return The CLASSPATH variable for the NeXtMidas libraries.
      @see #getNmLibs()
      @deprecated Since NeXtMidas 3.3.4 - not used in baseline
   */
  @Deprecated
  private static String getNmLibPath() {
    final File[] jars = getNmLibs();
    final StringBuffer classpath  = new StringBuffer();

    if (jars.length > 0) {
      classpath.append(jars[0]);
      for (int i = 1; i < jars.length; i++) {
        classpath.append(File.pathSeparator);
        classpath.append(jars[i]);
      }
    }

    return classpath.toString();
  }

  /** This will attempt to determine the CLASSPATH variable that includes all of
      the NeXtMidas libraries. <br>
      <br>
      By default this will include all <code>.jar</code> files that do not begin
      with <code>nxm-</code> in <code>$NMROOT/jars</code>,
      <code>$NMROOT/nxm/sys/jars</code> and <code>$JAVAHOME/lib</code>.

      @return The library files to include in the classpath for NeXtMidas.
      @deprecated Since NeXtMidas 3.3.4 - not used in baseline
   */
  @Deprecated
  private static File[] getNmLibs() {
    final File           nxmDir  = new File(getNmRoot()+File.separator+"nxm");
    final File[]         optDirs = nxmDir.listFiles();
    final ArrayList<String>      dirs = new ArrayList<>();
    final Hashtable<String,File> jars = new Hashtable<>();
    final FilenameFilter filter  = new FilenameFilter() {
        public boolean accept(File dir, String name) {
          return name.endsWith(".jar") && (!name.startsWith("nxm-"));
        }
    };

    dirs.add(getNmRoot() + File.separator + "jars");
    if (optDirs != null) { // since 3.3.4 - prevent NPE
      for (File dir : optDirs) {
        dirs.add(dir.getAbsolutePath() + File.separator + "jars");
      }
    }
    dirs.add(System.getProperty("javahome") + File.separator + "lib");

    for (String dir : dirs) {
      final File jarDir = new File(dir);

      if (jarDir.exists() && jarDir.isDirectory()) {
        final File[] files = jarDir.listFiles(filter);

        for (File file : files) {
          jars.put(file.getName(), file);
        }
      }
    }

    // ORIG return (File[])new Vector(jars.values()).toArray(new File[0]);
    return jars.values().toArray(new File[0]);
  }

  /** Reload the property file if it is newer than the current cached one.
      @since NeXtMidas 2.9.2
   */
  private void checkForChanges () {
    try {
      if (propertyFile.lastModified() > lastModified) {
        if (propertyFile.exists()) {
          lastModified = propertyFile.lastModified();
          load(new FileInputStream(propertyFile));
        }
      }
    } catch (Exception e) {
      warn("BuildProps.checkForChanges: unable to check for changes to properties file: "+propertyFile);
      e.printStackTrace(System.err);
    }
  }

  /** This will get a build property for the <code>SYS</code> option tree.
      <br>
      <br>
      This is identical to <code>BuildProps.getPropFor("SYS",prop)</code>.

      @param prop The name of the property to get.

      @return The specified property for the <code>SYS</code> option tree,
              or <code>null</code> if such property does not exist.

      @see #getPropFor(String,String)
   */
  public static final String getSysProp(String prop) {
    return BuildProps.getPropFor("SYS", prop);
  }

  /** This will get a build property for the <code>SYS</code> option tree as
      a path for the current operating system.
      <br>
      <br>
      This is identical to <code>BuildProps.getPathFor("SYS",prop)</code>.

      @param prop The name of the property to get.

      @return The specified property for the <code>SYS</code> option tree,
              or <code>null</code> if such property does not exist.

      @see #getPathFor(String,String)
   */
  public static final String getSysPath(String prop) {
    return BuildProps.getPathFor("SYS", prop);
  }

  /** This will get the build property for a given option tree as
      a path for the current operating system. <br>
      <br>
      This is identical to
      <code>BuildProps.getPropFor(opt).getProperty(prop)</code>.

      @param opt  name of option tree to get the properties for
      @param prop The name of the property to get.

      @return The specified property for the <code>SYS</code> option tree,
              or <code>null</code> if such property does not exist.

      @see #getPropsFor(String)
      @see #getProperty(String)
   */
  public static final String getPropFor(String opt, String prop) {
      return getPropFor(opt, prop, null);
  }

  /** This will get the build property for a given option tree as
      a path for the current operating system. <br>
      <br>
      This is identical to
      <code>BuildProps.getPropsFor(opt).getProperty(prop)</code>.

      @param opt  name of option tree to get the properties for
      @param prop The name of the property to get.
      @param ref The reference object (a pointer to Shell if available).
      @return The specified property for the <code>SYS</code> option tree,
              or <code>null</code> if such property does not exist.

      @see #getPropsFor(String)
      @see #getProperty(String)
   */
  public static final String getPropFor(String opt, String prop, Object ref) {
    return BuildProps.getPropsFor(opt,ref).getProperty(prop);
  }

  /** This will get the build property for a given option tree. <br>
      <br>
      This is identical to
      <code>BuildProps.getPropFor(opt).getPath(prop)</code>.

      @param opt  name of option tree to get the properties for
      @param prop The name of the property to get.

      @return The specified property for the <code>SYS</code> option tree,
              or <code>null</code> if such property does not exist.

      @see #getPropsFor(String)
      @see #getPath(String)
   */
  public static final String getPathFor(String opt, String prop) {
      return getPathFor(opt, prop, null);
  }

  /** This will get the build property for a given option tree. <br>
      <br>
      This is identical to
      <code>BuildProps.getPropFor(opt).getPath(prop)</code>.

      @param opt  name of option tree to get the properties for (cannot be null).
      @param prop The name of the property to get.
      @param ref The reference object (a pointer to Shell if available).
      @return The specified property for the <code>SYS</code> option tree,
              or <code>null</code> if such property does not exist.

      @see #getPropsFor(String)
      @see #getPath(String)
   */
  public static final String getPathFor(String opt, String prop, Object ref) {
    return BuildProps.getPropsFor(opt,ref).getPath(prop);
  }

  /** This will get the build properties for the specified option tree. <br>
      <br>
      If the properties file for the given option tree has not yet been loaded
      it will be loaded at this time. If the properties file for the option
      tree has already been loaded it will use the existing instance.

      @param opt The option tree to get the properties for (cannot be null).
      @return The build properties for the specified option tree.
   */
  public static final BuildProps getPropsFor (String opt) {
    return getPropsFor(opt, null);
  }

  /** This will get the build properties for the specified option tree. <br>
      <br>
      If the properties file for the given option tree has not yet been loaded
      it will be loaded at this time. If the properties file for the option
      tree has already been loaded it will use the existing instance.

      @param opt The option tree to get the properties for (cannot be null).
      @param ref The reference object (a pointer to Shell if available).
      @return The build properties for the specified option tree.
      @throws NullPointerException if opt is null
   */
  public static final BuildProps getPropsFor (String opt, Object ref) {
    if (opt == null) throw new NullPointerException("option tree cannot be null!");
    BuildProps props = allBuildProps.get(opt.toUpperCase());

    if (props != null) {
      props.checkForChanges();
    } else {
      props = new BuildProps(opt, ref);
    }
    return props;
  }

  /** This will expand variables specified via ("${varname}") in a string. This means that each
      variable surrounded by a dollar and braces will get replaces with the corresponding property's value.
      For example, if the following properties are set: <pre>
      x = joey
      test.joey = bella
      then
      y = Hello ${x}.       ==> Hello joey.
      z = Hi ${test.${x}}!  ==> Hi bella!  (e.g. nested variables are expanded since 2.9.2)
      @param value The original string with any ${varname} in it resolved (expanded).
      @param nestedLevel recursive nesting level (after #MAX_NESTED_LEVEL levels, a Runtime Exception will be thrown
                         since this is probably b/c of a recursive property variable expansion. (Since 3.3.1)
      @return The string with any variables resolved.
      @throws RuntimeException if a variable to expand is NOT found (i.e. not defined) or if braces are unbalanced.
      @since NeXtMidas 2.9.2
   */
  private String expandVariables (String value, int nestedLevel) {
    if (value==null) return value;
    int iDollar, iEnd, lenValue;

    // recursively check for embedded variables and replace them (working from backwards from end of string)
    for (int jj=lenValue=value.length(); (iDollar=value.lastIndexOf('$',jj))>=0; jj=iDollar-1) {
      String expandedValue = null;
      if ((iDollar+1 < lenValue) && value.charAt(iDollar+1)=='{') {
        iEnd = value.indexOf('}',iDollar);
        // If the curly braces do not match (i.e. "{ ... }") then it is an error.
        //   For example, "hello ${A" would cause this method to throw an exception.
        if (iEnd < iDollar+1) {
          throw new RuntimeException("Error loading property. Syntax Error: Can not evaluate variable: Missing '}' character in ( " + value + " ).");
        }
        String varName = value.substring(iDollar+2, iEnd);
        if (nestedLevel >= MAX_NESTED_LEVEL) // since 3.3.1
          throw new RuntimeException("Error loading property. Too many nested levels [" + nestedLevel
            + "] trying to expand variable(s) in property [" + varName + "] from value [" + value
            + "]. Check for any recursive property variables to expand.");

        expandedValue = getProperty(varName, nestedLevel+1);

        if (expandedValue == null) {
          throw new RuntimeException("Error loading property. Unknown variable '${" + varName + "}'.");
        }

        iEnd++; // point to just after right (closing) brace
      }
      else {
        if (jj<1) continue;
        iEnd = iDollar+jj+1;
      }
      if (expandedValue==null) continue;
      if (iEnd<lenValue) {
        expandedValue = expandedValue + value.substring(iEnd); // append value to right of expanded variable's value
      }
      if (iDollar==0) return expandedValue; // at start, so just return expanded value
      value = value.substring(0, iDollar) + expandedValue; // process again with left of variable + expanded variable's value
      lenValue = value.length();
    } // end for loop
    return value;
  }

  /** Gets the specified property. <br>
      <br>
      When loading the property the following translations will be performed:
      <ul>
        <li>
          Any embedded variables will be replaced. (For example a variable
          defined as <code>"${nmroot}/nxm/sys/mcr"</code> in the properties
          file will have <code>${nmroot}</code> replaced with the property it
          represents.)
        </li>
      </ul>

      @param key The name of the property to get. If <code><i>key</i></code>
                 starts with <code>"env."</code> it will automatically get
                 the property from the system properties (via
                 {@link System#getProperties()}). <i>(This feature is for
                 compatibility with Ant which can load the system properties
                 to match this.)</i> Cannot be null.
      @return  The value in this property list with the specified key value. Returns
               null if the property is not found at the specified key.
      @throws NullPointerException If key is null.
      @throws RuntimeException If the property has an embedded variable that can not be resolved.

      @see #getProperty(String,String)
   */
  public final String getProperty (String key) {
    return getProperty(key, 0);
  }

  /** @see #getProperty(String)
      @since NeXtMidas 3.3.1
   */
  private final String getProperty (String key, int nestedLevel) {
    String prop = null;
    try {
      if (key.startsWith("env.")) {
        prop = System.getProperty(key.substring(4));
      } else {
        checkForChanges();
        // NOTE: DO NOT call super.getProperty(optNameUC + "." + key) or key + (append|prepend)suffix, etc. here
        //       since it can call default(SYS-BuildProps).getProperty(String) and not return proper value for non-SYS option tree
        prop = super.getProperty(key);
      }
    } catch (RuntimeException exception) {
      warn("Unable to get '" + key + "' property. " + exception.getMessage());
      // Unable to get property, just make it null.
      prop = null;
    }

    return expandVariables(prop, nestedLevel);
  }

  /** Get value of property at specified key. If prependSuffixes is specified, this
      method will search for properties at "key + prependSuffixes[n]" and prepend it to
      the property value (space separated). Similarly, if appendSuffixes is specified, this
      method will search for properties at "key + appendSuffixes[n]" and append
      it to the property value (spaces separated). <br>
      For example, given following property key/values
      <pre>
      mykey = ABC
      mykey.32bit = options for 32-bit
      mykey.64bit = options for 64-bit
      mykey.append = ZZZ
      mykey.prepend = JJJ

      and the following code:
      String[] prependSuffixes = {".prepend"};
      String[] appendSuffixes = {".32bit", ".anothersuffix", ".append"};
      String propValue  = getProperty("mykey", prependSuffixes, appendSuffixes);
      ----
      The above gets propValue will include all the values from keys at:
        "mykey.prepend" + "mykey", "mykey.32bit", "mykey.anothersuffix", "mykey.append",
      (if any of those properties exists in that order).
      This results in
        propValue {@code ==>} "JJJ ABC options for 32-bit ZZZ".</pre>

      Since NeXtMidas 3.6.0: OPT_NAME(in uppercase).key will override key, and so forth for prepend and append suffixes.
      e.g. if SYS.mykey = <span style="color:blue">FOO</span> and SYS.mykey.append = <b>BAR</b>, then above will result in:
        propValue {@code ==>} "JJJ <span style="color:blue">FOO</span> options for 32-bit <b>BAR</b>".

      @param key             The name of the property to get.
      @param prependSuffixes String array of suffixes (e.g. {".prepend.32bit"}) to prepend to key
                             to search for additional property values to prepend to final returned
                             property value. Ignored if null.
      @param appendSuffixes  String array of suffixes (e.g. {".append.64bit"}) to append to key
                             to search for additional property values to append to final returned
                             property value. Ignored if null.
      @return the property value at specified key (null if none).
      @see #getProperty(String)
      @since NeXtMidas 2.9.2
   */
  public final String getProperty (String key, String[] prependSuffixes, String[] appendSuffixes) {
    final String optDotKey = this.optNameUC + "." + key;
    StringBuilder sb = new StringBuilder();
    String val;

    if (prependSuffixes != null) {
      for (String suffix : prependSuffixes) {  // prepend to property the following key suffixes
        val = getProperty(optDotKey + suffix); // since 3.6.0, overrides <key>.<prepend-suffix>
        if (val == null) val = getProperty(key + suffix);
        if (val != null && val.length()>0) {   // ignore null and empty values
          sb.append(val).append(" ");
        }
      }
    }

    val = getProperty(optDotKey);              // since 3.6.0: <OPT>.<key> overrides <key>
    if (val == null) val = getProperty(key);
    if (val != null && val.length()>0) {       // ignore null and empty values
      sb.append(val);
    }

    if (appendSuffixes != null) {              // append to property the following key suffixes
      for (String suffix : appendSuffixes) {
        val = getProperty(optDotKey + suffix); // since 3.6.0, overrides <key>.<append-suffix>
        if (val == null) val = getProperty(key + suffix);
        if (val != null && val.length()>0) {   // ignore null and empty values
          sb.append(" ").append(val);
        }
      }
    }

    return (sb.length() == 0)? null : sb.toString();
  }

  /** Gets the specified property as a path for the current
      operating system. <br>
      <br>
      This is identical to <code>getPath(key, null)</code>

      @param key The name of the property to get.
      @return The value as a path string for current OS of the property matching the given key or
              null if the key is not found.
      @see #getProperty(String)
      @see #getPath(String, String)
   */
  public final String getPath(String key) {
    return this.getPath(key,null);
  }

  /** Gets the specified property as a path for the current
      operating system. <br>
      <br>
      This is the same as {@link #getProperty(String)} except that any path
      separators in the property (either <code>'\'</code> (DOS) or
      <code>'/'</code> (UNIX)) will be converted to the path separator in use
      by the current operating system.

      @param key The name of the property to get.
      @param def The default value to return if the key is not found.

      @return The value of the property matching the given key or
              <code><i>def</i></code> the key is not found.

      @see #getProperty(String)
   */
  public final String getPath(String key, String def) {
    String prop = this.getProperty(key, def);

    if (prop != null) {
      /* Change any occurrence of '\' or '/' into the proper file separator. */
      if (!File.separator.equals("\\")) {
        prop = prop.replaceAll("[\\\\/]", File.separator);
      } else {
        prop = prop.replaceAll("[\\\\/]", "\\\\"); /* Must use escape char. */
      }

      return prop;
    } else {
      return prop;
    }
  }

  /** Display specified warning message with "WARN: " prepended using System.err.println (to STDERR).
      @param msg warning message to display
   */
  private static final void warn (CharSequence msg) { // since 3.1.3
    System.err.println("WARN: "+msg);
  }

  /**
   * Gets the default machine architecture type (32-bit, 64-bit) to use.
   * This is a best guest for current machine architecture type and defaults to "32" for unknown machine architecture types.
   * It first uses the SUN System property "sun.arch.data.model" and if that is not available,
   * then tries the "os.arch" property with our own mapping for os.arch string to "32" or "64".
   * @return "64" for 64-bit machine architecture or
   *         "32" for 32-bit and unknown machine architectures
   * @since NeXtMidas 3.6.0 (copied from nxm.sys.lib.Shell.getDefArchType())
   */
  static String getArchType () {
    // The following property is likely to exist with Sun/Oracle versions of Java
    String bits = System.getProperty("sun.arch.data.model");

    if (bits == null || bits.length() == 0 || bits.equalsIgnoreCase("unknown")) {
      String arch = System.getProperty("os.arch");
      // may add more to this list in the future
      if      (arch.equals("i386"  )) bits = "32";
      else if (arch.equals("x86"   )) bits = "32";
      else if (arch.equals("ppc"   )) bits = "32";
      else if (arch.equals("arm"   )) bits = "32";
      else if (arch.equals("sparc" )) bits = "64";
      else if (arch.equals("amd64" )) bits = "64";
      else if (arch.equals("alpha" )) bits = "64"; // remove this under Java 5
      else if (arch.equals("x86_64")) bits = "64"; // since 3.1.2 for BUG 2589
      else {
        warn("Could not determine if '" + arch + "' was 32-Bit or 64-Bit, assuming 32-Bit.");
        bits = "32";
      }
    }
    return bits;
  }

  /** Command-line interface that permits external scripts to read from the
      <code>build.props</code> file for the specified option tree (DEF=SYS).<br>
      <br>
      Usage:
      <pre>
        tools.BuildProps usage: [-p] PROPERTY [OPT [VNAME]]
          Gets the value of the PROPERTY from the build.props
          file of the OPT option tree (default is SYS).

          -p  Translates any path separators in the prop to
              that of the current system.

          If optional VNAME is specified, it will print out the property value in a
          "set VNAME=" syntax (used by the Windows shell scripts).
      </pre>

      @param args The command-line arguments.
   */
  public static void main (String[] args) {
    boolean error   = false;
    boolean convert = false;
    String  prop    = null;
    String  opt     = "sys";

    if ((args == null) || (args.length == 0)) {
      System.err.println("ERROR: No property specified.");
      error = true;

    } else if (args.length == 1) {
      prop    = args[0];

    } else if (args.length == 2) {
      if (args[0].equals("-p")) {
        convert = true;
        prop    = args[1];

      } else {
        prop    = args[0];
        opt     = args[1];
      }

    } else if (args.length == 3) {
      if (args[0].equals("-p")) {
        convert = true;
        prop    = args[1];
        opt     = args[2];
      } else {
        prop    = args[0];
        opt     = args[1];
        System.out.print("set " + args[2] + "=");
      }
    } else if ((args.length == 4) && (args[0].equals("-p"))) {
      convert = true;
      prop    = args[1];
      opt     = args[2];
      System.out.print("set " + args[3] + "=");
    } else {
      System.err.println("ERROR: Invalid number of arguments.");
      error = true;
    }

    if (!error) {
      String value;
      if (convert) {
        value = BuildProps.getPathFor(opt, prop);
      } else {
//      value = BuildProps.getPropFor(opt, prop); // orig prior to 3.6.0
        value = BuildProps.getPropsFor(opt).getProperty(prop, PREPEND_SUFFIXES, APPEND_SUFFIXES); // since 3.6.0 include *suffixes
      }
      System.out.println(value);

    } else {
      System.err.println();
      System.err.println("tools.BuildProps usage: [-p] PROPERTY [OPT [VNAME]]");
      System.err.println("  Gets the value of the <PROPERTY> from the build.props");
      System.err.println("  file of the <OPT> option tree (default is SYS).");
      System.err.println();
      System.err.println("  -p  Translates any path separators in the prop to");
      System.err.println("      that of the current system.");
      System.err.println("");
      System.err.println("      If optional VNAME is specified it will print out the");
      System.err.println("      property value in a 'set VNAME=' syntax (used by");
      System.err.println("      the Windows shell scripts).");

      System.exit(-1);
    }
  }
}
