/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.pojo.impl;

import org.jboss.cache.Fqn;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * POJO class metadata information.
 * When an object is looked up or put in PojoCache, this object will be advised with a CacheFieldInterceptor.
 * The underlying cache stores a reference to this object (for example to update the instance variables, etc.).
 * Since this reference need to be transactional but never replicated (the reference is only valid
 * within the VM this reference is thus stored into an PojoReference (as a transient field).
 * In addition, this instance also serves as a metadata for PojoCache. E.g., it has a reference counting for
 * multiple references and reference FQN.
 *
 * @author Ben Wang
 */
public class PojoInstance implements Serializable // Externalizable is no more efficient
{
   //    protected static Log log=LogFactory.getLog(PojoReference.class.getLastElementAsString());
   public static final String KEY = InternalConstant.POJOCACHE_KEY_PREFIX + "PojoInstance";
   public static final int INITIAL_COUNTER_VALUE = -1;

   static final long serialVersionUID = 6492134565825613209L;

   // The instance is transient to avoid replication outside the VM
   private transient Object instance_;

   // If not null, it signifies that this is a reference that points to this fqn.
   // Note that this will get replicated.
   private String internalFqn_ = null;

   // Reference counting. THis will get replicated as well. This keep track of number of
   // other instances that referenced this fqn.
   private int refCount_ = INITIAL_COUNTER_VALUE;

   // List of fqns that reference this fqn. Assume list size is not big since it may not be efficient.
   private List<Fqn> referencedBy_ = null;
   private Class clazz_ = null;
   private transient PojoUtil util_ = new PojoUtil();

   public PojoInstance()
   {
   }

   public PojoInstance(Object instance)
   {
      set(instance);
   }

   public void setPojoClass(Class clazz)
   {
      clazz_ = clazz;
   }

   public Class getPojoClass()
   {
      return clazz_;
   }

   public Object get()
   {
      return instance_;
   }

   public void set(Object instance)
   {
      instance_ = instance;
   }

   public String getInternalFqn()
   {
      return internalFqn_;
   }

   public void setInternalFqn(String refFqn)
   {
      internalFqn_ = refFqn;
   }

   public void removeInternalFqn()
   {
      internalFqn_ = null;
   }

   synchronized public int incrementRefCount(Fqn sourceFqn)
   {
      if (sourceFqn == null)
      {
         throw new IllegalStateException("PojoInstance.incrementRefCount(): null sourceFqn");
      }

      if (referencedBy_ == null)
      {
         referencedBy_ = new ArrayList();
      }

      if (util_ == null) util_ = new PojoUtil();
      refCount_ = util_.incrementReferenceCount(sourceFqn, refCount_, referencedBy_);
//      referencedBy_.add(sourceFqn);

//      refCount_ += 1;
//logger_.info("incrementRefCount(): current ref count " +refCount_);
      return refCount_;
   }

   synchronized public int decrementRefCount(Fqn sourceFqn)
   {
      if (sourceFqn == null)
      {
         throw new IllegalStateException("PojoInstance.incrementRefCount(): null sourceFqn");
      }

      if (!referencedBy_.contains(sourceFqn))
         throw new IllegalStateException("PojoReference.decrementRefCount(): source fqn: " +
                                         sourceFqn + " is not present.");

      if (util_ == null) util_ = new PojoUtil();
      refCount_ = util_.decrementReferenceCount(sourceFqn, refCount_, referencedBy_);
//      referencedBy_.remove(sourceFqn);

//      refCount_ -= 1;
//logger_.info("decrementRefCount(): current ref count " +refCount_);
      return refCount_;
   }

   synchronized public int getRefCount()
   {
      return refCount_;
   }

   public List<Fqn> getReferences()
   {
      return Collections.unmodifiableList(referencedBy_);
   }

   synchronized public Fqn getAndRemoveFirstFqnInList()
   {
      return (Fqn) referencedBy_.remove(0);
   }

   synchronized public void addXFqnIntoList(Fqn fqn)
   {
      referencedBy_.add(0, fqn);
   }

   public String toString()
   {
      return "PI[fqn=" + internalFqn_ + " ref=" + refCount_ + " class=" + clazz_.getName() + "]";
   }
}
