/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm;

import java.io.File;
import java.util.Arrays;
import org.jikesrvm.adaptive.controller.Controller;
import org.jikesrvm.classloader.RVMClassLoader;
import org.jikesrvm.compilers.baseline.BaselineCompiler;
import org.jikesrvm.compilers.baseline.BaselineOptions;
import org.jikesrvm.compilers.common.RuntimeCompiler;
import org.jikesrvm.mm.mminterface.MemoryManager;

import static org.jikesrvm.runtime.SysCall.sysCall;
import org.jikesrvm.scheduler.Scheduler;
import org.jikesrvm.scheduler.greenthreads.GreenScheduler;

/**
 * Command line option processing.
 *
 *      Arbitrary prefix support
 */
public class CommandLineArgs {
  private static final boolean DEBUG = false;

  /**
   * Argument types
   */
  private enum PrefixType {
    /**
     * Invalid argument type
     */
    INVALID_ARG, // default
    /**
     * Application argument
     */
    APPLICATION_ARG,

    // -----------------------------------------------//
    // The following arguments are standard java.     //
    // -----------------------------------------------//
    CLASSPATH_ARG,
    ENVIRONMENT_ARG,
    VERBOSE_JNI_ARG,
    VERBOSE_CLS_ARG,
    JAR_ARG,
    JAVAAGENT_ARG,
    ENABLE_ASSERTION_ARG,
    DISABLE_ASSERTION_ARG,

    // -----------------------------------------------//
    // The following arguments are RVM-specific.      //
    // -----------------------------------------------//
    HELP_ARG,
    ARG,
    IRC_HELP_ARG,
    IRC_ARG,
    RECOMP_HELP_ARG,
    RECOMP_ARG,
    AOS_HELP_ARG,
    AOS_ARG,
    BASE_HELP_ARG,
    BASE_ARG,
    OPT_ARG,
    OPT_HELP_ARG,
    /* Silently ignored */
    VERIFY_ARG,
    GC_HELP_ARG,
    GC_ARG,
    BOOTSTRAP_CLASSES_ARG,
    CPUAFFINITY_ARG,
    PROCESSORS_ARG
  }

  /** Represent a single command line prefix */
  private static final class Prefix implements Comparable<Prefix> {
    /** The command line string e.g. "-X:irc:" */
    public final String value;
    /** A number that describes the type of the argument */
    public final PrefixType type;
    /** Number of arguments of this type seen */
    public int count = 0;

    /** Construct a prefix with the given argument string and type */
    public Prefix(String v, PrefixType t) {
      value = v;
      type = t;
      if (t == null) {
        throw new Error("Type of prefix should never be null");
      }
    }

    /** Sorting method for Comparable. Sort by string value */
    public int compareTo(Prefix o) {
      return -value.compareTo(o.value);
    }
    /** Equals method to be consistent with Comparable */
    public boolean equals(Object o) {
      if (o instanceof Prefix) {
        return value.equals(((Prefix)o).value);
      }
      return false;
    }
    /** Hashcode to be consistent with Comparable */
    public int hashCode() {
      return value.hashCode();
    }
    /** Command line string representation of the prefix */
    public String toString() {
      return value;
    }
  }

  /**
   * A catch-all prefix to find application name.
   */
  private static final Prefix app_prefix = new Prefix("", PrefixType.APPLICATION_ARG);

