package org.jboss.cache.util;

import net.jcip.annotations.Immutable;

import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Contains a fixed array of read-only map entries, from a copy of an existing map.
 * This class is more lightweight for places where the copied map will just be iterated over.
 * <p/>
 * This map is strictly read-only, and map modification methods (as well as modifications over iterators) will throw
 * {@link UnsupportedOperationException}s.
 *
 * @author Elias Ross
 */
@Immutable
public class MapCopy<K, V> extends AbstractMap<K, V> implements Serializable
{

   private static final long serialVersionUID = -958813082188242956L;

   private final List<Entry<K, V>> data;

   private transient Set<Map.Entry<K, V>> entrySet;

   /**
    * Copies the supplied map to an internal array.
    *
    * @param m map to copy
    */
   public MapCopy(Map<K, V> m)
   {
      data = new ArrayList<Entry<K, V>>(m.size());
      for (Map.Entry<K, V> me : m.entrySet())
      {
         if (me == null)
            throw new NullPointerException();
         data.add(new SimpleImmutableEntry<K, V>(me));
      }
      init();
   }

   public MapCopy()
   {
      this(new HashMap<K, V>(0));
   }

   /**
    * Returns a copy of the given map.
    */
   public static <L, W> Map<L, W> copy(Map<L, W> m)
   {
      return new MapCopy<L, W>(m);
   }

   private void init()
   {
      this.entrySet = new AbstractSet<Map.Entry<K, V>>()
      {
         @Override
         public int size()
         {
            return data.size();
         }

         @Override
         public Iterator<Map.Entry<K, V>> iterator()
         {
            return new EntryIterator();
         }
      };
   }

   private class EntryIterator implements Iterator<Entry<K, V>>
   {
      private int index;

      public boolean hasNext()
      {
         return index < data.size();
      }

      public Entry<K, V> next()
      {
         return data.get(index++);
      }

      public void remove()
      {
         throw new UnsupportedOperationException();
      }
   }

   @Override
   public Set<Map.Entry<K, V>> entrySet()
   {
      return entrySet;
   }

   @Override
   public int size()
   {
      return data.size();
   }

   private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException
   {
      in.defaultReadObject();
      init();
   }
}
