/*
 * JBoss, Home of Professional Open Source
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.cache.marshall;

import org.jboss.cache.Fqn;
import org.jboss.cache.Region;
import static org.jboss.cache.Region.Status;
import org.jboss.cache.buddyreplication.GravitateResult;
import org.jboss.cache.commands.ReplicableCommand;
import org.jboss.cache.factories.CommandsFactory;
import org.jboss.cache.factories.annotations.Inject;
import org.jboss.cache.optimistic.DefaultDataVersion;
import org.jboss.cache.transaction.GlobalTransaction;
import org.jboss.cache.util.MapCopy;
import org.jgroups.Address;
import org.jgroups.stack.IpAddress;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * An enhanced marshaller for RPC calls between CacheImpl instances.
 *
 * @author <a href="mailto:manik@jboss.org">Manik Surtani (manik@jboss.org)</a>
 */
public class CacheMarshaller200 extends AbstractMarshaller
{
   // magic numbers
   protected static final int MAGICNUMBER_METHODCALL = 1;
   protected static final int MAGICNUMBER_FQN = 2;
   protected static final int MAGICNUMBER_GTX = 3;
   protected static final int MAGICNUMBER_IPADDRESS = 4;
   protected static final int MAGICNUMBER_ARRAY_LIST = 5;
   protected static final int MAGICNUMBER_INTEGER = 6;
   protected static final int MAGICNUMBER_LONG = 7;
   protected static final int MAGICNUMBER_BOOLEAN = 8;
   protected static final int MAGICNUMBER_STRING = 9;
   protected static final int MAGICNUMBER_DEFAULT_DATA_VERSION = 10;
   protected static final int MAGICNUMBER_LINKED_LIST = 11;
   protected static final int MAGICNUMBER_HASH_MAP = 12;
   protected static final int MAGICNUMBER_TREE_MAP = 13;
   protected static final int MAGICNUMBER_HASH_SET = 14;
   protected static final int MAGICNUMBER_TREE_SET = 15;
   protected static final int MAGICNUMBER_NODEDATA_MARKER = 16;
   protected static final int MAGICNUMBER_NODEDATA_EXCEPTION_MARKER = 17;
   protected static final int MAGICNUMBER_NODEDATA = 18;
   protected static final int MAGICNUMBER_GRAVITATERESULT = 19;
   protected static final int MAGICNUMBER_SHORT = 20;
   protected static final int MAGICNUMBER_MAPCOPY = 21;
   protected static final int MAGICNUMBER_MARSHALLEDVALUE = 22;
   protected static final int MAGICNUMBER_NULL = 99;
   protected static final int MAGICNUMBER_SERIALIZABLE = 100;
   protected static final int MAGICNUMBER_REF = 101;

   protected static final InactiveRegionException IRE = new InactiveRegionException("Cannot unmarshall to an inactive region");

   public CacheMarshaller200()
   {
      initLogger();
      // enabled, since this is always enabled in JBC 2.0.0.
      useRefs = true;
   }

   protected CommandsFactory commandsFactory;

   @Inject
   public void injectCommandsFactory(CommandsFactory commandsFactory)
   {
      this.commandsFactory = commandsFactory;
   }

   // -------- AbstractMarshaller interface

   public void objectToObjectStream(Object o, ObjectOutputStream out) throws Exception
   {
      if (useRegionBasedMarshalling)
      {
         Fqn region = null;

         if (o instanceof RegionalizedReturnValue)
         {
            RegionalizedReturnValue rrv = (RegionalizedReturnValue) o;
            region = rrv.region;
            o = rrv.returnValue;
         }
         else if (o instanceof ReplicableCommand)
         {
            ReplicableCommand marshallableCommand = (ReplicableCommand) o;
            region = extractFqnRegion(marshallableCommand);
         }

         if (trace) log.trace("Region based call.  Using region " + region);
         objectToObjectStream(o, out, region);
      }
      else
      {
         // not region based!
         objectToObjectStream(o, out, null);
      }
   }

   @Override
   public RegionalizedMethodCall regionalizedMethodCallFromObjectStream(ObjectInputStream in) throws Exception
   {
      // parse the stream as per normal.
      Object[] retVal = objectFromObjectStreamRegionBased(in);
      RegionalizedMethodCall rmc = new RegionalizedMethodCall();
      rmc.command = (ReplicableCommand) retVal[0];
      rmc.region = (Fqn) retVal[1];
      return rmc;
   }


