/*
 * @(#)RebuildClassIUTest.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the 
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in 
 *  all copies or substantial portions of the Software. 
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL 
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.codecoverage.v2.compiler;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import net.sourceforge.groboutils.autodoc.v1.AutoDoc;
import net.sourceforge.groboutils.codecoverage.v2.ArrayClassLoader;
import net.sourceforge.groboutils.codecoverage.v2.BytecodeLoaderUtil;
import net.sourceforge.groboutils.util.io.v1.ReadByteStream;


/**
 * Attempts to load a classfile, disassemble it, modify the class file with
 * the logging, rebuild it to a new file, then load the new class file and
 * run it to ensure that everything's kosher with it.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @version   $Date: 2004/04/15 05:48:27 $
 * @since     December 19, 2002
 */
public class RebuildClassIUTest extends TestCase
{
    //-------------------------------------------------------------------------
    // Standard JUnit Class-specific declarations
    
    private static final Class THIS_CLASS = RebuildClassIUTest.class;
    private static final AutoDoc DOC = new AutoDoc( THIS_CLASS );
    
    public RebuildClassIUTest( String name )
    {
        super( name );
    }



    //-------------------------------------------------------------------------
    // Tests
    
    public static class MarkData
    {
        public String classSig;
        public short measureIndex;
        public short methodIndex;
        public short markIndex;
        
        public MarkData( String sig, short meaI, short metI, short marI )
        {
            this.classSig = sig;
            this.measureIndex = meaI;
            this.methodIndex = metI;
            this.markIndex = marI;
        }
        
        public String toString()
        {
            return "[Class Sig="+this.classSig+";measure="+
                this.measureIndex+";method="+this.methodIndex+
                ";mark="+this.markIndex+"]";
        }
    }
   
    public static class MyLogger
    {
        public static List marks = new ArrayList();
        
        
        public synchronized static void cover( String classSig,
                short methodIndex, short channel, short markIndex )
        {
            DOC.getLog().info( "Inside cover" );
            MarkData md = new MarkData( classSig, channel, methodIndex,
                markIndex );
            DOC.getLog().info( "adding mark: "+md );
            marks.add( md );
        }
        
        
        public synchronized static void reset()
        {
            marks.clear();
        }
    }
   