  /**
   * A list of possible prefixes for command line arguments.
   * Each argument will be classified by the prefix it matches.
   * One prefix can contain another.
   *
   * Prefixes are normally matched with the start of the argument.
   * If the last character of the prefix is a '$', the prefix (without the
   * trailing '$') will be matched with the whole argument.
   * If the last character of the prefix is a ' ' (space), the prefix
   * (without the trailing ' ') will be matched with the whole argument,
   * and the next argument will be appended to the end of the one matching
   * the prefix, with a space in between.
   *
   * The type will be used to classify the prefix.  Multiple entries CAN
   * have the same type.
   */
  private static final Prefix[] prefixes = {new Prefix("-classpath ", PrefixType.CLASSPATH_ARG),
                                            // Note: space is significant
                                            new Prefix("-cp ", PrefixType.CLASSPATH_ARG),
                                            // Note: space is significant
                                            new Prefix("-jar ", PrefixType.JAR_ARG),
                                            // Note: space is significant
                                            new Prefix("-javaagent:", PrefixType.JAVAAGENT_ARG),
                                            new Prefix("-D", PrefixType.ENVIRONMENT_ARG),
                                            new Prefix("-verbose:class$", PrefixType.VERBOSE_CLS_ARG),
                                            new Prefix("-verbose:jni$", PrefixType.VERBOSE_JNI_ARG),
                                            new Prefix("-verbose$", PrefixType.VERBOSE_CLS_ARG),
                                            new Prefix("-enableassertions:", PrefixType.ENABLE_ASSERTION_ARG),
                                            new Prefix("-ea:", PrefixType.ENABLE_ASSERTION_ARG),
                                            new Prefix("-enableassertions", PrefixType.ENABLE_ASSERTION_ARG),
                                            new Prefix("-ea", PrefixType.ENABLE_ASSERTION_ARG),
                                            new Prefix("-disableassertions:", PrefixType.DISABLE_ASSERTION_ARG),
                                            new Prefix("-da:", PrefixType.DISABLE_ASSERTION_ARG),
                                            new Prefix("-disableassertions", PrefixType.DISABLE_ASSERTION_ARG),
                                            new Prefix("-da", PrefixType.DISABLE_ASSERTION_ARG),
                                            new Prefix("-X:vmClasses=", PrefixType.BOOTSTRAP_CLASSES_ARG),
                                            new Prefix("-X:cpuAffinity=", PrefixType.CPUAFFINITY_ARG),
                                            new Prefix("-X:processors=", PrefixType.PROCESSORS_ARG),
                                            new Prefix("-X:irc:help$", PrefixType.IRC_HELP_ARG),
                                            new Prefix("-X:irc$", PrefixType.IRC_HELP_ARG),
                                            new Prefix("-X:irc:", PrefixType.IRC_ARG),
                                            new Prefix("-X:recomp:help$", PrefixType.RECOMP_HELP_ARG),
                                            new Prefix("-X:recomp$", PrefixType.RECOMP_HELP_ARG),
                                            new Prefix("-X:recomp", PrefixType.RECOMP_ARG),
                                            new Prefix("-X:aos:help$", PrefixType.AOS_HELP_ARG),
                                            new Prefix("-X:aos$", PrefixType.AOS_HELP_ARG),
                                            new Prefix("-X:aos:", PrefixType.AOS_ARG),
                                            new Prefix("-X:gc:help$", PrefixType.GC_HELP_ARG),
                                            new Prefix("-X:gc$", PrefixType.GC_HELP_ARG),
                                            new Prefix("-X:gc:", PrefixType.GC_ARG),
                                            new Prefix("-X:base:help$", PrefixType.BASE_HELP_ARG),
                                            new Prefix("-X:base$", PrefixType.BASE_HELP_ARG),
                                            new Prefix("-X:base:", PrefixType.BASE_ARG),
                                            new Prefix("-X:opt:help$", PrefixType.OPT_HELP_ARG),
                                            new Prefix("-X:opt$", PrefixType.OPT_HELP_ARG),
                                            new Prefix("-X:opt:", PrefixType.OPT_ARG),
                                            new Prefix("-X:vm:help$", PrefixType.HELP_ARG),
                                            new Prefix("-X:vm$", PrefixType.HELP_ARG),
                                            new Prefix("-X:vm:", PrefixType.ARG),

                                            /* Silently ignored */
                                            new Prefix("-Xverify", PrefixType.VERIFY_ARG),

                                            app_prefix};

  static {
    Arrays.sort(prefixes);
    if (DEBUG) {
      for (int i = 0; i < prefixes.length; i++) {
        Prefix t = prefixes[i];
        VM.sysWrite("Prefix[" + i + "]: \"" + t.value + "\"; " + t.type + "\n");
      }
    }
  }

  /**
   * The command line arguments.
   */
  private static String[] args;
  /**
   * The types of each command line argument.
   */
  private static PrefixType[] arg_types;
  /**
   * The position of application class name.
   */
  private static int app_name_pos = -1;