   public Object objectFromObjectStream(ObjectInputStream in) throws Exception
   {
      if (useRegionBasedMarshalling)
      {
         return objectFromObjectStreamRegionBased(in)[0];
      }
      else
      {
         UnmarshalledReferences refMap = useRefs ? new UnmarshalledReferences() : null;
         Object retValue = unmarshallObject(in, defaultClassLoader, refMap, false);
         if (trace) log.trace("Unmarshalled object " + retValue);
         return retValue;
      }
   }

   public void objectToObjectStream(Object o, ObjectOutputStream out, Fqn region) throws Exception
   {
      if (trace) log.trace("Marshalling object " + o);
      Map<Object, Integer> refMap = useRefs ? new IdentityHashMap<Object, Integer>() : null;
      ClassLoader toUse = defaultClassLoader;
      Thread current = Thread.currentThread();
      ClassLoader old = current.getContextClassLoader();
      if (old != null) toUse = old;

      try
      {
         if (useRegionBasedMarshalling) // got to check again in case this meth is called directly
         {
            if (trace) log.trace("Writing region " + region + " to stream");
            Region r = null;
            if (region != null) r = regionManager.getRegion(region, false);
            if (r != null && r.getClassLoader() != null) toUse = r.getClassLoader();
            current.setContextClassLoader(toUse);
            marshallObject(region, out, refMap);
         }
         else
         {
            current.setContextClassLoader(toUse);
         }
         marshallObject(o, out, refMap);
      }
      finally
      {
         current.setContextClassLoader(old);
      }
   }

   /**
    * @param in
    * @return a 2-object array.  The first one is the unmarshalled object and the 2nd is an Fqn that relates to the region used.  If region-based marshalling is not used, the 2nd value is null.
    * @throws Exception
    */
   protected Object[] objectFromObjectStreamRegionBased(ObjectInputStream in) throws Exception
   {
      UnmarshalledReferences refMap = useRefs ? new UnmarshalledReferences() : null;
      Object o = unmarshallObject(in, refMap);
      Fqn regionFqn = null;
      if (o == null)
      {
         // a null region.  Could happen.  Use std marshalling.
         log.trace("Unmarshalled region as null.  Not using a context class loader to unmarshall.");
      }
      else
      {
         regionFqn = (Fqn) o;
      }

      if (trace) log.trace("Unmarshalled regionFqn " + regionFqn + " from stream");

      Region region = null;
      Object[] retValue = {null, null};

      if (regionFqn != null)
      {
         region = findRegion(regionFqn);
      }
      if (region == null)
      {
         if (log.isDebugEnabled())
            log.debug("Region does not exist for Fqn " + regionFqn + " - not using a context classloader.");
         retValue[0] = unmarshallObject(in, defaultClassLoader, refMap, false);
      }
      else
      {
         retValue[0] = unmarshallObject(in, region.getClassLoader(), refMap, true);
         retValue[1] = regionFqn;
      }
      if (trace) log.trace("Unmarshalled object " + retValue[0] + " with region " + retValue[1]);
      return retValue;
   }

   private Region findRegion(Fqn fqn) throws InactiveRegionException
   {
      Region region = regionManager.getValidMarshallingRegion(fqn);

      if (region != null)
      {
         Status status = region.getStatus();
         if (status == Status.INACTIVATING || status == Status.INACTIVE)
         {
            if (log.isDebugEnabled())
            {
               throw new InactiveRegionException("Cannot unmarshall message for region " + fqn + ". This region is inactive.");
            }
            else
            {
               throw IRE;
            }
         }
      }
      else if (defaultInactive)
      {
         if (log.isDebugEnabled())
         {
            // No region but default inactive means region is inactive
            throw new InactiveRegionException("Cannot unmarshall message for region " + fqn + ". By default region " + fqn
                  + " is inactive.");
         }
         else
         {
            throw IRE;
         }
      }

      return region;
   }

   private Fqn extractFqnRegion(ReplicableCommand cmd) throws Exception
   {
      Fqn fqn = extractFqn(cmd);

      Region r = regionManager.getValidMarshallingRegion(fqn);
      return r == null ? null : r.getFqn();
   }

   // --------- Marshalling methods

