public static void main(final String[] args) {
    String CLASSPATH = null;
    String outputDirName = null;
    String lookIn = null;

    // Parse the command line
    for (int i = 0; i < args.length; i++) {
      if (args[i].equals("-trace")) {
        BloatBenchmark.TRACE = true;
        PersistentBloatContext.DB_COMMIT = true;

      } else if (args[i].equals("-calleeSize")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No callee size specified");
          BloatBenchmark.usage();
        }

        try {
          BloatBenchmark.CALLEE_SIZE = Integer.parseInt(args[i]);

        } catch (final NumberFormatException ex33) {
          BloatBenchmark.err.println("** Bad number: " + args[i]);
          BloatBenchmark.usage();
        }

      } else if (args[i].startsWith("-classpath")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No classpath specified");
          BloatBenchmark.usage();
        }

        // If there is more than one -classpath append it to the
        // current one. That way the CLASSPATH reflects the order in
        // which the options came on the command line.
        if (CLASSPATH == null) {
          CLASSPATH = args[i];

        } else {
          CLASSPATH += File.pathSeparator + args[i];
        }

      } else if (args[i].equals("-no-stack-alloc")) {
        Main.STACK_ALLOC = false;

      } else if (args[i].equals("-peel-loops")) {
        if (++i >= args.length) {
          BloatBenchmark.usage();
        }

        final String n = args[i];

        if (n.equals("all")) {
          FlowGraph.PEEL_LOOPS_LEVEL = FlowGraph.PEEL_ALL_LOOPS;

        } else {
          try {
            FlowGraph.PEEL_LOOPS_LEVEL = Integer.parseInt(n);

            if (FlowGraph.PEEL_LOOPS_LEVEL < 0) {
              BloatBenchmark.usage();
            }
          } catch (final NumberFormatException ex) {
            BloatBenchmark.usage();
          }
        }
      } else if (args[i].equals("-no-color")) {
        Liveness.UNIQUE = true;

      } else if (args[i].equals("-no-dce")) {
        Main.DCE = false;

      } else if (args[i].equals("-no-prop")) {
        Main.PROP = false;

      } else if (args[i].equals("-no-pre")) {
        Main.PRE = false;

      } else if (args[i].equals("-no-check")) {
        BloatBenchmark.CHECK = false;

      } else if (args[i].equals("-depth")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No depth specified");
          BloatBenchmark.usage();
        }

        try {
          BloatBenchmark.DEPTH = Integer.parseInt(args[i]);

        } catch (final NumberFormatException ex33) {
          BloatBenchmark.err.println("** Bad number: " + args[i]);
          BloatBenchmark.usage();
        }

      } else if (args[i].equals("-inline")) {
        // Inline calls to static methods
        BloatBenchmark.INLINE = true;

      } else if (args[i].equals("-intra")) {
        BloatBenchmark.INTRA = true;

      } else if (args[i].equals("-lookIn")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No directory specified");
          BloatBenchmark.usage();
        }

        if (lookIn != null) {
          lookIn += File.pathSeparator + args[i];

        } else {
          lookIn = args[i];
        }

      } else if (args[i].equals("-morph")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No morphosity specified");
          BloatBenchmark.usage();
        }

        try {
          BloatBenchmark.MORPH = Integer.parseInt(args[i]);

        } catch (final NumberFormatException ex33) {
          BloatBenchmark.err.println("** Bad number: " + args[i]);
          BloatBenchmark.usage();
        }

      } else if (args[i].equals("-noinline")) {
        // Don't perform inlining, just specialize
        BloatBenchmark.INLINE = false;

      } else if (args[i].equals("-peephole")) {
        // Perform peephole optimizations when doing interprocedural
        // stuff
        BloatBenchmark.PEEPHOLE = true;

      } else if (args[i].equals("-size")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No size specified");
          BloatBenchmark.usage();
        }

        try {
          BloatBenchmark.SIZE = Integer.parseInt(args[i]);

        } catch (final NumberFormatException ex33) {
          BloatBenchmark.err.println("** Bad number: " + args[i]);
          BloatBenchmark.usage();
        }

      } else if (args[i].equals("-specialize")) {
        // Specialize virtual method call sites
        BloatBenchmark.SPECIALIZE = true;

      } else if (args[i].equals("-stats")) {
        if (++i >= args.length) {
          BloatBenchmark.err.println("** No stats file specified");
          BloatBenchmark.usage();
        }

        BloatBenchmark.statsFile = args[i];

      } else if (args[i].equals("-sun")) {
        // Optimize sun packages
        BloatBenchmark.SUN = true;

      } else if (args[i].equals("-times")) {
        BloatBenchmark.TIMES = true;

        if (++i >= args.length) {
          BloatBenchmark.err.println("** No times file specified");
          BloatBenchmark.usage();
        }

        BloatBenchmark.timesFile = args[i];

      } else if (args[i].equals("-no-verify")) {
        BloatBenchmark.VERIFY = false;

      } else if (args[i].equals("-no-opt-stack")) {
        CodeGenerator.OPT_STACK = false;

      } else if (args[i].equals("-no-stack-vars")) {
        Tree.USE_STACK = false;

      } else if (args[i].equals("-skip")) {
        if (++i >= args.length) {
          BloatBenchmark.usage();
        }

        String pkg = args[i];

        // Account for class file name on command line
        if (pkg.endsWith(".class")) {
          pkg = pkg.substring(0, pkg.lastIndexOf('.'));
        }

        BloatBenchmark.SKIP.add(pkg.replace('.', '/'));

      } else if (args[i].equals("-1.1")) {
        // There are some classes that we don't want to be pre-live.
        // They don't exist in JDK1.1.
        BloatBenchmark.USE1_1 = true;
        CallGraph.USE1_2 = false;

      } else if (args[i].equals("-1.2")) {
        CallGraph.USE1_2 = true;

        if (lookIn != null) {
          lookIn += File.separator + "1.2";
        }

      } else if (args[i].startsWith("-")) {
        BloatBenchmark.err.println("** Unrecognized option: " + args[i]);
        BloatBenchmark.usage();

      } else if (i == args.length - 1) {
        outputDirName = args[i];

      } else {
        BloatBenchmark.CLASSES.add(args[i]);
      }
    }

    if (BloatBenchmark.CLASSES.isEmpty()) {
      BloatBenchmark.err.println("** No classes specified");
      BloatBenchmark.usage();
    }

    if (outputDirName == null) {
      BloatBenchmark.err.println("** No output directory specified");
      BloatBenchmark.usage();
    }

    // Make sure the options the user entered make sense
    if (BloatBenchmark.CHECK) {
      BloatBenchmark.checkOptions();
    }

    if (BloatBenchmark.USE1_1) {
      // Don't generate stats for 1.1
      BloatBenchmark.statsFile = null;
    }

    if (lookIn != null) {
      CLASSPATH = lookIn + File.pathSeparator + CLASSPATH;
    }

    final StringBuffer sb = new StringBuffer();
    for (int i = 0; i < args.length; i++) {
      sb.append(args[i] + " ");
    }
    BloatBenchmark.tr("BLOATing with command line: " + sb);

    BloatContext context = null;

    float systemStart = 0.0F;
    float systemDelta = 0.0F;
    float systemEnd = 0.0F;
    float systemTotal = 0.0F;

    float userStart = 0.0F;
    float userDelta = 0.0F;
    float userEnd = 0.0F;
    float userTotal = 0.0F;

    PrintWriter times = null;

    if (BloatBenchmark.TIMES) {
      try {
        times = new PrintWriter(new FileWriter(BloatBenchmark.timesFile), true);

      } catch (final IOException ex) {
        times = new PrintWriter(System.out, true);
      }
    }

    if (BloatBenchmark.INTRA) {
      BloatBenchmark.tr("Intraprocedural BLOAT");

      // First compute the roots of the call graph. Figure out which
      // methods are live.
      context = BloatBenchmark.makeContext(CLASSPATH, null);
      final Collection liveMethods = BloatBenchmark.liveMethods(BloatBenchmark.CLASSES, context);

      // Run intraprocedural BLOAT on the live methods.
      BloatBenchmark.tr(liveMethods.size() + " live methods");
      context = BloatBenchmark.makeContext(CLASSPATH, outputDirName);
      BloatBenchmark.intraBloat(liveMethods, context);

    } else {
      BloatBenchmark.tr("Interprocedural BLOAT");

      if (BloatBenchmark.TIMES) {
        Times.snapshot();
        systemStart = Times.systemTime();
        userStart = Times.userTime();
      }

      // Do the interprocedural BLOATing
      context = BloatBenchmark.makeContext(CLASSPATH, outputDirName);
      BloatBenchmark.liveMethods(BloatBenchmark.CLASSES, context);

      if (BloatBenchmark.TIMES) {
        // Take a measurement
        Times.snapshot();

        systemEnd = Times.systemTime();
        userEnd = Times.userTime();

        systemDelta = systemEnd - systemStart;
        userDelta = userEnd - userStart;

        systemStart = systemEnd;
        userStart = userEnd;

        systemTotal += systemDelta;
        userTotal += userDelta;

        times.println("Call graph construction");
        times.println("  User: " + userDelta);
        times.println("  System: " + systemDelta);
      }

      if (BloatBenchmark.SPECIALIZE) {
        BloatBenchmark.specialize(context);
      }

      if (BloatBenchmark.TIMES) {
        // Take a measurement
        Times.snapshot();

        systemEnd = Times.systemTime();
        userEnd = Times.userTime();

        systemDelta = systemEnd - systemStart;
        userDelta = userEnd - userStart;

        systemStart = systemEnd;
        userStart = userEnd;

        systemTotal += systemDelta;
        userTotal += userDelta;

        times.println("Call site specialization");
        times.println("  User: " + userDelta);
        times.println("  System: " + systemDelta);
      }

      if (BloatBenchmark.INLINE) {
        BloatBenchmark.inline(context);
      }

      if (BloatBenchmark.TIMES) {
        // Take a measurement
        Times.snapshot();

        systemEnd = Times.systemTime();
        userEnd = Times.userTime();

        systemDelta = systemEnd - systemStart;
        userDelta = userEnd - userStart;

        systemStart = systemEnd;
        userStart = userEnd;

        systemTotal += systemDelta;
        userTotal += userDelta;

        times.println("Method inlining");
        times.println("  User: " + userDelta);
        times.println("  System: " + systemDelta);
      }

      if (BloatBenchmark.PEEPHOLE) {
        BloatBenchmark.peephole(context);
      }
    }

    // Commit dirty data
    BloatBenchmark.tr("Committing dirty methods");
    context.commitDirty();

    if (BloatBenchmark.TIMES) {
      // Take a measurement
      Times.snapshot();

      systemEnd = Times.systemTime();
      userEnd = Times.userTime();

      systemDelta = systemEnd - systemStart;
      userDelta = userEnd - userStart;

      systemStart = systemEnd;
      userStart = userEnd;

      systemTotal += systemDelta;
      userTotal += userDelta;

      times.println("Committal");
      times.println("  User: " + userDelta);
      times.println("  System: " + systemDelta);
    }

    if (BloatBenchmark.TIMES) {
      times.println("Total");
      times.println("  User: " + userTotal);
      times.println("  System: " + systemTotal);
    }

    if (BloatBenchmark.statsFile != null) {
      final InlineStats stats = context.getInlineStats();
      PrintWriter statsOut = null;
      try {
        statsOut = new PrintWriter(new FileWriter(BloatBenchmark.statsFile), true);

      } catch (final IOException ex) {
        statsOut = new PrintWriter(System.out, true);
      }

      stats.printSummary(statsOut);
    }

    BloatBenchmark.tr("Finished");
  }