package org.jboss.cache.aop;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jboss.cache.aop.PojoCache;
import org.jboss.cache.aop.test.Address;
import org.jboss.cache.aop.test.Person;
import org.jboss.cache.aop.test.Link;
import org.jboss.cache.aop.test.NodeManager;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.DataNode;

import java.util.*;

/**
 * Test object graph handling in aop, e.g., circular reference, multiple reference, link, etc.
 * @author Ben Wang
 */

public class ObjectGraphAopTest extends TestCase
{
   Log log=LogFactory.getLog(ObjectGraphAopTest.class);
   PojoCache cache_;

   public ObjectGraphAopTest(String name)
   {
      super(name);
   }

   protected void setUp() throws Exception
   {
      super.setUp();
      log.info("setUp() ....");
//      String configFile = "META-INF/local-service.xml";
      String configFile = "META-INF/local-service.xml";
      cache_ = new PojoCache();
      PropertyConfigurator config = new PropertyConfigurator();
      config.configure(cache_, configFile); // read in generic replSync xml
      cache_.start();
   }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      cache_.stop();
   }

//   public void testDummy() {}

   protected Person createPerson(String name, int age)
   {
      Person p = new Person();
      p.setName(name);
      p.setAge(age);
      return p;
   }

   /**
    * Test shared sub-object. In diagram, it is a forest
    * where the sub-object is connected by two tree parents.
    */
   public void testMultipleReference() throws Exception
   {
      log.info("testMultipleReference() ...");
      cache_.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache_.getObject("/person/joe");
      cache_.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache_.getObject("/person/ben");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      // They share the sub-object: address
      log.info("testMultipleReference(): set Joe address");
      joe.setAddress(addr);
      log.info("testMultipleReference(): set Ben address");
      ben.setAddress(addr);

      log.info("testMultipleReference(): verify");
      Address add1 = (Address) ((Person)cache_.getObject("/person/joe")).getAddress();
      Address add2 = (Address) ((Person)cache_.getObject("/person/ben")).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());
   }

   public void testRefCount() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testRefCount() ...");
      stage1();
      stage2();

      cache_.removeObject("/person/ben");
   }

   private void stage1() throws Exception
   {
      Person joe = createPerson("Joe Black", 31);
      Person ben = createPerson("Ben Hogan", 51);
      cache_.putObject("/person/joe", joe);
      cache_.putObject("/person/ben", ben);

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      joe.setAddress(addr);
      ben.setAddress(addr);
      // They share the sub-object: address
      Address add1 = (Address) ((Person)cache_.getObject("/person/joe")).getAddress();
      Address add2 = (Address) ((Person)cache_.getObject("/person/ben")).getAddress();
      assertEquals("Joe's address should still be valid ", "Sunnyvale", add1.getCity());
      assertEquals("Ben's address should still be valid ", "Sunnyvale", add2.getCity());
   }

   private void stage2() throws Exception
   {
      //
      cache_.removeObject("/person/joe");
      Person ben = (Person) cache_.getObject("/person/ben");
      Address addr = ben.getAddress();
      assertEquals("Ben's address should still be valid ", "Sunnyvale", addr.getCity());
      addr.setCity("Santa Clara");
      assertEquals("Ben's address should be changed ", "Santa Clara", addr.getCity());
   }

   /**
    * Pure parent child relationsip
    * @throws Exception
    */
   public void testCircularReference() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      parent.setLink(child);
      child.setLink(parent);
      cache_.putObject("/link/parent", parent);
      assertEquals("parent", ((Link) cache_.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache_.getObject("/link/parent")).getLink().getName());

      // Would fail if uncoment becuase of internal mapping is non-canonical.
