/*
 * Distributable under LGPL license. See terms of license at gnu.org.
 */

package org.jboss.profiler.jvmtitest;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;

import junit.framework.TestCase;

import org.jboss.profiler.jvmti.InventoryDataPoint;
import org.jboss.profiler.jvmti.JVMTICallBack;
import org.jboss.profiler.jvmti.JVMTIInterface;
import org.jboss.profiler.jvmti.ReferenceDataPoint;
import org.jboss.profiler.memoryprofiler.engine.MemorySnapshotController;
import org.jboss.profiler.memoryprofiler.engine.MemorySnapshotEngine;
import org.jboss.profiler.memoryprofiler.model.MemoryClass;

class B
{
}


class Dummy
{
	String a;
	String b;
	B b12 = null;
	String c;
	static B staticB ;
}
class A extends Dummy
{
	public A()
	{
		b = new B();
		staticB=b;
	}
	B b;
	B b1 = null;
	String str = null;
}

class Holder
{
    int count = 0;

    UsedByHolder[] objs = new UsedByHolder[100000];

    public void addInstance(UsedByHolder obj)
    {
        objs[count++] = obj;

    }

    public void print()
    {
        for (int i = 0; i < count; i++)
        {
            System.out.println("class=" + objs[i].toString());
        }

    }
}

class UsedByHolder
{
    int x;

    static int count = 0;

    public UsedByHolder()
    {
        x = count++;
    }

    public String toString()
    {
        return "value " + x;
    }
}

/**
 * To run this test, you need to use -agentlib:jbossAgent in you IDE
 * @author Clebert Suconic
 */

public class JVMTITest extends TestCase
{
	static class LocalCallbackTestOnClasses implements JVMTICallBack
	{
		int count=0;

		public void notifyReference(long referenceHolder, long referencedObject, long classTag, long index, long method, byte referenceType) {
			fail("Shouldn't be called");
		}

		public void notifyClass(long classTag, Class clazz) {
			count++;
		}

		public void notifyObject(long classTag, long objectId, long bytes) {
			fail("Shouldn't be called");
			
		}
	}

	static class LocalCallbackTestOnObjects implements JVMTICallBack
	{
		int count=0;

		public void notifyReference(long referenceHolder, long referencedObject, long classTag, long index, long method, byte referenceType) {
			fail("Shouldn't be called");
		}

		public void notifyClass(long classTag, Class clazz) {
			fail("Shouldn't be called");
		}

		public void notifyObject(long classTag, long objectId, long bytes) {
			count++;
		}
	}
	
	public void testNotifyOnClasses()
	{
		JVMTIInterface jvmti = new JVMTIInterface();
		LocalCallbackTestOnClasses localTest = new LocalCallbackTestOnClasses();
		jvmti.notifyInventory(true,null,null,localTest);
		
		assertEquals(jvmti.getLoadedClasses().length,localTest.count);
	}
	
	public void testInventoryReport() throws Exception
	{
		JVMTIInterface jvmti = new JVMTIInterface();
		System.out.println(jvmti.inventoryReport());
	}
  
  public void testProduceInventoryMinimal() throws Exception
  {
    JVMTIInterface jvmti = new JVMTIInterface(); // The JVMTIWrapper used to produce inventories
    
    Map map = jvmti.produceInventory();
    Map map2 = jvmti.produceInventory();
    assertTrue( jvmti.compareInventories(System.out,map,map2,null,null,null));
    
  }
	
	public void testProduceInventory() throws Exception
	{
		class TestClass
		{
			
		}
		
		JVMTIInterface jvmti = new JVMTIInterface(); // The JVMTIWrapper used to produce inventories
    
    Map map = jvmti.produceInventory();
    Map map2 = jvmti.produceInventory();
    jvmti.compareInventories(System.out,map,map2,null,null,null);
    System.out.println("size=" + map.size());
    map=null;
    map2=null;

		TestClass keepr = new TestClass(); // at least one instance, so point2 won't be null
		
		Map firstMap = jvmti.produceInventory(); // The inventory of classes, this is HashMap<Class,InventoryDataPoint>

		TestClass [] tests = new TestClass[1000];
		for (int i=0;i<1000;i++)
		{
			tests[i] = new TestClass(); // allocating 1000 objects
		}
		 
		Map secondMap = jvmti.produceInventory(); // the second inventory
		
		InventoryDataPoint point1 = (InventoryDataPoint)secondMap.get(TestClass.class);
		InventoryDataPoint point2 = (InventoryDataPoint)firstMap.get(TestClass.class);
		
		assertEquals(1000,point1.getInstances()-point2.getInstances()); // you can manually compare it
		
		assertTrue(jvmti.compareInventories(System.out,firstMap,secondMap,new Class[]{WeakHashMap.class},new String[]{"[Ljava.util.WeakHashMap$Entry;","java.lang.ref"}, new InventoryDataPoint[]{new InventoryDataPoint(TestClass.class,2000)}));
		assertFalse(jvmti.compareInventories(System.out,firstMap,secondMap,new Class[]{WeakHashMap.class},new String[]{"[Ljava.util.WeakHashMap$Entry;","java.lang.ref"}, new InventoryDataPoint[]{new InventoryDataPoint(TestClass.class,100)}));

	}
	
