/**************************************************************************************
 * Copyright (c) Jonas Bonér, Alexandre Vasseur. All rights reserved.                 *
 * http://aspectwerkz.codehaus.org                                                    *
 * ---------------------------------------------------------------------------------- *
 * The software in this package is published under the terms of the QPL license       *
 * a copy of which has been included with this distribution in the license.txt file.  *
 **************************************************************************************/
package test.xmldef.memusage;

import junit.framework.TestCase;
import junit.framework.TestSuite;

import java.net.URLClassLoader;
import java.net.URL;
import java.io.File;
import java.util.List;
import java.util.ArrayList;

import org.codehaus.aspectwerkz.hook.impl.WeavingClassLoader;
import org.apache.bcel.Constants;
import org.apache.bcel.generic.*;

/**
 * This test can create as many classes as needed.
 * Classes are dumped in _temp dir.
 * A single test loads classFactor classes and creates and invokes instanceFactor instances of each
 * Use CLASS_KSIZE and INSTANCE_KSIZE to adapt the object sizes.
 *
 * Eg: with 50 class and 10 instance with KSize of 10 for each this leads to
 * 50 * 10k + 50*10 * 10k = apprx 5.5 M
 *
 * If isClassCache and isInstanceCache are set, classes and instances cannot be GC.
 *
 * It is possible to run offline mode on the dumped class in _temp as well to check the GC usage.
 *
 * @author <a href="mailto:alex@gnilux.com">Alexandre Vasseur</a>
 */
public class MemUsageTest extends TestCase {

    private static boolean areClassWritten = false;

    /** a statci byte[] array for generated class to have a larger mem fooprint */
    private final static int CLASS_KSIZE = 10;

    /** a instance array field for generated class to have a larger mem fooprint */
    private final static int INSTANCE_KSIZE = 10;

    /** each generated class has this number of sayHello<XX>() instance methods */
    private final static int HELLO_METHOD_COUNT = 30;

    /** if set, created class are kept to avoid GC during the test */
    private boolean isClassCache = true;

    /** if set, created instances are kept to avoid GC during the test */
    private boolean isInstanceCache = true;

    /** number of class HelloXX created thru bcel for the test */
    private int classFactor;

    /** number of instance of each HelloXX class instanciated for the test */
    private int instanceFactor;

    /** class cache */
    private List classCache = new ArrayList();

    /** instance cache */
    private List instanceCache = new ArrayList();

    /**
     * Create a new test
     */
    public MemUsageTest(String s, int classFactor, int instanceFactor, boolean isClassCache, boolean isInstanceCache) {
        super(s);
        this.classFactor = classFactor;
        this.instanceFactor = instanceFactor;
        this.isClassCache = isClassCache;
        this.isInstanceCache = isInstanceCache;
    }

    /**
     * Creates a Hello implementation class file
     * @param dir where to store generated file
     * @param className
     */
    private void createClassFile(String dir, String className) {
        // class classNameXX implements Hello
        ClassGen cg = new ClassGen(className, "java.lang.Object",
                                     "<generated>", Constants.ACC_PUBLIC | Constants.ACC_SUPER,
                                     new String[]{Hello.class.getName()});
        ConstantPoolGen cp = cg.getConstantPool();
        InstructionFactory factory = new InstructionFactory(cg);

        // private byte[] buffer = new byte[XXX];
        FieldGen field = new FieldGen(Constants.ACC_PRIVATE, new ArrayType(Type.BYTE, 1), "buffer", cp);
        cg.addField(field.getField());

        // private static byte[] sbuffer = new byte[XXX];
        field = new FieldGen(Constants.ACC_PRIVATE | Constants.ACC_STATIC, new ArrayType(Type.BYTE, 1), "sbuffer", cp);
        cg.addField(field.getField());

        // <init> to initialize buffer field
        InstructionList il = new InstructionList();
        MethodGen method = new MethodGen(Constants.ACC_PUBLIC, Type.VOID, Type.NO_ARGS,
                new String[]{}, "<init>", className, il, cp);
        il.append(factory.createLoad(Type.OBJECT, 0));
        il.append(factory.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Constants.INVOKESPECIAL));
        il.append(factory.createLoad(Type.OBJECT, 0));
        il.append(new PUSH(cp, INSTANCE_KSIZE*1000));
        il.append(factory.createNewArray(Type.BYTE, (short) 1));
        il.append(factory.createFieldAccess(className, "buffer", new ArrayType(Type.BYTE, 1), Constants.PUTFIELD));
        il.append(factory.createReturn(Type.VOID));
        method.setMaxStack();
        method.setMaxLocals();
        cg.addMethod(method.getMethod());
        il.dispose();

        // <clinit> to initialize sbuffer field
        il = new InstructionList();
        method = new MethodGen(Constants.ACC_STATIC, Type.VOID, Type.NO_ARGS, new String[] {  }, "<clinit>", className, il, cp);
        il.append(new PUSH(cp, CLASS_KSIZE*1000));
        il.append(factory.createNewArray(Type.BYTE, (short) 1));
        il.append(factory.createFieldAccess(className, "sbuffer", new ArrayType(Type.BYTE, 1), Constants.PUTSTATIC));
        il.append(factory.createReturn(Type.VOID));
        method.setMaxStack();
        method.setMaxLocals();
        cg.addMethod(method.getMethod());
        il.dispose();

