Bean-Managed Persistence Using a Proxy List
Vol. 5 - Issue 6 p.126

Listing 1: ListInvocationHandler.java

package orders.demandlist;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.*;

public class ListInvocationHandler implements
  InvocationHandler,PersistentOperations
{
  private List backingList;
  private DataStore dataStore;
  private boolean dataLoaded = false;
  private List deletedItems = new LinkedList();
  private Set originalSet = new HashSet();

  public ListInvocationHandler( List list,
    DataStore dataStore )
  {
    this.backingList = list;
    this.dataStore = dataStore;
  }

  public Object invoke(Object proxy,
    Method method, Object[] args) throws Throwable
  {
    // persistent operations interface
    if (method.getDeclaringClass().equals(
      orders.demandlist.PersistentOperations.class
      ))
        return method.invoke( this, args );
    else
    {
      // list interface
      if (!dataLoaded)
      {
        dataStore.load( this );
        dataLoaded = true;
      }
      processDeletedItems( method, args );
      Object obj = method.invoke( backingList,
        args );
        if (obj instanceof Iterator)
        {
          return DemandListIteratorFactory.
            getDemandListIterator( (Iterator)obj,
            this );
        }
        else if (obj instanceof List)
        {
          return Collections.unmodifiableList(
            (List)obj);
        }
        else
          return obj;
    }
  }

  // PersistentOperations implementation
  public void loadFromStore()
  {
    dataStore.load( this );
  }

  public void saveToStore()
  {
    if (dataLoaded)
    {
      dataStore.persist( this );
      originalSet.addAll( backingList );
    }
  }

  public List getDeletedObjects()
  {
    return Collections.unmodifiableList(
      deletedItems );
  }

  public Set getOriginalSet()
  {
    return Collections.unmodifiableSet(
      originalSet );
  }

  public List getCurrentList()
  {
    return Collections.unmodifiableList(
      backingList );
  }

  public void addFromStore( Object obj )
  {
    backingList.add( obj );
    originalSet.add( obj );
  }

  // package protected iterator support
  void notifyObjectRemoved( Object obj )
  {
    deletedItems.add( obj );
  }
 
  // implementation
  private void processDeletedItems(Method method,
    Object[] args)
  {
    String methodName = method.getName();
    if (methodName.equals("clear"))
    {
      deletedItems.addAll( backingList );
    }
    else if (methodName.equals("removeAll"))
    {
      deletedItems.addAll( (Collection)args[0] );
    }
    else if (methodName.equals("retainAll"))
    {
      List tempList = new LinkedList();
      tempList.addAll( backingList );
      tempList.removeAll( (Collection)args[0] );
      deletedItems.addAll( tempList );
    }
    else if (methodName.equals("remove"))
    {
      Class[] paramTypes =
        method.getParameterTypes();

      if (paramTypes[0].equals( Integer.TYPE ))
      {
        Object obj = backingList.get(
          ((Integer)args[0]).intValue() );
        deletedItems.add( obj );
      }
      else
      {
        deletedItems.add( args[0] );
      }
    }
  }
}

Listing 2: DemandListIteratorFactory.java

package orders.demandlist;

import java.util.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Method;

public class DemandListIteratorFactory
{

  public static Iterator getDemandListIterator(
    final Iterator iterator,
    final ListInvocationHandler invocationHandler)
  {
    InvocationHandler handler =
      new InvocationHandler()
    {
      private Object cacheLastRetrieval = null;

      public Object invoke(Object proxy,
        Method method, Object[] args)
        throws Throwable
      {
        String methodName = method.getName();
        if (methodName.equals("next") ||
          methodName.equals("previous") )
        {
          Object obj = method.invoke(
            iterator, args );
          cacheLastRetrieval = obj;
          return obj;
        }
        else if (methodName.equals("remove"))
        {
          Object obj = method.invoke(
            iterator, args );
          invocationHandler.notifyObjectRemoved(
            cacheLastRetrieval );
          return obj;
        }
        return method.invoke( iterator, args );
      }
    };

    Class clazz = null;
    if (iterator instanceof ListIterator)
      clazz = java.util.ListIterator.class;
    else
      clazz = java.util.Iterator.class;
    return (Iterator) Proxy.newProxyInstance(
      java.util.List.class.getClassLoader(),
      new Class[] { clazz }, handler);
  }
}

Listing 3: PersistentOperations.java

package orders.demandlist;

import java.util.*;

public interface PersistentOperations
{
  public void loadFromStore();
  public void saveToStore();

  public void addFromStore( Object obj );
  public List getDeletedObjects();
  public Set  getOriginalSet();
  public List getCurrentList();
}

Listing 4: DataStore.java

package orders.demandlist;

import java.util.*;

public interface DataStore
{
  public void load(
    PersistentOperations persistOp );
  public void persist(
    PersistentOperations persistOp );

}

Listing 5: DemandList Factory.java

package orders.demandlist;

import java.util.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class DemandListFactory
{
  public static List getDemandList( final List
    list, final DataStore dataStore )
  {
    InvocationHandler handler =
      new ListInvocationHandler(list, dataStore);

    return (List) Proxy.newProxyInstance(
      PersistentOperations.class.getClassLoader(),
      new Class[] { List.class,
      PersistentOperations.class }, handler);
  }
}