/**
   * Performs intraprocedural BLOAT on a program's live methods.
   *
   * @param liveMethods Should be alphabetized. This way we can commit a class once we've BLOATed
   *     all of its methods.
   */
  private static void intraBloat(final Collection liveMethods, final BloatContext context) {

    ClassEditor prevClass = null;
    final Iterator iter = liveMethods.iterator();
    for (int count = 0; iter.hasNext(); count++) {
      MethodEditor live = null;
      ClassEditor ce = null; // Hack to make sure commit happens
      try {
        live = context.editMethod((MemberRef) iter.next());
        ce = context.editClass(live.declaringClass().classInfo());

      } catch (final NoSuchMethodException ex3) {
        BloatBenchmark.err.println("** Could not find method " + ex3.getMessage());
        System.exit(1);
      }

      /* So we can skip classes or packages */
      final String name = ce.type().className();
      final String qual = ce.type().qualifier() + "/*";
      boolean skip = false;
      for (int i = 0; i < BloatBenchmark.SKIP.size(); i++) {
        final String pkg = (String) BloatBenchmark.SKIP.get(i);

        if (name.equals(pkg) || qual.equals(pkg)) {
          skip = true;
          break;
        }
      }

      if (context.ignoreMethod(live.memberRef()) || skip) {
        // Don't display ignored methods, it's misleading.
        context.release(live.methodInfo());
        continue;
      }

      final Runtime runtime = Runtime.getRuntime();
      runtime.gc();

      final Date start = new Date();
      BloatBenchmark.tr(
          "  " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type());
      BloatBenchmark.tr("    Start: " + start);

      try {
        EDU.purdue.cs.bloat.optimize.Main.TRACE = BloatBenchmark.TRACE;
        if (!BloatBenchmark.VERIFY) {
          EDU.purdue.cs.bloat.optimize.Main.VERIFY = false;
        }
        EDU.purdue.cs.bloat.optimize.Main.bloatMethod(live, context);

      } catch (final Exception oops) {
        BloatBenchmark.err.println("******************************************");
        BloatBenchmark.err.println(
            "Exception while BLOATing "
                + live.declaringClass().name()
                + "."
                + live.name()
                + live.type());
        BloatBenchmark.err.println(oops.getMessage());
        oops.printStackTrace(System.err);
        BloatBenchmark.err.println("******************************************");
      }

      // Commit here in an attempt to conserve memory
      context.commit(live.methodInfo());
      context.release(live.methodInfo());

      if (prevClass == null) {
        prevClass = ce;

      } else if (!prevClass.equals(ce)) {
        // We've finished BLOATed the methods for prevClass, commit
        // prevClass and move on
        BloatBenchmark.tr(prevClass.type() + " != " + ce.type());
        context.commit(prevClass.classInfo());
        context.release(prevClass.classInfo());
        // context.commitDirty();
        // tr(context.toString());
        prevClass = ce;

      } else {
        context.release(ce.classInfo());
      }

      final Date end = new Date();
      BloatBenchmark.tr("    Ellapsed time: " + (end.getTime() - start.getTime()) + " ms");
    }

    context.commitDirty();
  }
  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");
  }