  /**
   * Fetch arguments from program command line.
   */
  static void fetchCommandLineArguments() {
    if (args != null) {
      // if already been here...
      return;
    }
    ArgReader argRdr = new ArgReader();

    int numArgs = argRdr.numArgs();
    args = new String[numArgs];
    arg_types = new PrefixType[numArgs];

    for (int i = 0; i < numArgs; ++i) {
      String arg = argRdr.getArg(i);

      if (app_prefix.count > 0) {
        /* We're already into the application arguments.  Here's another
         * one. */
        args[i] = arg;
        arg_types[i] = PrefixType.APPLICATION_ARG;
        app_prefix.count++;
        continue;
      }

      // Note: This loop will never run to the end.
      for (Prefix p : prefixes) {
        String v = p.value;
        if (!matches(arg, v)) {
          continue;
        }
        // Chop off the prefix (which we've already matched) and store the
        // value portion of the string (the unique part) in args[i].  Store
        // information about the prefix itself in arg_types[i].
        args[i] = arg.substring(length(v));
        if (DEBUG) {
          VM.sysWrite("length(v) = ");
          VM.sysWrite(length(v));

          VM.sysWrite("; v = \"");
          VM.sysWrite(v);
          VM.sysWriteln("\"");
          VM.sysWrite("args[");
          VM.sysWrite(i);
          VM.sysWrite("] = \"");
          VM.sysWrite(args[i]);
          VM.sysWrite("\"; arg = \"");
          VM.sysWrite(arg);
          VM.sysWriteln("\"");
        }

        arg_types[i] = p.type;
        p = findPrefix(p.type); // Find the canonical prefix for this type...
        p.count++;              // And increment the usage count for that
        // canonical prefix.
        if (v.endsWith(" ")) {
          if (++i >= numArgs) {
            VM.sysWriteln("vm: ", v, "needs an argument");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          args[i - 1] += argRdr.getArg(i);
          args[i] = null;
        }
        if (p == app_prefix) {
          app_name_pos = i;
        }
        break;
      }
    } // for (i = 0; i < numArgs...)
    /*
     * If no application is specified, set app_name_pos to numArgs (that is,
     * to one past the last item in the array of arguments) to ensure all
     * command-line arguments are processed.
     */
    if (app_name_pos == -1) {
      app_name_pos = numArgs;
    }
  }

  /**
   * Does the argument match the prefix?
   * @param arg argument
   * @param p prefix
   * @return true if argument "matches" the prefix, false otherwise
   */
  private static boolean matches(String arg, String p) {
    if (p.endsWith(" ")) {
      return arg.equals(p.substring(0, p.length() - 1)) || arg.startsWith(p);
    }
    if (p.endsWith("$")) {
      return arg.equals(p.substring(0, p.length() - 1));
    }
    return arg.startsWith(p);
  }

  /**
   * The real length of the prefix.
   * @param p prefix
   * @return real length of prefix
   */
  private static int length(String p) {
    if (p.endsWith("$") || p.endsWith(" ")) return p.length() - 1;
    return p.length();
  }

  /**
   * Find a Prefix object of a given type.
   */
  private static Prefix findPrefix(PrefixType type) {
    for (Prefix prefix : prefixes) if (prefix.type == type) return prefix;
    return null;
  }

  /**
   * Extract all command line arguments of a particular type.
   * Strips out the prefixes (if any).
   * !!TODO: cache results instead of constructing a new array each time.
   * @param prefix type of arguments to extract
   * @return array of arguments or null if type is invalid
   */
  public static String[] getArgs(PrefixType prefix) {
    String[] retarg = null;
    Prefix p = findPrefix(prefix);
    if (p != null) {
      retarg = new String[p.count];
      for (int i = 0, j = 0; i < args.length; i++) {
        if (arg_types[i] == prefix) {
          retarg[j++] = args[i];
        }
      }
    }
    return retarg;
  }

  /**
   * Extract command line arguments for the Java agent
   * @return Java agent arguments
   */
  public static String[] getJavaAgentArgs() {
    return CommandLineArgs.getArgs(CommandLineArgs.PrefixType.JAVAAGENT_ARG);
  }

  /**
   * Get all environment arguments as pairs of string of key followed by value
   */
  public static String[] getEnvironmentArgs() {
    if (!VM.runningVM) throw new IllegalAccessError("Environment variables can't be read in a non-running VM");
    return getArgs(PrefixType.ENVIRONMENT_ARG);
  }

  /**
   * Extract the first -D... command line argument that matches a given
   * variable, and return it.
   * @return the environment arg, or null if there is none.
   */
  public static String getEnvironmentArg(String variable) {
    if (!VM.runningVM) throw new IllegalAccessError("Environment variables can't be read in a non-running VM");
    String[] allEnvArgs = getArgs(PrefixType.ENVIRONMENT_ARG);
    String prefix = variable + "=";
    if (allEnvArgs != null) {
      for (String allEnvArg : allEnvArgs) {
        if (allEnvArg.startsWith(prefix)) {
          return allEnvArg.substring(variable.length() + 1);
        }
      }
    }

    // There are some that we treat specially.
    if (variable.equals("java.home")) {
      return getRvmRoot();
    } else if (variable.equals("gnu.classpath.home.url")) {
      return "file:" + getRvmRoot();
    } else if (variable.equals("gnu.classpath.vm.shortname")) {
      return "JikesRVM";
    } else if (variable.equals("user.home")) {
      return getUserHome();
    } else if (variable.equals("user.dir")) {
      return getCWD();
    } else if (variable.equals("os.name")) {
      return getOsName();
    } else if (variable.equals("os.version")) {
      return getOsVersion();
    } else if (variable.equals("os.arch")) {
      return getOsArch();
    }
    // Ok, didn't find it.
    return null;
  }

  private static String getRvmRoot() {
    return null;
  }

  private static String getUserHome() {
    return null;
  }

  private static String getCWD() {
    return null;
  }

  private static String getOsName() {
    return null;
  }

  private static String getOsVersion() {
    return null;
  }

  private static String getOsArch() {
    return null;
  }

  /**
   * Extract the -X:vmClasses command line argument and return it.
   * @return null if no such command line argument is given.
   */
  static String getBootstrapClasses() {
    String[] vmClassesAll = getArgs(PrefixType.BOOTSTRAP_CLASSES_ARG);
    String vmClasses = null;
    // could be specified multiple times, use last specification
    if (vmClassesAll.length > 0) {
      vmClasses = vmClassesAll[vmClassesAll.length - 1];
    }
    return vmClasses;
  }

  /**
   * Stage1 processing of virtual machine directives appearing in argument list.
   * We try to process as many classes of command line arguments as possible here.
   * Only those command line arguments that require a more or less
   * fully booted VM to handle are delayed until lateProcessCommandLineArguments.
   */
  static void earlyProcessCommandLineArguments() {
    for (int i = 0; i < app_name_pos; i++) {
      String arg = args[i];
      PrefixType type = arg_types[i];
      if (type == PrefixType.INVALID_ARG) continue;
      Prefix p = findPrefix(type);
      if (DEBUG) VM.sysWriteln(" CommandLineArgs.earlyProcessCLA(" + p + arg + " - " + type + ")");
      switch (type) {

        case CLASSPATH_ARG:
          // arguments of the form "-classpath a:b:c" or "-cp a:b:c"
          // We are experimentally processing this early so that we can have the
          // Application class loader complete for when
          // ClassLoader$StaticData's initializer is run.
          RVMClassLoader.stashApplicationRepositories(arg);
          i++; // skip second argument to classpath
          break;

        case JAR_ARG:
          // maybe also load classes on the classpath list in the manifest
          RVMClassLoader.stashApplicationRepositories(arg);
          i++; // skip second argument to jar
          break;

        case ENABLE_ASSERTION_ARG:
          // arguments of the form "-ea[:<packagename>...|:<classname>]"
          RVMClassLoader.stashEnableAssertionArg(arg);
          break;

        case DISABLE_ASSERTION_ARG:
          // arguments of the form "-da[:<packagename>...|:<classname>]"
          RVMClassLoader.stashDisableAssertionArg(arg);
          break;

        case VERBOSE_CLS_ARG:
          VM.verboseClassLoading = true;
          break;

        case VERBOSE_JNI_ARG:
          VM.verboseJNI = true;
          break;

          // -------------------------------------------------//
          // Options needed by Scheduler to boot correctly //
          // -------------------------------------------------//
        case CPUAFFINITY_ARG:
          int cpuAffinity = -1;
          try { cpuAffinity = primitiveParseInt(arg); } catch (NumberFormatException e) {}
          if (cpuAffinity < 0) {
            VM.sysWriteln("vm: ", p.value, " needs a cpu number (0..N-1), but found '", arg, "'");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          Scheduler.cpuAffinity = cpuAffinity;
          break;

        case PROCESSORS_ARG: // "-X:processors=<n>" or "-X:processors=all"
          int nProcs;
          if (arg.equals("all")) {
            /* Assume sysCall.sysNumProcessors() always returns a sane
          value.  */
            nProcs = sysCall.sysNumProcessors();
          } else {
            nProcs = primitiveParseInt(arg);
          }
          if (nProcs < 1 || nProcs > (GreenScheduler.MAX_PROCESSORS - 1)) {
            VM.sysWrite("vm: ", p.value, " needs an argument between 1 and ");
            VM.sysWriteln(GreenScheduler.MAX_PROCESSORS - 1, " (inclusive), but found ", arg);
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          GreenScheduler.numProcessors = nProcs;
          break;

          // -------------------------------------------------------------------
          // GC options
          // -------------------------------------------------------------------
        case GC_HELP_ARG:  // -X:gc passed 'help' as an option
          MemoryManager.processCommandLineArg("help");
          break;
        case GC_ARG: // "-X:gc:arg" pass 'arg' as an option
          MemoryManager.processCommandLineArg(arg);
          break;

          // ----------------------------------------------------
          // Access initial runtime compiler (may be baseline or optimizing).
          // ----------------------------------------------------
        case IRC_HELP_ARG:
          RuntimeCompiler.processCommandLineArg("-X:irc:", "help");
          break;
        case IRC_ARG: // "-X:irc:arg"; pass 'arg' as an option
          RuntimeCompiler.processCommandLineArg("-X:irc:", arg);
          break;

          // --------------------------------------------------------------------
          // Access adaptive system's recompilation compilers
          // Currently this means the opt compiler, but in general we could be
          // talking to several different compilers used by AOS for recompilation.
          // --------------------------------------------------------------------
        case RECOMP_HELP_ARG:
          if (VM.BuildForAdaptiveSystem) {
            Controller.addOptCompilerOption("opt:help");
          } else {
            VM.sysWriteln("vm: nonadaptive configuration; -X:recomp is not a legal prefix in this configuration");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;
        case RECOMP_ARG:
          // "-X:recomp[?]:arg" process as 'opt[?]:arg' to opt
          // Note arg actually includes the optional opt level and :
          if (VM.BuildForAdaptiveSystem) {
            Controller.addOptCompilerOption("opt" + arg);
          } else {
            VM.sysWriteln("vm: nonadaptive configuration; -X:recomp is not a legal prefix in this configuration");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;

          // -------------------------------------------------------------------
          // Access adaptive optimization system
          // -------------------------------------------------------------------
        case AOS_HELP_ARG:  // -X:aos passed 'help' as an option
          if (VM.BuildForAdaptiveSystem) {
            Controller.processCommandLineArg("help");
          } else {
            VM.sysWrite("vm: nonadaptive configuration; -X:aos is not a legal prefix in this configuration\n");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;
        case AOS_ARG: // "-X:aos:arg" pass 'arg' as an option
          if (VM.BuildForAdaptiveSystem) {
            Controller.processCommandLineArg(arg);
          } else {
            VM.sysWrite("vm: nonadaptive configuration; -X:aos is not a legal prefix in this configuration\n");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;

          // ----------------------------------------------------
          // Access baseline compiler
          // ----------------------------------------------------
        case BASE_HELP_ARG:
          BaselineOptions.printHelp("-X:base:");
          break;
        case BASE_ARG: // "-X:base:arg"; pass 'arg' as an option
          BaselineCompiler.processCommandLineArg(p.value, arg);
          break;

          // ----------------------------------------------------
          // Access all 'logical' optimizing compilers
          // (both irc and recomp compilers)
          // ----------------------------------------------------
        case OPT_HELP_ARG:
          if (VM.BuildForAdaptiveSystem) {
            RuntimeCompiler.processOptCommandLineArg("-X:opt:", "help");
          } else {
            VM.sysWriteln("vm: You are not using a system that includes the optimizing compiler.");
            VM.sysWriteln("  Illegal command line argument prefix '-X:opt'");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;
        case OPT_ARG: // "-X:opt:arg"; pass 'arg' as an option
          if (VM.BuildForAdaptiveSystem) {
            RuntimeCompiler.processOptCommandLineArg("-X:opt:", arg);
            Controller.addOptCompilerOption("opt:" + arg);
          } else {
            VM.sysWriteln("vm: You are not using a system that includes the optimizing compiler.");
            VM.sysWriteln("  Illegal command line argument prefix '-X:opt'");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;

          // -------------------------------------------------------------------
          // Other arguments to the core VM
          // -------------------------------------------------------------------
        case HELP_ARG:  // -X:vm passed 'help' as an option
          Options.printHelp();
          break;
        case ARG: // "-X:vm:arg" pass 'arg' as an option
          if (!Options.process(arg)) {
            VM.sysWriteln("Unrecognized command line argument ", p.value, arg);
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          break;
      }
    }
  }

  /**
   * Stage2 processing of virtual machine directives appearing in argument list.
   * This function is responsible for processing the few
   * command line arguments that need to be handled late in booting.
   * It also returns the application's command line arguments.
   *
   * @return application arguments (first is application class name)
   * If no application arguments are specified on the command line,
   * process commands anyway.
   */
  static String[] lateProcessCommandLineArguments() {
    for (int i = 0; i < app_name_pos; i++) {
      String arg = args[i];
      PrefixType type = arg_types[i];
      if (type == PrefixType.INVALID_ARG) continue;
      Prefix p = findPrefix(type);
      if (DEBUG) VM.sysWriteln(" CommandLineArgs.processCLA(" + p + arg + " - " + type + ")");
      switch (type) {
        case ENVIRONMENT_ARG: // arguments of the form "-Dx=y"
        {
          int mid = arg.indexOf('=');
          if (mid == -1 || mid + 1 == arg.length()) {
            VM.sysWriteln("vm: bad property setting: \"", arg, "\"");
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          String name = arg.substring(0, mid);
          String value = arg.substring(mid + 1);
          System.getProperties().put(name, value);
        }
        break;

        case CLASSPATH_ARG:   // This is run in duplicate.
          // arguments of the form "-classpath a:b:c" or "-cp a:b:c"
          RVMClassLoader.setApplicationRepositories(arg);
          i++; // skip second argument to classpath
          break;

        case JAR_ARG:             // XXX This WILL BECOME  the second half of
          // handling JAR_ARG.  TODO
          // arguments of the form -jar <jarfile>
          java.util.jar.Manifest mf = null;
          try {
            java.util.jar.JarFile jf = new java.util.jar.JarFile(arg);
            mf = jf.getManifest();
          } catch (Exception e) {
            VM.sysWriteln("vm: IO Exception opening JAR file ", arg, ": ", e.getMessage());
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          if (mf == null) {
            VM.sysWriteln("The jar file is missing the manifest entry for the main class: ", arg);
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          String s = mf.getMainAttributes().getValue("Main-Class");
          if (s == null) {
            VM.sysWriteln("The jar file is missing the manifest entry for the main class: ", arg);
            VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
          }
          // maybe also load classes on the classpath list in the manifest
          RVMClassLoader.setApplicationRepositories(arg);

          args[i] = s;
          arg_types[i] = PrefixType.APPLICATION_ARG;
          app_prefix.count++;
          i++; // skip second argument to classpath
          break;
        case JAVAAGENT_ARG:
          /* Extract jar file from the -javaagent:<jar>[=options] form */
          int equalsPos = arg.indexOf("=");
          String jarPath;
          if (equalsPos != -1) {
            jarPath = arg.substring(0, equalsPos);
          } else {
            jarPath = arg;
          }
          String newClassPath = RVMClassLoader.getApplicationRepositories() + File.pathSeparator + jarPath;
          RVMClassLoader.setApplicationRepositories(newClassPath);
          break;
      }
    }

    // get application directives
    String[] arglist = getArgs(PrefixType.APPLICATION_ARG);

    // Debugging: write out application arguments
    if (DEBUG) {
      VM.sysWrite("VM.CommandLineArgs(): application arguments " + arglist.length + "\n");
      for (int i = 0; i < arglist.length; i++) {
        VM.sysWrite(i + ": \"" + arglist[i] + "\"\n");
      }
    }

    return arglist;
  }

  /**
   * Read the <code>argno</code>'th command line argument from the C argv
   * @param argno Number of argument sought
   * @param buf   Buffer to fill
   * @return number of bytes placed in buffer. -1 means buffer too small
   *         for argument to fit)
   */
  private static int sysArg(int argno, byte[] buf) {
    return sysCall.sysArg(argno, buf, buf.length);
  }

  /**
   * Primitive parsing of float/double values.
   * Done this way to enable us to parse command line arguments
   * early in VM booting before we are able to do the JNI call
   * that using Double.valueOf would require.
   * Does not support the full Java spec.
   */
  public static float primitiveParseFloat(String arg) {
    byte[] b = stringToBytes("floating point", arg);
    return sysCall.sysPrimitiveParseFloat(b);
  }

  /**
   * Primitive parsing of byte/integer numeric values.
   * Done this way to enable us to parse command line arguments
   * early in VM booting before we are able call
   * Byte.parseByte or Integer.parseInt.
   */
  public static int primitiveParseInt(String arg) {
    byte[] b = stringToBytes("integer or byte", arg);
    return sysCall.sysPrimitiveParseInt(b);
  }

  /**
   * Primitive parsing of memory sizes, with proper error handling,
   * and so on.
   * Works without needing Byte.parseByte or Integer.parseInt().
   *
   * At the moment, we have a maximum limit of an unsigned integer.  If
   *
   * @return Negative values on error.
   *      Otherwise, positive or zero values as bytes.
   * */
  public static long parseMemorySize(String sizeName, String sizeFlag, String defaultFactor, int roundTo,
                                     String fullArg, String subArg) {
    return sysCall.sysParseMemorySize(s2b(sizeName),
                                      s2b(sizeFlag),
                                      s2b(defaultFactor),
                                      roundTo,
                                      s2b(fullArg),
                                      s2b(subArg));
  }

  private static final class ArgReader {
    //    int buflen = 10;              // for testing; small enough to force
    // reallocation really soon.
    int buflen = 512;

    byte[] buf;                 // gets freed with the class instance.

    ArgReader() {
      buf = new byte[buflen];
    }

    /** Read argument # @param i
     * Assume arguments are encoded in the platform's
     * "default character set". */
    @SuppressWarnings({"deprecation"})
    String getArg(int i) {
      int cnt;
      for (; ;) {
        cnt = sysArg(i, buf);
        if (cnt >= 0) {
          break;
        }
        buflen += 1024;
        buf = new byte[buflen];
      }
      if (VM.VerifyAssertions) VM._assert(cnt != -1);
      /*
       * Implementation note: Do NOT use the line below, which uses the
       * three-argument constructor for String, the one that respects the native
       * encoding (the platform's "default character set").
       *
       * Instead, we use the four-argument constructor, the one that takes a
       * HIBYTE parameter.
       *
       * 1) It is safe to do this; we *know* that all of the legal command-line
       * args use only characters within the ASCII character set.
       *
       * 2) The "default character set" version below will break. That is
       * because GNU Classpath's implementation of the
       * three-argument-constructor will fail if EncodingManager.getDecoder()
       * returns a null pointer. And EncodingManager.getDecoder() returns a null
       * pointer if it's called early on in the boot process (which the
       * default-character-set version below does).
       */
      //      return new String(buf, 0, cnt);
      return new String(buf, 0, 0, cnt);
    }

    int numArgs() {
      return sysArg(-1, buf);
    }
  }

  /** Convenience method for calling stringToBytes */
  private static byte[] s2b(String arg) {
    return stringToBytes(null, arg);
  }

  /** Convert the string s (the "argument") to a null-terminated byte array.
   * This is used for converting arguments and for converting fixed
   * strings we pass down to lower commands.
   *
   * @return a byte array that represents <code>arg</code> as a
   * null-terminated C string.
   * Returns null for a null arg.
   *
   * @param arg the argument to convert
   * @param argName text to print for error reporting. */
  private static byte[] stringToBytes(String argName, String arg) {
    if (arg == null) {
      return null;
    }
    int len = arg.length();
    byte[] b = new byte[len + 1];

    for (int i = 0; i < len; i++) {
      char c = arg.charAt(i);
      if (c > 127) {
        VM.sysWrite("vm: Invalid character found in a");
        if (argName == null) {
          VM.sysWrite("n");
        } else {
          char v = argName.charAt(0);
          switch (v) {
            case'a':
            case'e':
            case'i':
            case'o':
            case'u':
              VM.sysWrite("n");
          }
          VM.sysWrite(" ", argName);
        }
        VM.sysWriteln(" argument: >", arg, "<");
        VM.sysExit(VM.EXIT_STATUS_BOGUS_COMMAND_LINE_ARG);
      }
      b[i] = (byte) c;
    }
    return b;
  }
}
