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

import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.logging.Logger;
import org.jboss.util.NestedRuntimeException;

import java.util.*;

/**
 * Map interceptor to perform Map cache storage and retrieval
 *
 * @author <a href="mailto:harald@gliebe.de">Harald Gliebe</a>
 * @author Ben Wang
 */
public class CachedMapInterceptor implements CachedCollectionInterceptor, Map
{

   protected static final Logger log = Logger.getLogger(CachedMapInterceptor.class);
   protected static final Map managedMethods =
         CollectionInterceptorUtil.getManagedMethods(Map.class);

   protected TreeCacheAop cache;
   protected Fqn fqn;
   protected Map methodMap;

   protected CachedMapInterceptor(TreeCacheAop cache, Fqn fqn, Class clazz)
   {
      this.cache = cache;
      this.fqn = fqn;
      methodMap = CollectionInterceptorUtil.getMethodMap(clazz);
   }

   public String getName()
   {
      return "CachedMapInterceptor";
   }

   public Object invoke(org.jboss.aop.joinpoint.Invocation invocation) throws Throwable
   {
      return CollectionInterceptorUtil.invoke(invocation,
            this,
            methodMap,
            managedMethods);
   }

   // implementation of the java.util.Map interface

   protected Node getNode()
   {
      try {
         return cache.get(fqn);
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public Object get(Object key)
   {
      try {
         return cache.getObject(new Fqn(fqn, key));
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public Object put(Object key, Object value)
   {
      try {
         return cache.putObject(new Fqn(fqn, key), value);
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public void putAll(Map map)
   {
      for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
         Map.Entry entry = (Map.Entry) i.next();
         put(entry.getKey(), entry.getValue());
      }
   }

   public Object remove(Object key)
   {
      try {
         return cache.removeObject(new Fqn(fqn, key));
      } catch (Exception e) {
         throw new NestedRuntimeException(e);
      }
   }

   public void clear()
   {
      // Need to clone first to avoid CME
      ArrayList list = new ArrayList(keySet());
      for(int i=0; i < list.size(); i++) {
         remove(list.get(i));
      }
   }

   public int size()
   {
      Node node = getNode();
      if(node == null) {
         return 0;
      }

      Map children = node.getChildren();
      return children == null ? 0 : children.size();
   }

   public boolean isEmpty()
   {
      return size() == 0;
   }

   public boolean containsKey(Object object)
   {
      Map children = getNode().getChildren();
      return children == null ? false : children.containsKey(object);
   }

   public boolean containsValue(Object object)
   {
      throw new AopOperationNotSupportedException("CachedMapInterceptor: map.containsValues() operation not supported");
   }

   public Set entrySet()
   {
      final CachedMapInterceptor map = this;

      return new AbstractSet()
      {

         public int size()
         {
            Map children = getNode().getChildren();
            return children == null ? 0 : children.size();
         }

         public Iterator iterator()
         {
            Map children = getNode().getChildren();
            final Iterator i =
                  children == null
                  ? Collections.EMPTY_LIST.iterator()
                  : children.keySet().iterator();

            return new Iterator()
            {
               Object lastKey; // for remove

               public boolean hasNext()
               {
                  return i.hasNext();
               }

               public Object next()
               {
                  return new Entry(lastKey = i.next());
               }

               public void remove()
               {
                  map.remove(lastKey);
               }
            };
         }
      };
   }

   public Collection values()
   {
      throw new AopOperationNotSupportedException("CachedMapInterceptor: map.values() operation not supported");
   }

   public Set keySet()
   {
      /**
       * TODO Note that return Set is not thread safe because children is not.
       */
      Map children = getNode().getChildren();
      return children == null ? Collections.EMPTY_SET : children.keySet();
   }

   public int hashCode()
   {
      int result = 0;
      for (Iterator i = entrySet().iterator(); i.hasNext();) {
         result += i.next().hashCode();
      }
      return result;
   }

   public boolean equals(Object object)
   {
      if (object == this)
         return true;
      if (!(object instanceof Map))
         return false;
      Map map = (Map) object;
      if (size() != map.size())
         return false;
      for (Iterator i = entrySet().iterator(); i.hasNext();) {
         Map.Entry entry = (Map.Entry) i.next();
         Object value = entry.getValue();
         if (value != null) {
            if (map.get(entry.getKey()) == null
                  || !map.containsKey(entry.getKey()))
               return false;
         } else { // Null key is not allowed
            return false;
         }
      }
      return true;
   }

   public String toString() {
      StringBuffer buf = new StringBuffer();
      Set set = keySet();
      for(Iterator it = set.iterator(); it.hasNext();) {
         Object key = it.next();
         buf.append("[" +key).append(", ").append(get(key)).append("]");
         if(it.hasNext()) buf.append(", ");
      }

      return buf.toString();
   }

   protected class Entry implements Map.Entry
   {

      Object key;

      public Entry(Object key)
      {
         this.key = key;
      }

      public Object getKey()
      {
         return key;
      }

      public Object getValue()
      {
         try {
            return cache.getObject(new Fqn(fqn, key));
         } catch (Exception e) {
            throw new NestedRuntimeException(e);
         }
      }

      public Object setValue(Object value)
      {
         try {
            return cache.putObject(new Fqn(fqn, key), value);
         } catch (Exception e) {
            throw new NestedRuntimeException(e);
         }
      }

      public int hashCode()
      {
         Object value = getValue();
         return ((key == null) ? 0 : key.hashCode())
               ^ ((value == null) ? 0 : value.hashCode());
      }

      public boolean equals(Object obj)
      {
         if (!(obj instanceof Map.Entry))
            return false;
         Map.Entry entry = (Map.Entry) obj;
         Object value = getValue();
         return (
               key == null
               ? entry.getKey() == null
               : key.equals(entry.getKey()))
               && (value == null
               ? entry.getValue() == null
               : value.equals(entry.getValue()));
      }
   }

}