   protected void marshallObject(Object o, ObjectOutputStream out, Map<Object, Integer> refMap) throws Exception
   {
      if (o == null)
      {
         out.writeByte(MAGICNUMBER_NULL);
      }
      else if (useRefs && refMap.containsKey(o))// see if this object has been marshalled before.
      {
         out.writeByte(MAGICNUMBER_REF);
         writeReference(out, refMap.get(o));
      }
      else if (o instanceof ReplicableCommand)
      {
         ReplicableCommand command = (ReplicableCommand) o;

         if (command.getCommandId() > -1)
         {
            out.writeByte(MAGICNUMBER_METHODCALL);
            marshallCommand(command, out, refMap);
         }
         else
         {
            throw new IllegalArgumentException("MethodCall does not have a valid method id.  Was this method call created with MethodCallFactory?");
         }
      }
      else if (o instanceof org.jgroups.blocks.MethodCall)
      {
         throw new IllegalArgumentException("Usage of a legacy MethodCall object!!");
      }
      else if (o instanceof MarshalledValue)
      {
         out.writeByte(MAGICNUMBER_MARSHALLEDVALUE);
         ((MarshalledValue) o).writeExternal(out);
      }
      else if (o instanceof Fqn)
      {
         out.writeByte(MAGICNUMBER_FQN);
         if (useRefs) writeReference(out, createReference(o, refMap));
         marshallFqn((Fqn) o, out, refMap);
      }
      else if (o instanceof GlobalTransaction)
      {
         out.writeByte(MAGICNUMBER_GTX);
         if (useRefs) writeReference(out, createReference(o, refMap));
         marshallGlobalTransaction((GlobalTransaction) o, out, refMap);
      }
      else if (o instanceof IpAddress)
      {
         out.writeByte(MAGICNUMBER_IPADDRESS);
         marshallIpAddress((IpAddress) o, out);
      }
      else if (o instanceof DefaultDataVersion)
      {
         out.writeByte(MAGICNUMBER_DEFAULT_DATA_VERSION);
         marshallDefaultDataVersion((DefaultDataVersion) o, out);
      }
      else if (o.getClass().equals(ArrayList.class))
      {
         out.writeByte(MAGICNUMBER_ARRAY_LIST);
         marshallCollection((Collection) o, out, refMap);
      }
      else if (o.getClass().equals(LinkedList.class))
      {
         out.writeByte(MAGICNUMBER_LINKED_LIST);
         marshallCollection((Collection) o, out, refMap);
      }
      else if (o.getClass().equals(HashMap.class))
      {
         out.writeByte(MAGICNUMBER_HASH_MAP);
         marshallMap((Map) o, out, refMap);
      }
      else if (o.getClass().equals(TreeMap.class))
      {
         out.writeByte(MAGICNUMBER_TREE_MAP);
         marshallMap((Map) o, out, refMap);
      }
      else if (o.getClass().equals(MapCopy.class))
      {
         out.writeByte(MAGICNUMBER_MAPCOPY);
         marshallMap((Map) o, out, refMap);
      }
      else if (o.getClass().equals(HashSet.class))
      {
         out.writeByte(MAGICNUMBER_HASH_SET);
         marshallCollection((Collection) o, out, refMap);
      }
      else if (o.getClass().equals(TreeSet.class))
      {
         out.writeByte(MAGICNUMBER_TREE_SET);
         marshallCollection((Collection) o, out, refMap);
      }
      else if (o instanceof Boolean)
      {
         out.writeByte(MAGICNUMBER_BOOLEAN);
         out.writeBoolean(((Boolean) o).booleanValue());
      }
      else if (o instanceof Integer)
      {
         out.writeByte(MAGICNUMBER_INTEGER);
         out.writeInt(((Integer) o).intValue());
      }
      else if (o instanceof Long)
      {
         out.writeByte(MAGICNUMBER_LONG);
         out.writeLong(((Long) o).longValue());
      }
      else if (o instanceof Short)
      {
         out.writeByte(MAGICNUMBER_SHORT);
         out.writeShort(((Short) o).shortValue());
      }
      else if (o instanceof String)
      {
         out.writeByte(MAGICNUMBER_STRING);
         if (useRefs) writeReference(out, createReference(o, refMap));
         marshallString((String) o, out);
      }
      else if (o instanceof NodeDataMarker)
      {
         out.writeByte(MAGICNUMBER_NODEDATA_MARKER);
         ((Externalizable) o).writeExternal(out);
      }
      else if (o instanceof NodeDataExceptionMarker)
      {
         out.writeByte(MAGICNUMBER_NODEDATA_EXCEPTION_MARKER);
         ((Externalizable) o).writeExternal(out);
      }
      else if (o instanceof NodeData)
      {
         out.writeByte(MAGICNUMBER_NODEDATA);
         ((Externalizable) o).writeExternal(out);
      }
      else if (o instanceof GravitateResult)
      {
         out.writeByte(MAGICNUMBER_GRAVITATERESULT);
         marshallGravitateResult((GravitateResult) o, out, refMap);
      }
      else if (o instanceof Serializable)
      {
         if (trace)
         {
            log.trace("Warning: using object serialization for " + o.getClass());
         }
         out.writeByte(MAGICNUMBER_SERIALIZABLE);
         if (useRefs) writeReference(out, createReference(o, refMap));
         out.writeObject(o);
      }
      else
      {
         throw new Exception("Don't know how to marshall object of type " + o.getClass());
      }
   }