    public static class MyLogger2
    {
        public static void cover( String classSig,
                short methodIndex, short channel, short markIndex )
        {
            System.out.println( "MyLogger2.cover" );
            DOC.getLog().info( "!!cover!!" );
        }
    }
    
    
    private static final String COVER_METHOD_NAME = "cover";
    private static final String CLASSFILE_PATH =
        "net/sourceforge/groboutils/codecoverage/v2/compiler/testcode/";
    private static final String CLASSNAME_PACKAGE =
        "net.sourceforge.groboutils.codecoverage.v2.compiler.testcode.";
    private static final String MAIN_SIG = "main([Ljava/lang/String;)V";
    public void testRebuild1()
            throws Exception
    {
        final String classFileName = CLASSFILE_PATH+"Main1.class";
        final String className = CLASSNAME_PACKAGE+"Main1";
        byte[] origClassBytes = loadBytecode( classFileName );
//        ModifiedClass mc = new ModifiedClass( new ParseCoverageLogger(
//            Main1.class, COVER_METHOD_NAME ), classFileName, origClassBytes );
        ModifiedClass mc = new ModifiedClass( new ParseCoverageLogger(
            MyLogger.class, COVER_METHOD_NAME ), classFileName, origClassBytes );
        ModifiedMethod[] mmL = mc.getMethods();
        assertNotNull(
            "Returned null method list.",
            mmL );
        ModifiedMethod mm = null;
        for (int i = 0; i < mmL.length; ++i)
        {
            assertNotNull(
                "Method "+i+" is null.",
                mmL[i] );
            DOC.getLog().info("Method "+i+": "+mmL[i].getMethodName());
            if (mmL[i].getMethodName().equals( MAIN_SIG ))
            {
                mm = mmL[i];
            }
        }
        assertNotNull(
            "Did not find a main method.",
            mm );
        DOC.getLog().info( "Modifying method "+mm.getMethodName()+"." );
        ModifiedInstructionList mil = mm.getInstructionList();
        assertNotNull(
            "Null instruction list returned.",
            mil );
        DOC.getLog().info( "Instruction count: "+mil.getInstructionCount() );
        assertTrue(
            "Do not have any instructions to mark.",
            mil.getInstructionCount() > 0 );
        MarkedInstruction mi = mil.getInstructionAt( 0 );
        assertNotNull(
            "Null instruction returned.",
            mi );
        // set measure, mark, and modify method
        mi.addMark( (short)0, (short)1 );
        
        // recompile!!!!
        final byte[] newClassfile = mc.getModifiedClass();
        assertNotNull(
            "Returned null classfile array.",
            newClassfile );
        
        DOC.getLog().info( "Original Classfile:" );
        debugClass( origClassBytes, classFileName );
        DOC.getLog().info( "Recompiled Classfile:" );
        debugClass( newClassfile, classFileName );
        
        // clean-up
        mc = null;
        mi = null;
        mil = null;
        mm = null;
        mmL = null;
        
        // load new, modified class.
        ArrayClassLoader acl = new ArrayClassLoader();
        acl.addClass( className, newClassfile );
//        acl.addClass( className, origClassBytes );
        Class clazz = acl.loadClass( className );
        
        // run the class
        MyLogger.reset();
        runMain( clazz );
        
        // check results
        Iterator iter = MyLogger.marks.iterator();
        assertTrue(
            "Did not record any marks in MyLogger.",
            iter.hasNext() );
        MarkData md = (MarkData)iter.next();
        assertNotNull(
            "Returned null mark data entry.",
            md );
        DOC.getLog().info( "First mark is: "+md );
        assertEquals(
            "Did not set correct measure.",
            (short)0,
            md.measureIndex );
        assertEquals(
            "Did not set correct mark.",
            (short)1,
            md.markIndex );
    }
    
    
    
    
    //-------------------------------------------------------------------------
    // helpers
    
    
    protected byte[] loadBytecode( String classFileName )
            throws IOException
    {
        ClassLoader cl = this.getClass().getClassLoader();
        InputStream is = cl.getSystemResourceAsStream( classFileName );
        assertNotNull(
            "resource '"+classFileName+"' could not be found.",
            is );
        return ReadByteStream.readByteStream( is );
    }
    
    
    protected void runMain( Class cz )
            throws Exception
    {
        String s[] = new String[0];
        java.lang.reflect.Method m = cz.getMethod( "main",
            new Class[] { s.getClass() } );
        m.invoke( null, new Object[] { s } );
    }
    
    
    protected static void debugClass( byte[] classBytes, String filename )
            throws Exception
    {
        ByteArrayInputStream bais = new ByteArrayInputStream( classBytes );
        org.apache.bcel.classfile.ClassParser cp =
            new org.apache.bcel.classfile.ClassParser( bais, filename );
        org.apache.bcel.classfile.JavaClass origClass = cp.parse();
        String className = origClass.getClassName();
        org.apache.bcel.generic.ClassGen modClass =
            new org.apache.bcel.generic.ClassGen( origClass );
        org.apache.bcel.generic.ConstantPoolGen constantPool =
            modClass.getConstantPool();
        org.apache.bcel.classfile.Method mL[] = modClass.getMethods();
        
        DOC.getLog().debug( "-->> Class "+className+":" );
        /*
        DOC.getLog().debug( "  ClassGen ["+modClass+"]" );
        DOC.getLog().debug( "  Class Version ["+modClass.getMajor()+"."+
            modClass.getMinor()+"]" );
        DOC.getLog().debug( "  ConstantPoolGen:" );
        for (int i = 0; i < constantPool.getSize(); ++i)
        {
            DOC.getLog().debug( "     "+i+": ["+
                printConstant( constantPool.getConstant( i ) )+"]" ); 
        }
        */
        for (int i = 0; i < mL.length; ++i)
        {
            BytecodeLoaderUtil.verifyMethod( mL[i], constantPool );
            if (mL[i].getName().equals( "main" ))
            {
                int nameIndex = mL[i].getNameIndex();
                int sigIndex = mL[i].getSignatureIndex();
                DOC.getLog().debug( "   main name["+nameIndex+"] = ["+
                    printConstant(constantPool.getConstant( nameIndex ))+"]" );
                DOC.getLog().debug( "   main signature["+sigIndex+"] = ["+
                    printConstant(constantPool.getConstant( sigIndex ))+"]" );
                org.apache.bcel.classfile.Attribute attr[] =
                    mL[i].getCode().getAttributes();
                DOC.getLog().debug( "   code attributes:" );
                for (int j = 0; j < attr.length; ++j)
                {
                    nameIndex = attr[j].getNameIndex();
                    DOC.getLog().debug( "       "+
                        printConstant(constantPool.getConstant( nameIndex ))+
                        " ("+j+") ["+attr[j].getTag()+"] = ["+
                        attr[j]+"] ("+attr[j].getClass().getName()+")" );
                }
            }
            org.apache.bcel.generic.MethodGen mg =
                new org.apache.bcel.generic.MethodGen( mL[i], className,
                constantPool );
            DOC.getLog().debug( "  Method "+i+" ["+mg+"]" );
            /*
            org.apache.bcel.generic.InstructionList il =
                mg.getInstructionList();
            DOC.getLog().debug( "      Code [\n"+il+"]" );
            */
        }
        DOC.getLog().debug( "<<--" );
    }
    
    
    protected static String printConstant( org.apache.bcel.classfile.Constant c )
    {
        if (c == null)
        {
            return null;
        }
        StringBuffer sb = new StringBuffer( "Tag " );
        sb.append( c.getTag() ).
            append( '\'' ).append( c.toString() ).append( '\'' );
        return sb.toString();
    }
    
    
    
    //-------------------------------------------------------------------------
    // Standard JUnit declarations
    
    
    public static Test suite()
    {
        TestSuite suite = new TestSuite( THIS_CLASS );
        
        return suite;
    }
    
    public static void main( String[] args )
    {
        String[] name = { THIS_CLASS.getName() };
        
        // junit.textui.TestRunner.main( name );
        // junit.swingui.TestRunner.main( name );
        
        junit.textui.TestRunner.main( name );
    }
    
    
    /**
     * 
     * @exception Exception thrown under any exceptional condition.
     */
    protected void setUp() throws Exception
    {
        super.setUp();
        
        // set ourself up
    }
    
    
    /**
     * 
     * @exception Exception thrown under any exceptional condition.
     */
    protected void tearDown() throws Exception
    {
        // tear ourself down
        
        
        super.tearDown();
    }
}