	public void testExploreObject() throws Exception
	{
		A a = new A();
		B b = a.b;
		
		JVMTIInterface jvmti = new JVMTIInterface();
		HashMap map = jvmti.createIndexMatrix();
		jvmti.exploreClassReferences(B.class.getName(),-1,true,true,true,true,false,map);
		
		System.out.println(jvmti.exploreObjectReferences(map,b,1,true));
		/*map=null;
		jvmti.releaseTags();
		System.out.println(jvmti.exploreObjectReferences(B.class.getName(),2,true)); */
		
		
		
	}

	public void notestNotify()
	{
		A a = new A();
		B b = a.b;
		final ArrayList list = new ArrayList();
		
		JVMTIInterface jvmti = new JVMTIInterface();
		jvmti.notifyOnReferences("/tmp/tst.refs",new JVMTICallBack(){

			int count=0;
			public void notifyReference(long referenceHolder, long referencedObject,long classTag, long index, long method, byte referenceType)
			{
				list.add(new ReferenceDataPoint(referenceHolder, referencedObject,classTag, index, method, referenceType)); 
			}
			public void notifyClass(long classTag, Class clazz) {
				fail("This shouldn't be called now");
			}
			public void notifyObject(long classTag, long objectId, long bytes) {
				fail("This shouldn't be called now");
			}
		});
		
		long tag = jvmti.getTagOnObject(a);
		Object newObject = jvmti.getObjectOnTag(tag);
		assertSame(a,newObject);
		
		int count=0;
		Iterator iter = list.iterator();
		while (iter.hasNext())
		{
			ReferenceDataPoint point = (ReferenceDataPoint)iter.next();
			//System.out.println("point=" + point);
			if (point.getClassTag()!=0 ) 
			{
				Object obj = jvmti.getObjectOnTag(point.getClassTag());
				if (!(obj instanceof Class))
				{
					System.out.println("point=" + point);
					System.out.println("obj=" + obj);
					fail("Object was supposed to be a Class");
				}
			}
			

			if (point.getReferenceType()==JVMTICallBack.JVMTI_REFERENCE_STATIC_FIELD || point.getReferenceType()==JVMTICallBack.JVMTI_REFERENCE_FIELD)
			{
				try
				{

					Object referenced = jvmti.getObjectOnTag(point.getReferencedObject());
					Object obj = jvmti.getObjectOnTag(point.getReferenceHolder());
					Field field = jvmti.getObjectField(obj .getClass(),(int)point.getIndex());
					Class clazz = (Class)jvmti.getObjectOnTag(point.getClassTag());
					/*if (field==null)
					{
						System.out.println(point);
						System.out.println(referenced + " couldn't find field " +point.getIndex() + " on " + obj);
						Field field2 = jvmti.getObjectField(obj .getClass(),(int)point.getIndex());
					}
					assertNotNull(field);  -- I would like to enforce this, but it seems impossible due to internal classes on JVM */
				}
				catch (NullPointerException e)
				{
				}
			}
			else if (point.getReferenceType()==JVMTICallBack.THREAD_REFERENCE)
			{
				try
				{
				Object classTag = jvmti.getObjectOnTag(point.getClassTag());
				Object objReferenced = jvmti.getObjectOnTag(point.getReferencedObject());
				Object objHolder = jvmti.getObjectOnTag(point.getReferenceHolder());
				
				String methodName= jvmti.getMethodClass(point.getMethod()).getName() + "::" +
				jvmti.getMethodName(point.getMethod()) + jvmti.getMethodSignature(point.getMethod());
				//System.out.println(objReferenced + " being referenced at "+methodName);
				}
				catch (NullPointerException e)
				{
					// this can happen here;
				}
			}
			
		}
		
		System.out.println();
		
		long tagOnA = jvmti.getTagOnObject(a.b);
		iter = list.iterator();
		while (iter.hasNext())
		{
			ReferenceDataPoint point = (ReferenceDataPoint)iter.next();
			
			if (tagOnA == point.getReferencedObject() && (point.getReferenceType()==JVMTICallBack.JVMTI_REFERENCE_FIELD  || point.getReferenceType()==JVMTICallBack.JVMTI_REFERENCE_STATIC_FIELD))
			{
				Object obj = jvmti.getObjectOnTag(point.getReferenceHolder());
				Field field  = null;
				
				if (point.getReferenceType()==JVMTICallBack.JVMTI_REFERENCE_FIELD)
				{
					field  = jvmti.getObjectField((Class)obj.getClass() ,(int)point.getIndex());
				}
				else
				{
					field  = jvmti.getObjectField((Class)obj ,(int)point.getIndex());
				}
				System.out.println(obj + " being referenced at " + field);
			}
			else if (tagOnA == point.getReferencedObject() && (point.getReferenceType()==JVMTICallBack.THREAD_REFERENCE))
			{
				Object classTag = jvmti.getObjectOnTag(point.getClassTag());
				Object objReferenced = jvmti.getObjectOnTag(point.getReferencedObject());
				Object objHolder = jvmti.getObjectOnTag(point.getReferenceHolder());
				
				System.out.println("classTag=" + classTag);
				System.out.println("objReferenced=" + objReferenced);
				System.out.println("objHolder=" + objHolder);
				System.out.println("name=" +jvmti.getMethodClass(point.getMethod()).getName() + "::" +
						jvmti.getMethodName(point.getMethod()) + jvmti.getMethodSignature(point.getMethod()));
			}
		}
		
		
		try
		{
			jvmti.getObjectOnTag(0);
			fail("Supposed to throw an exception");
		}
		catch (Throwable e)
		{
		}
		
		try
		{
			jvmti.getObjectOnTag(-1);
			fail("Supposed to throw an exception");
		}
		catch (Throwable e)
		{
		}
		

		
		jvmti.releaseTags();
		assertEquals(0,jvmti.getTagOnObject(a));
		
		
	}
	