   private void marshallGravitateResult(GravitateResult gravitateResult, ObjectOutputStream out, Map<Object, Integer> refMap) throws Exception
   {
      marshallObject(gravitateResult.isDataFound(), out, refMap);
      if (gravitateResult.isDataFound())
      {
         marshallObject(gravitateResult.getNodeData(), out, refMap);
         marshallObject(gravitateResult.getBuddyBackupFqn(), out, refMap);
      }

   }

   private int createReference(Object o, Map<Object, Integer> refMap)
   {
      int reference = refMap.size();
      refMap.put(o, reference);
      return reference;
   }

   protected void marshallString(String s, ObjectOutputStream out) throws Exception
   {
      //StringUtil.saveString(out, s);
      out.writeObject(s);
   }

   private void marshallCommand(ReplicableCommand command, ObjectOutputStream out, Map<Object, Integer> refMap) throws Exception
   {
      out.writeShort(command.getCommandId());
      Object[] args = command.getParameters();
      byte numArgs = (byte) (args == null ? 0 : args.length);
      out.writeByte(numArgs);

      for (int i = 0; i < numArgs; i++)
      {
         marshallObject(args[i], out, refMap);
      }
   }

   private void marshallGlobalTransaction(GlobalTransaction globalTransaction, ObjectOutputStream out, Map<Object, Integer> refMap) throws Exception
   {
      out.writeLong(globalTransaction.getId());
      marshallObject(globalTransaction.getAddress(), out, refMap);
   }


   protected void marshallFqn(Fqn fqn, ObjectOutputStream out, Map<Object, Integer> refMap) throws Exception
   {
      boolean isRoot = fqn.isRoot();
      out.writeBoolean(isRoot);
      if (!isRoot)
      {
         out.writeShort(fqn.size());
         for (Object o : fqn.peekElements())
         {
            marshallObject(o, out, refMap);
         }
      }
   }

   private void marshallIpAddress(IpAddress ipAddress, ObjectOutputStream out) throws Exception
   {
      ipAddress.writeExternal(out);
   }

   private void marshallCollection(Collection c, ObjectOutputStream out, Map refMap) throws Exception
   {
      writeUnsignedInt(out, c.size());
      for (Object o : c)
      {
         marshallObject(o, out, refMap);
      }
   }

   private void marshallMap(Map map, ObjectOutputStream out, Map<Object, Integer> refMap) throws Exception
   {
      int mapSize = map.size();
      writeUnsignedInt(out, mapSize);
      if (mapSize == 0) return;

      for (Map.Entry me : (Set<Map.Entry>) map.entrySet())
      {
         marshallObject(me.getKey(), out, refMap);
         marshallObject(me.getValue(), out, refMap);
      }
   }

   // --------- Unmarshalling methods

   protected Object unmarshallObject(ObjectInputStream in, ClassLoader loader, UnmarshalledReferences refMap, boolean overrideContextClassloaderOnThread) throws Exception
   {
      if (loader == null)
      {
         return unmarshallObject(in, refMap);
      }
      else
      {
         Thread currentThread = Thread.currentThread();
         ClassLoader old = currentThread.getContextClassLoader();
         try
         {
            // only do this if we haven't already set a context class loader elsewhere.
            if (overrideContextClassloaderOnThread || old == null) currentThread.setContextClassLoader(loader);
            return unmarshallObject(in, refMap);
         }
         finally
         {
            if (overrideContextClassloaderOnThread || old == null) currentThread.setContextClassLoader(old);
         }
      }
   }

