/** Checks to make sure that the chosen options make sense. */
  private static void checkOptions() {
    if (!BloatBenchmark.INTRA && !BloatBenchmark.SPECIALIZE && !BloatBenchmark.INLINE) {
      BloatBenchmark.err.println("** There is nothing to do!");
      BloatBenchmark.usage();

    } else if ((BloatBenchmark.MORPH != -1) && !BloatBenchmark.SPECIALIZE) {
      BloatBenchmark.err.println("** Must specialize when setting morphosity");
      BloatBenchmark.usage();
    }
  }
  /** Specializes the live methods in a program. */
  private static void specialize(final BloatContext context) {

    final CallGraph cg = context.getCallGraph();

    final Set liveMethods = new TreeSet(new MemberRefComparator());
    liveMethods.addAll(cg.liveMethods());

    // Specialize all possible methods
    final InlineStats stats = context.getInlineStats();

    if (BloatBenchmark.statsFile != null) {
      Specialize.STATS = true;
      stats.setConfigName("BloatBenchmark");
    }

    if (BloatBenchmark.MORPH != -1) {
      Specialize.MAX_MORPH = BloatBenchmark.MORPH;
    }
    final Specialize spec = new Specialize(context);

    if (Specialize.STATS) {
      stats.noteLiveMethods(liveMethods.size());
      stats.noteLiveClasses(cg.liveClasses().size());
    }

    BloatBenchmark.tr("Specializing live methods");
    final Iterator iter = liveMethods.iterator();

    for (int count = 0; iter.hasNext(); count++) {
      try {
        final MethodEditor live = context.editMethod((MemberRef) iter.next());

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

        BloatBenchmark.tr(
            "  " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type());

        spec.specialize(live);

      } catch (final NoSuchMethodException ex2) {
        BloatBenchmark.err.println("** Could not find method " + ex2.getMessage());
        System.exit(1);
      }
    }
  }
  /** Performs peephole optimizations on a program's live methods. */
  private static void peephole(final BloatContext context) {

    final Set liveMethods = new TreeSet(new MemberRefComparator());
    final CallGraph cg = context.getCallGraph();
    liveMethods.addAll(cg.liveMethods());

    // Perform peephole optimizations. We do this separately because
    // some peephole optimizations do things to the stack that
    // inlining doesn't like. For instance, a peephole optimizations
    // might make it so that a method has a non-empty stack upon
    // return. Inlining will barf at the sight of this.
    BloatBenchmark.tr("Performing peephole optimizations");

    final Iterator iter = liveMethods.iterator();
    while (BloatBenchmark.PEEPHOLE && iter.hasNext()) {
      try {
        final MethodEditor live = context.editMethod((MemberRef) iter.next());
        Peephole.transform(live);
        context.commit(live.methodInfo());
        context.release(live.methodInfo());

      } catch (final NoSuchMethodException ex314) {
        BloatBenchmark.err.println("** Could not find method " + ex314.getMessage());
        ex314.printStackTrace(System.err);
        System.exit(1);
      }
    }
  }
  /** Creates a <tt>BloatContext</tt> that loads classes from a given CLASSPATH. */
  static BloatContext makeContext(final String classpath, final String outputDirName) {
    final ClassFileLoader loader = new ClassFileLoader();
    if (classpath != null) {
      loader.prependClassPath(classpath);
    }

    // if(TRACE) {
    // loader.setVerbose(true);
    // }

    BloatBenchmark.tr("  Creating a BloatContext for CLASSPATH: " + loader.getClassPath());

    if (outputDirName != null) {
      loader.setOutputDir(new File(outputDirName));
    }
    final BloatContext context = new CachingBloatContext(loader, BloatBenchmark.CLASSES, true);

    // Always ignore the sun packages and the opj stuff for
    // interprocedural stuff
    if (!BloatBenchmark.SUN) {
      context.addIgnorePackage("sun");
    }

    context.addIgnorePackage("java.lang.ref");
    context.addIgnorePackage("org.opj.system");

    if (BloatBenchmark.USE1_1) {
      // Toba can't deal with java.lang.Character
      context.addIgnoreClass(Type.getType("Ljava/lang/Character;"));
    }

    return (context);
  }
  /** Inlines calls to static methods in the live methods of a given program. */
  private static void inline(final BloatContext context) {

    final Set liveMethods = new TreeSet(new MemberRefComparator());
    final CallGraph cg = context.getCallGraph();
    liveMethods.addAll(cg.liveMethods());

    BloatBenchmark.tr("Inlining " + liveMethods.size() + " live methods");

    if (BloatBenchmark.CALLEE_SIZE != -1) {
      Inline.CALLEE_SIZE = BloatBenchmark.CALLEE_SIZE;
    }

    final Iterator iter = liveMethods.iterator();
    for (int count = 0; BloatBenchmark.INLINE && iter.hasNext(); count++) {
      try {
        final MethodEditor live = context.editMethod((MemberRef) iter.next());

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

        BloatBenchmark.tr(
            "  " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type());

        final Inline inline = new Inline(context, BloatBenchmark.SIZE);
        inline.setMaxCallDepth(BloatBenchmark.DEPTH);
        inline.inline(live);

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

      } catch (final NoSuchMethodException ex3) {
        BloatBenchmark.err.println("** Could not find method " + ex3.getMessage());
        System.exit(1);
      }
    }
  }
  /**
   * Returns the live methods of a program whose root methods are the <tt>main</tt> method of a set
   * of classes.
   *
   * @param classes Names of classes containing root methods
   * @param context Repository for accessing BLOAT stuff
   * @return The <tt>MemberRef</tt>s of the live methods
   */
  private static Collection liveMethods(final Collection classes, final BloatContext context) {

    // Determine the roots of the call graph
    final Set roots = new HashSet();
    Iterator iter = classes.iterator();
    while (iter.hasNext()) {
      final String className = (String) iter.next();
      try {
        final ClassEditor ce = context.editClass(className);
        final MethodInfo[] methods = ce.methods();

        for (int i = 0; i < methods.length; i++) {
          final MethodEditor me = context.editMethod(methods[i]);

          if (!me.name().equals("main")) {
            continue;
          }

          BloatBenchmark.tr("  Root " + ce.name() + "." + me.name() + me.type());
          roots.add(me.memberRef());
        }

      } catch (final ClassNotFoundException ex1) {
        BloatBenchmark.err.println("** Could not find class: " + ex1.getMessage());
        System.exit(1);
      }
    }

    if (roots.isEmpty()) {
      BloatBenchmark.err.print("** No main method found in classes: ");
      iter = classes.iterator();
      while (iter.hasNext()) {
        final String name = (String) iter.next();
        BloatBenchmark.err.print(name);
        if (iter.hasNext()) {
          BloatBenchmark.err.print(", ");
        }
      }
      BloatBenchmark.err.println("");
    }

    context.setRootMethods(roots);
    final CallGraph cg = context.getCallGraph();

    final Set liveMethods = new TreeSet(new MemberRefComparator());
    liveMethods.addAll(cg.liveMethods());

    return (liveMethods);
  }
  /**
   * 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: "******"  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: "******"  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: "******"  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: "******"  System: " + systemDelta);
    }

    if (BloatBenchmark.TIMES) {
      times.println("Total");
      times.println("  User: "******"  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");
  }