	public void testGetReferenceHolders()
	{
		JVMTIInterface jvmti = new JVMTIInterface();
		A a = new A();
		Object[] objects = jvmti.getReferenceHolders(new Object[]{a.b});
		assertEquals(2,objects.length);
		assertSame(a,objects[0]);
	}
	
	public void testGetObjects()
    {
        JVMTIInterface interfaceJVMTI = new JVMTIInterface();
        Object[] result = interfaceJVMTI.getAllObjects(java.lang.String.class);

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

        result = interfaceJVMTI.getAllObjects(java.lang.Class.class);
        assertTrue(result.length>0);

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

    ThreadLocal local = new ThreadLocal();
    
    static Holder holder[] = new Holder[100];
    
    static
    {
        for (int i=0;i<holder.length;i++)
        {
            holder[i] = new Holder();
        }
    }
    
    public void testHolders() throws Exception
    {
        JVMTIInterface interfaceJVMTI = new JVMTIInterface();
        
    	Object holders[] = interfaceJVMTI.getReferenceHolders(new Object[]{this});
    	
    	System.out.println("There are " + holders.length + " holding a reference");
    }

    public void testNavigation() throws Exception
    {
        JVMTIInterface interfaceJVMTI = new JVMTIInterface();
        Class classes[] = interfaceJVMTI.getLoadedClasses();
        System.out.println("length=" + classes.length);
        ArrayList stringVer = new ArrayList();
        stringVer.add(new String("tst"));
        try
        {
            Thread.sleep(1000);
        } catch (Exception e)
        {
            e.printStackTrace();
        }

        Holder holder = new Holder();
        holder.addInstance(new UsedByHolder());
        holder.addInstance(new UsedByHolder());

        Holder holder2 = new Holder();
        holder2.addInstance(new UsedByHolder());
        holder2.addInstance(new UsedByHolder());
        
        Holder holder3 = new Holder();
        holder3=null;
        
        local.set(holder3);
        
        

        JVMTIInterface jvmti = new JVMTIInterface();
        jvmti.forceGC();
        
        //File tmpFile = File.createTempFile("profiler","");
        File tmpFile = new File("tst","");
        
        System.out.println("Generating data to " + tmpFile.getAbsolutePath());
        
        jvmti.heapSnapshot(tmpFile.getAbsolutePath(), "log");
        holder.print();
        holder2.print();
        
        MemorySnapshotEngine engine = new MemorySnapshotEngine();
        engine.processFiles(tmpFile.getAbsolutePath(),"log");
        
        MemorySnapshotController controller = new MemorySnapshotController(engine);
        Collection coll = controller.filterRoots(false);
        
        Iterator iter = coll.iterator();
        
        int i=0;
        while (iter.hasNext())
        {
            MemoryClass clazz = (MemoryClass)iter.next();
            if ((i++)==1)
            {
                Collection collResult = controller.summarizeReferenceByPath(true,new String[]{"C" + clazz.getId()});
                //assertTrue(collResult.size()>0);
            }
            
            
            if (clazz.getClassLoaderId()!=0)
            {
                Collection collClassLoader = controller.solveLoadedClass(clazz.getClassLoaderId(),false);
            }
            
        }
        
    }
    
    public static void main(String arg[])
    {
        JVMTITest test = new JVMTITest();
        
        try
        {
            test.testGetObjects();
        } catch (Throwable e1)
        {
            e1.printStackTrace();
        }
        
        
        try
        {
            test.testNavigation();
        } catch (Throwable e)
        {
            e.printStackTrace();
        }
    }
}
















