//$Id: MapDataModel.java 7579 2008-03-14 12:08:54Z pete.muir@jboss.org $
package org.jboss.seam.jsf;

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

import javax.faces.model.DataModelEvent;
import javax.faces.model.DataModelListener;

/**
 * A JSF DataModel for maps.
 * 
 * @author Gavin King
 *
 */
public class MapDataModel extends javax.faces.model.DataModel implements
      Serializable
{
   private static final long serialVersionUID = -4888962547222002402L;
   
   private int rowIndex = -1;
   
   private Map data;
   private transient List<Map.Entry> entries;
   
   public MapDataModel() {}
   
   public MapDataModel(Map map)
   {
      if (map == null)
      {
         throw new IllegalArgumentException("null map data");
      }
      setWrappedData(map);
   }
   
   @Override
   public int getRowCount()
   {
      if (entries == null)
      {
         return -1;
      }
      return entries.size();
   }
   
   /**
    * Returns a Map.Entry
    */
   @Override
   public Object getRowData()
   {
      if (entries == null)
      {
         return null;
      }
      if ( !isRowAvailable() )
      {
         throw new IllegalArgumentException("row is unavailable");
      }
      return entries.get(rowIndex);
   }
   
   @Override
   public int getRowIndex()
   {
      return rowIndex;
   }
   
   @Override
   public Object getWrappedData()
   {
      return new AbstractMap()
      {
         @Override
         public Set entrySet()
         {
            return new AbstractSet()
            {
               @Override
               public Iterator iterator()
               {
                  return entries.iterator();
               }
               @Override
               public int size()
               {
                  return entries.size();
               }
            };
         }
      };
   }
   
   @Override
   public boolean isRowAvailable()
   {
      return entries!=null && 
            rowIndex >= 0 && 
            rowIndex < entries.size();
   }
   
   @Override
   public void setRowIndex(int newRowIndex)
   {
      if (newRowIndex < -1)
      {
         throw new IllegalArgumentException("illegal rowIndex " + newRowIndex);
      }
      int oldRowIndex = rowIndex;
      rowIndex = newRowIndex;
      if (entries != null && oldRowIndex != newRowIndex)
      {
         Object data = isRowAvailable() ? getRowData() : null;
         DataModelEvent event = new DataModelEvent(this, newRowIndex, data);
         DataModelListener[] listeners = getDataModelListeners();
         for (int i = 0; i < listeners.length; i++)
         {
            listeners[i].rowSelected(event);
         }
      }
   }
   
   @Override
   public void setWrappedData(Object data)
   {
      this.data = (Map) data;
      entries = data == null ? null : new ArrayList( ( (Map) data ).entrySet() );
      int rowIndex = data != null ? 0 : -1;
      setRowIndex(rowIndex);
   }
   
   private void writeObject(ObjectOutputStream oos) throws IOException
   {
      oos.writeInt(rowIndex);
      oos.writeObject(data);
   }
   
   private void readObject(ObjectInputStream ois) throws IOException,
         ClassNotFoundException
   {
      rowIndex = ois.readInt();
      data = (Map) ois.readObject();
      entries = data == null ? null : new ArrayList( data.entrySet() );
   }
   
}