   protected Object unmarshallObject(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      byte magicNumber = in.readByte();
      int reference = 0;
      Object retVal;
      switch (magicNumber)
      {
         case MAGICNUMBER_NULL:
            return null;
         case MAGICNUMBER_REF:
            if (useRefs)
            {
               reference = readReference(in);
               return refMap.getReferencedObject(reference);
            }
            else break;
         case MAGICNUMBER_SERIALIZABLE:
            if (useRefs) reference = readReference(in);
            retVal = in.readObject();
            if (useRefs) refMap.putReferencedObject(reference, retVal);
            return retVal;
         case MAGICNUMBER_MARSHALLEDVALUE:
            MarshalledValue mv = new MarshalledValue();
            mv.readExternal(in);
            return mv;
         case MAGICNUMBER_METHODCALL:
            retVal = unmarshallCommand(in, refMap);
            return retVal;
         case MAGICNUMBER_FQN:
            if (useRefs) reference = readReference(in);
            retVal = unmarshallFqn(in, refMap);
            if (useRefs) refMap.putReferencedObject(reference, retVal);
            return retVal;
         case MAGICNUMBER_GTX:
            if (useRefs) reference = readReference(in);
            retVal = unmarshallGlobalTransaction(in, refMap);
            if (useRefs) refMap.putReferencedObject(reference, retVal);
            return retVal;
         case MAGICNUMBER_IPADDRESS:
            retVal = unmarshallIpAddress(in);
            return retVal;
         case MAGICNUMBER_DEFAULT_DATA_VERSION:
            retVal = unmarshallDefaultDataVersion(in);
            return retVal;
         case MAGICNUMBER_ARRAY_LIST:
            return unmarshallArrayList(in, refMap);
         case MAGICNUMBER_LINKED_LIST:
            return unmarshallLinkedList(in, refMap);
         case MAGICNUMBER_HASH_MAP:
            return unmarshallHashMap(in, refMap);
         case MAGICNUMBER_TREE_MAP:
            return unmarshallTreeMap(in, refMap);
         case MAGICNUMBER_HASH_SET:
            return unmarshallHashSet(in, refMap);
         case MAGICNUMBER_TREE_SET:
            return unmarshallTreeSet(in, refMap);
         case MAGICNUMBER_MAPCOPY:
            return unmarshallMapCopy(in, refMap);
         case MAGICNUMBER_BOOLEAN:
            return in.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
         case MAGICNUMBER_INTEGER:
            return in.readInt();
         case MAGICNUMBER_LONG:
            return in.readLong();
         case MAGICNUMBER_SHORT:
            return in.readShort();
         case MAGICNUMBER_STRING:
            if (useRefs) reference = readReference(in);
            retVal = unmarshallString(in);
            if (useRefs) refMap.putReferencedObject(reference, retVal);
            return retVal;
         case MAGICNUMBER_NODEDATA_MARKER:
            retVal = new NodeDataMarker();
            ((NodeDataMarker) retVal).readExternal(in);
            return retVal;
         case MAGICNUMBER_NODEDATA_EXCEPTION_MARKER:
            retVal = new NodeDataExceptionMarker();
            ((NodeDataExceptionMarker) retVal).readExternal(in);
            return retVal;
         case MAGICNUMBER_NODEDATA:
            retVal = new NodeData();
            ((NodeData) retVal).readExternal(in);
            return retVal;
         case MAGICNUMBER_GRAVITATERESULT:
            return unmarshallGravitateResult(in, refMap);
         default:
            if (log.isErrorEnabled())
            {
               log.error("Unknown Magic Number " + magicNumber);
            }
            throw new Exception("Unknown magic number " + magicNumber);
      }
      throw new Exception("Unknown magic number " + magicNumber);
   }

   private GravitateResult unmarshallGravitateResult(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      Boolean found = (Boolean) unmarshallObject(in, refMap);
      if (found)
      {
         List<NodeData> stuff = (List<NodeData>) unmarshallObject(in, refMap);
         Fqn fqn = (Fqn) unmarshallObject(in, refMap);
         return GravitateResult.subtreeResult(stuff, fqn);
      }
      else
      {
         return GravitateResult.noDataFound();
      }
   }

   protected String unmarshallString(ObjectInputStream in) throws Exception
   {
      return (String) in.readObject();
   }

   private ReplicableCommand unmarshallCommand(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      short methodId = in.readShort();
      byte numArgs = in.readByte();
      Object[] args = null;

      if (numArgs > 0)
      {
         args = new Object[numArgs];

         for (int i = 0; i < numArgs; i++)
         {
            args[i] = unmarshallObject(in, refMap);
         }
      }

      return commandsFactory.fromStream(methodId, args);
   }