        // sayHello<XX> 0..()
        for (int i = 0; i < HELLO_METHOD_COUNT; i++) {
            method = new MethodGen(Constants.ACC_PUBLIC, // access flags
                                            Type.STRING,               // return type
                                            new Type[]{},     // arg type
                                            new String[]{}, // arg names
                                            "sayHello"+i, className,    // method, class
                                            il, cp);
            il.append(new PUSH(cp, "sayHello"+i));
            il.append(factory.createReturn(Type.STRING));
            method.setInstructionList(il);
            method.setMaxLocals();
            method.setMaxStack();
            cg.addMethod(method.getMethod());
            il.dispose();
        }

        // dump in dir
        try {
            cg.getJavaClass().dump(dir + File.separator + className.replace('.', File.separatorChar)+ ".class");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Assumes cl classloader has access to classPrefix + 1..classFactor classes
     * Call operation on them
     * Each call is a JUnit assertion
     */
    private void callClassesOneByOne(ClassLoader cl, String classPrefix) throws Exception {
        Class klass = null;
        Hello instance = null;
        for (int i = 1; i <= classFactor; i++) {
            klass = Class.forName(classPrefix+i, true, cl);
            if (isClassCache) classCache.add(klass);
            for (int j = 1; j <= instanceFactor; j++) {
                instance = (Hello) klass.newInstance();
                if (isInstanceCache) instanceCache.add(instance);
                for (int k = 0; k < HELLO_METHOD_COUNT; k++) {
                    //System.out.print(":");
                    assertEquals("before sayHello"+k+" after", klass.getMethod("sayHello"+k, new Class[]{}).invoke(instance, new Object[]{}));
                    ///*no aspect*/assertEquals("sayHello"+k, klass.getMethod("sayHello"+k, new Class[]{}).invoke(instance, new Object[]{}));
                }
            }
        }
    }

    /**
     * Continue calling class instances
     * Can be used with profiling tool to check memory fooprint
     */
    private void continueCalls() {
        if (!isInstanceCache) return;
        Hello instance = null;
        while (true) {
            for (int i = 0; i < instanceCache.size(); i++) {
                instance = (Hello) instanceCache.get(i);
                for (int k = 0; k < HELLO_METHOD_COUNT; k++) {
                    try {
                        assertEquals("before sayHello"+k+" after",
                            instance.getClass().getMethod("sayHello"+k, new Class[]{}).invoke(instance, new Object[]{}));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            try { Thread.sleep(200); System.out.print(".");} catch (Exception e) {;}
        }
    }

    /**
     * Release cached instances and classes
     */
    private void releaseCache() {
        while ( ! instanceCache.isEmpty()) {
            instanceCache.remove(0);
        }
        while ( ! classCache.isEmpty()) {
            classCache.remove(0);
        }
    }

    /**
     * Creates classFactor classes in dir whith name classPrefix + 1..classFactor
     */
    private void createClassFiles(String dir, String classPrefix) {
        for (int i = 1; i <= classFactor; i++)
            createClassFile(dir, classPrefix+i);
    }

    /**
     * Run with forced weaving thru custom classloader
     * -Daspectwerkz.definition.file=src\test\test-xmldef.xml -Daspectwerkz.transform.verbose=yes -Daspectwerkz.transform.dump=test..*
     */
    public void runThruWeavingClassLoader() throws Exception {
        ClassLoader cl = new WeavingClassLoader(new URL[]{(new File("_temp")).toURL()}, ClassLoader.getSystemClassLoader());
        createClassFiles("_temp", "atest");
        long ms = System.currentTimeMillis();
        callClassesOneByOne(cl, "atest");
        System.out.println("completed in: " + (System.currentTimeMillis() - ms));
        //continueCalls();// uncomment me if needed
        releaseCache();// uncomment me if needed
    }

    /**
     * Run as normal
     * Is weaved thru online mode
     */
    public void runThruStandardClassLoader() throws Exception {
        ClassLoader cl = new URLClassLoader(new URL[]{(new File("_temp")).toURL()}, ClassLoader.getSystemClassLoader());
        createClassFiles("_temp", "HelloClass");
        long ms = System.currentTimeMillis();
        callClassesOneByOne(cl, "HelloClass");
        System.out.println("completed in: " + (System.currentTimeMillis() - ms));
        releaseCache();
    }

    /**
     * Test hook
     */
    public void testLongRun() throws Exception {
        runThruStandardClassLoader();
    }

    public synchronized void setUp() {
        if ( ! areClassWritten ) {
            //System.out.println("creating");
            createClassFile("_temp", "HelloClass");
            areClassWritten = true;
            //System.out.println("created");
        }
    }

    public void tearDown() {
        //todo clean _temp
    }

    //-- junit hooks --//

    public static void main(String[] args) throws Throwable {
        //junit.textui.TestRunner.run(suite());

        // uncomment for inside IDE use
        MemUsageTest me = new MemUsageTest("test", 10, 20, true, true);
        me.createClassFiles("_temp", "HelloClass");
        me.runThruWeavingClassLoader();
    }

    public static junit.framework.Test suite() {
        TestSuite suite = new TestSuite();
        suite.addTest(new MemUsageTest("testLongRun", 10, 20, true, true));
        return suite;
    }


}
