/***
 * ASM Guide
 * Copyright (c) 2007 Eric Bruneton
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package app.sec2;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import org.objectweb.asm.ClassAdapter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.EmptyVisitor;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.SimpleVerifier;

import ch3.sec2.AddTimerAdapter;
import ch3.sec2.RemoveGetFieldPutFieldAdapter;
import ch6.sec1.AddTimerTransformer;
import ch6.sec1.RemoveGetFieldPutFieldTransformer;

/**
 * ASM Guide example class.
 * 
 * @author Eric Bruneton
 */
public class Performances {

  public static void main(String[] args) throws Exception {
    ZipFile zip = new ZipFile(args[0]);
    String clazz = args.length > 1 ? args[1] : null;

    List classes = new ArrayList();
    Enumeration entries = zip.entries();
    while (entries.hasMoreElements()) {
      ZipEntry e = (ZipEntry) entries.nextElement();
      String s = e.getName();
      if (s.endsWith(".class")) {
        s = s.substring(0, s.length() - 6).replace('/', '.');
        if (clazz == null || s.indexOf(clazz) != -1) {
          InputStream is = zip.getInputStream(e);
          classes.add(new ClassReader(is).b);
        }
      }
    }

    long[] times = new long[15];
    for (int i = 0; i < times.length; ++i) {
      times[i] = Long.MAX_VALUE;
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        new ClassReader(b).accept(new EmptyVisitor(), 0);
      }
      t = System.currentTimeMillis() - t;
      times[0] = Math.min(t, times[0]);
      System.out.println("Time to deserialize " + classes.size()
          + " classes = " + t + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(0);
        new ClassReader(b).accept(cw, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[1] = Math.min(t, times[1]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size() + " classes = " + t + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassReader cr = new ClassReader(b);
        ClassWriter cw = new ClassWriter(cr, 0);
        cr.accept(cw, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[2] = Math.min(t, times[2]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size() + " classes with copyPool = " + t + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(0);
        ClassVisitor cv = new AddTimerAdapter(cw);
        new ClassReader(b).accept(cv, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[3] = Math.min(t, times[3]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size() + " classes with AddTimerAdapter = " + t
          + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassReader cr = new ClassReader(b);
        ClassWriter cw = new ClassWriter(cr, 0);
        ClassVisitor cv = new AddTimerAdapter(cw);
        cr.accept(cv, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[4] = Math.min(t, times[4]);
      System.out
          .println("Time to deserialize and reserialize "
              + classes.size()
              + " classes with AddTimerAdapter and copyPool = " + t
              + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(0);
        ClassVisitor cv = new ClassAdapter(cw) {
          public MethodVisitor visitMethod(int access, String name,
              String desc, String signature, String[] exceptions) {
            return new RemoveGetFieldPutFieldAdapter(cv.visitMethod(
                access, name, desc, signature, exceptions));
          }
        };
        new ClassReader(b).accept(cv, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[5] = Math.min(t, times[5]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size()
          + " classes with RemoveGetFieldPutFieldAdapter = " + t
          + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassReader cr = new ClassReader(b);
        ClassWriter cw = new ClassWriter(cr, 0);
        ClassVisitor cv = new ClassAdapter(cw) {
          public MethodVisitor visitMethod(int access, String name,
              String desc, String signature, String[] exceptions) {
            return new RemoveGetFieldPutFieldAdapter(cv.visitMethod(
                access, name, desc, signature, exceptions));
          }
        };
        cr.accept(cv, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[6] = Math.min(t, times[6]);
      System.out
          .println("Time to deserialize and reserialize "
              + classes.size()
              + " classes with RemoveGetFieldPutFieldAdapter and copyPool = "
              + t + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        new ClassReader(b).accept(cw, 0);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[7] = Math.min(t, times[7]);
      System.out
          .println("Time to deserialize and reserialize "
              + classes.size() + " classes with computeMaxs = " + t
              + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      int errors = 0;
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        try {
          new ClassReader(b).accept(cw, 0);
        } catch (Throwable e) {
          ++errors;
        }
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[8] = Math.min(t, times[8]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size() + " classes with computeFrames = " + t
          + " ms (" + errors + " errors)");
    }

    System.out.println();

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        new ClassReader(b).accept(new ClassNode(), 0);
      }
      t = System.currentTimeMillis() - t;
      times[9] = Math.min(t, times[9]);
      System.out.println("Time to deserialize " + classes.size()
          + " classes with tree package = " + t + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(0);
        ClassNode cn = new ClassNode();
        new ClassReader(b).accept(cn, 0);
        cn.accept(cw);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[10] = Math.min(t, times[10]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size() + " classes with tree package = " + t
          + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(0);
        ClassNode cn = new ClassNode();
        new ClassReader(b).accept(cn, 0);
        new AddTimerTransformer(null).transform(cn);
        cn.accept(cw);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[11] = Math.min(t, times[11]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size() + " classes with AddTimerTransformer = " + t
          + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size(); ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassWriter cw = new ClassWriter(0);
        ClassNode cn = new ClassNode();
        new ClassReader(b).accept(cn, 0);
        Iterator it = cn.methods.iterator();
        while (it.hasNext()) {
          MethodNode mn = (MethodNode) it.next();
          new RemoveGetFieldPutFieldTransformer(null).transform(mn);
        }
        cn.accept(cw);
        cw.toByteArray();
      }
      t = System.currentTimeMillis() - t;
      times[12] = Math.min(t, times[12]);
      System.out.println("Time to deserialize and reserialize "
          + classes.size()
          + " classes with RemoveGetFieldPutFieldTransformer = " + t
          + " ms");
    }

    for (int i = 0; i < 10; ++i) {
      int errors = 0;
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size() / 10; ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassReader cr = new ClassReader(b);
        ClassNode cn = new ClassNode();
        cr.accept(cn, 0);
        List methods = cn.methods;
        for (int k = 0; k < methods.size(); ++k) {
          MethodNode method = (MethodNode) methods.get(k);
          if (method.instructions.size() > 0) {
            Analyzer a = new Analyzer(new BasicInterpreter());
            try {
              a.analyze(cn.name, method);
            } catch (Throwable th) {
              ++errors;
            }
          }
        }
      }
      t = System.currentTimeMillis() - t;
      times[13] = Math.min(t, times[13]);
      System.out.println("Time to analyze " + classes.size() / 10
          + " classes with BasicInterpreter = " + t + " ms (" + errors
          + " errors)");
    }

    for (int i = 0; i < 10; ++i) {
      int errors = 0;
      long t = System.currentTimeMillis();
      for (int j = 0; j < classes.size() / 10; ++j) {
        byte[] b = (byte[]) classes.get(j);
        ClassReader cr = new ClassReader(b);
        ClassNode cn = new ClassNode();
        cr.accept(cn, 0);
        List methods = cn.methods;
        for (int k = 0; k < methods.size(); ++k) {
          MethodNode method = (MethodNode) methods.get(k);
          if (method.instructions.size() > 0) {
            Analyzer a = new Analyzer(new SimpleVerifier());
            try {
              a.analyze(cn.name, method);
            } catch (Throwable th) {
              ++errors;
            }
          }
        }
      }
      t = System.currentTimeMillis() - t;
      times[14] = Math.min(t, times[14]);
      System.out.println("Time to analyze " + classes.size() / 10
          + " classes with SimpleVerifier = " + t + " ms (" + errors
          + " errors)");
    }

    for (int i = 0; i < times.length; ++i) {
      System.out.println(times[i]);
    }
    System.out.println();

    double base = 100.0 / times[1];
    double read = times[0] * base;
    double write = (times[1] - times[0]) * base;
    System.out.println("base = 100 (including " + read + " for R, "
        + write + " for W)");
    System.out.println("add time = " + times[3] * base + " (including "
        + read + " for R, " + (times[3] - times[1]) * base + " for T, "
        + write + " for W)");
    System.out.println("remove seq = " + times[5] * base
        + " (including " + read + " for R, " + (times[5] - times[1])
        * base + " for T, " + write + " for W)");
    System.out.println();
    System.out.println("base' = " + times[2] * base);
    System.out.println("add time' = " + times[4] * base);
    System.out.println("remove seq' = " + times[6] * base);
    System.out.println();
    System.out.println("compute maxs = " + times[7] * base
        + " (including " + read + " for R, " + (times[7] - times[1])
        * base + " for C, " + write + " for W)");
    System.out.println("compute frames = " + times[8] * base
        + " (including " + read + " for R, " + (times[8] - times[1])
        * base + " for C, " + write + " for W)");
    double tread = times[9] * base;
    double twrite = (times[10] - times[9]) * base;
    System.out.println();
    System.out.println("tree base = " + times[10] * base
        + " (including " + tread + " for R, " + twrite + " for W)");
    System.out.println("tree add time = " + times[11] * base
        + " (including " + tread + " for R, " + (times[11] - times[10])
        * base + " for T, " + twrite + " for W)");
    System.out.println("tree remove seq = " + times[12] * base
        + " (including " + tread + " for R, " + (times[12] - times[10])
        * base + " for T, " + twrite + " for W)");
    System.out.println("basic interpreter = " + times[13] * 10 * base
        + " (including " + tread + " for R, "
        + (times[13] * 10 - times[10]) * base + " for C, " + twrite
        + " for W)");
    System.out.println("simple verifier = " + times[14] * 10 * base
        + " (including " + tread + " for R, "
        + (times[14] * 10 - times[10]) * base + " for C, " + twrite
        + " for W)");
  }
}