   private GlobalTransaction unmarshallGlobalTransaction(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      GlobalTransaction gtx = new GlobalTransaction();
      long id = in.readLong();
      Object address = unmarshallObject(in, refMap);
      gtx.setId(id);
      gtx.setAddress((Address) address);
      return gtx;
   }

   protected Fqn unmarshallFqn(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {

      boolean isRoot = in.readBoolean();
      Fqn fqn;
      if (!isRoot)
      {
         int numElements = in.readShort();
         List<Object> elements = new ArrayList<Object>(numElements);
         for (int i = 0; i < numElements; i++)
         {
            elements.add(unmarshallObject(in, refMap));
         }
         fqn = Fqn.fromList(elements, true);
      }
      else
      {
         fqn = Fqn.ROOT;
      }
      return fqn;
   }

   private IpAddress unmarshallIpAddress(ObjectInputStream in) throws Exception
   {
      IpAddress ipAddress = new IpAddress();
      ipAddress.readExternal(in);
      return ipAddress;
   }

   private List unmarshallArrayList(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      int listSize = readUnsignedInt(in);
      List list = new ArrayList(listSize);
      populateFromStream(in, refMap, list, listSize);
      return list;
   }

   private List unmarshallLinkedList(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      List list = new LinkedList();
      populateFromStream(in, refMap, list, readUnsignedInt(in));
      return list;
   }

   private Map unmarshallHashMap(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      Map map = new HashMap();
      populateFromStream(in, refMap, map);
      return map;
   }

   private Map unmarshallMapCopy(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      // read in as a HashMap first
      Map m = unmarshallHashMap(in, refMap);
      return new MapCopy(m);
   }

   private Map unmarshallTreeMap(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      Map map = new TreeMap();
      populateFromStream(in, refMap, map);
      return map;
   }

   private Set unmarshallHashSet(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      Set set = new HashSet();
      populateFromStream(in, refMap, set);
      return set;
   }

   private Set unmarshallTreeSet(ObjectInputStream in, UnmarshalledReferences refMap) throws Exception
   {
      Set set = new TreeSet();
      populateFromStream(in, refMap, set);
      return set;
   }

   private void populateFromStream(ObjectInputStream in, UnmarshalledReferences refMap, Map mapToPopulate) throws Exception
   {
      int size = readUnsignedInt(in);
      for (int i = 0; i < size; i++)
      {
         mapToPopulate.put(unmarshallObject(in, refMap), unmarshallObject(in, refMap));
      }
   }

   private void populateFromStream(ObjectInputStream in, UnmarshalledReferences refMap, Set setToPopulate) throws Exception
   {
      int size = readUnsignedInt(in);
      for (int i = 0; i < size; i++)
      {
         setToPopulate.add(unmarshallObject(in, refMap));
      }
   }

   private void populateFromStream(ObjectInputStream in, UnmarshalledReferences refMap, List listToPopulate, int listSize) throws Exception
   {
      for (int i = 0; i < listSize; i++)
      {
         listToPopulate.add(unmarshallObject(in, refMap));
      }
   }

   protected void marshallDefaultDataVersion(DefaultDataVersion ddv, ObjectOutputStream out) throws Exception
   {
      writeUnsignedLong(out, ddv.getRawVersion());
   }

   protected DefaultDataVersion unmarshallDefaultDataVersion(ObjectInputStream in) throws Exception
   {
      return new DefaultDataVersion(readUnsignedLong(in));
   }

   /**
    * Reads a reference from a given stream.
    *
    * @param in the stream to read from
    * @return an int representing a reference in RefMap.
    * @throws IOException propagated from the OIS
    */
   protected int readReference(ObjectInputStream in) throws IOException
   {
      return in.readShort();
   }

   /**
    * Writes a reference to a given object output stream.
    *
    * @param out       the stream to write to
    * @param reference the reference to write
    * @throws java.io.IOException propagated from the OOS
    */
   protected void writeReference(ObjectOutputStream out, int reference) throws IOException
   {
      out.writeShort(reference);
   }

   protected int readUnsignedInt(ObjectInputStream in) throws IOException
   {
      return in.readInt();
   }

   protected void writeUnsignedInt(ObjectOutputStream out, int i) throws IOException
   {
      out.writeInt(i);
   }

   protected long readUnsignedLong(ObjectInputStream in) throws IOException
   {
      return in.readLong();
   }

   protected void writeUnsignedLong(ObjectOutputStream out, long i) throws IOException
   {
      out.writeLong(i);
   }
}