//      child.setLink(parent);
      parent.setLink(child);
   }

   /**
    * Pure parent child relationsip
    * @throws Exception
    */
   public void testCircularReference1() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference1() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      parent.setLink(child);
      child.setLink(parent);
      cache_.putObject("/link/parent", parent);
      assertEquals("parent", ((Link) cache_.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache_.getObject("/link/parent")).getLink().getName());
      Link link = (Link)cache_.removeObject("/link/parent");
      assertEquals("child", link.getLink().getName());
      assertNull("Cache should be null ", ((DataNode)cache_.get("/parent")));
   }

   /**
    * cache managed first before put in the relationsip.
    * @throws Exception
    */
   public void testCircularReference2() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference2() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      cache_.putObject("/link/parent", parent);
      parent.setLink(child);
      child.setLink(parent);
      assertEquals("parent", ((Link) cache_.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache_.getObject("/link/parent")).getLink().getName());
      Link link = (Link)cache_.removeObject("/link/parent");
      assertEquals("child", link.getLink().getName());
      assertNull("Cache should be null ", ((DataNode)cache_.get("/parent")));
   }

   /**
    * Put first before settting the relationship
    * @throws Exception
    */
   public void testCircularReference3() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference3() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      cache_.putObject("/link/parent", parent);
      cache_.putObject("/link/child", child);
      parent.setLink(child);
      child.setLink(parent);
      assertEquals("parent", ((Link) cache_.getObject("/link/parent")).getName());
      assertEquals("child", ((Link) cache_.getObject("/link/parent")).getLink().getName());
      assertEquals("child", ((Link) cache_.getObject("/link/child")).getName());
      assertEquals("parent", ((Link) cache_.getObject("/link/child")).getLink().getName());
      Link link = (Link)cache_.removeObject("/link/parent");
      assertEquals("child", link.getLink().getName());
      assertNull("Cache should be null ", ((DataNode)cache_.get("/parent")));
   }

   /**
    * Setting the circular relationship and also as a shared object.
    * @throws Exception
    */
   public void testCircularReference4() throws Exception
   {
//        try {Thread.sleep(10000); } catch (Exception e) {};
      log.info("testCircularReference3() ...");
      Link parent = new Link("parent");
      Link child = new Link("child");
      parent.setLink(child);
      child.setLink(parent);

      List list = new ArrayList();
      list.add(parent);

      cache_.putObject("/list", list);
      cache_.putObject("/alias", list);

      List list1 = (List)cache_.getObject("/list");
      List list2 = (List)cache_.getObject("/alias");

      assertEquals("parent", ((Link) list1.get(0)).getName());
      assertEquals("child", ((Link) list2.get(0)).getLink().getName());
   }

   public void testCircularAndSharedReferences() throws Exception
   {
      log.info("testCircularAndSharedReferences() ...");
      NodeManager pm_ = new NodeManager();

      pm_.setRootNode("root");
      pm_.addNode("root", "kanto");
      pm_.addNode("root.kanto", "tokyo");
      pm_.addNode("root.kanto", "kanagawa");

      assertEquals("kanagawa", pm_.findNode("root.kanto.kanagawa").getNodeRDN());
      cache_.putObject("/propagation", pm_);
      pm_.addNode("root.kanto.tokyo", "hadanshita");
      assertEquals("hadanshita", pm_.findNode("root.kanto.tokyo.hadanshita").getNodeRDN());

      List list = pm_.findNode("root").getChildren();
      assertEquals("Root should have children of ", 1, list.size());
/*
      System.out.println("\n\n");
      System.out.println("---------------------------------------------");
      System.out.println("Initial cache content");
      System.out.println(cache_.printDetails());
      System.out.println("---------------------------------------------");

      System.out.println("\n\n");
      System.out.println("---------------------------------------------");
      System.out.println("Initial pm state");
      System.out.println("---------------------------------------------");
*/
      pm_.printNodes();
   }

   public void testRemoveObject1() throws Exception
   {
      log.info("testRemoveObject1() ...");
      cache_.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache_.getObject("/person/joe");
      cache_.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache_.getObject("/person/ben");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      // They share the sub-object: address
      log.info("testMultipleReference(): set Joe address");
      joe.setAddress(addr);
      log.info("testMultipleReference(): set Ben address");
      ben.setAddress(addr);

      Address add1 = (Address) ((Person)cache_.getObject("/person/joe")).getAddress();
      Address add2 = (Address) ((Person)cache_.getObject("/person/ben")).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());

      // Remove pojo joe will relocate the address field to ben's
      cache_.removeObject("/person/joe");
      add2 = (Address) ((Person)cache_.getObject("/person/ben")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
   }

   public void testRemoveObject2() throws Exception
   {
      log.info("testRemoveObject2() ...");
      cache_.putObject("/person/joe", createPerson("Joe Black", 31));
      Person joe = (Person) cache_.getObject("/person/joe");
      cache_.putObject("/person/ben", createPerson("Ben Hogan", 51));
      Person ben = (Person) cache_.getObject("/person/ben");
      cache_.putObject("/person/john", createPerson("John Daly", 41));
      Person john = (Person) cache_.getObject("/person/john");

      Address addr = new Address();
      addr.setStreet("123 Albert Ave.");
      addr.setCity("Sunnyvale");
      addr.setZip(94087);

      Address addr1 = new Address();
      addr1.setStreet("123 Albert Ave.");
      addr1.setCity("San Jose");
      addr1.setZip(94087);

      // They share the sub-object: address
      log.info("testMultipleReference(): set Joe address");
      joe.setAddress(addr);
      log.info("testMultipleReference(): set Ben address");
      ben.setAddress(addr);
      log.info("testMultipleReference(): set John address");
      john.setAddress(addr);

      Address add1 = (Address) ((Person)cache_.getObject("/person/joe")).getAddress();
      Address add2 = (Address) ((Person)cache_.getObject("/person/ben")).getAddress();
      assertEquals(add1.getCity(), add2.getCity());
      addr.setCity("Santa Clara");
      assertEquals(add1.getCity(), add2.getCity());

      // Remove pojo joe will relocate the address field to ben's
      joe.setAddress(addr1);
      add2 = (Address) ((Person)cache_.getObject("/person/joe")).getAddress();
      assertEquals("City ", "San Jose", add2.getCity());
      add2 = (Address) ((Person)cache_.getObject("/person/ben")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
      add2 = (Address) ((Person)cache_.getObject("/person/john")).getAddress();
      assertEquals("City ", "Santa Clara", add2.getCity());
   }

   public void XtestObjectIdentity() throws Exception
   {
   }

   public static Test suite() throws Exception
   {
      return new TestSuite(ObjectGraphAopTest.class);
   }

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

}

