package student.web.internal.converters;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.Vector;
import org.webcat.diff.DiffList;
import org.webcat.diff.DiffPatcher;
import org.webcat.diff.Differ;
import org.webcat.diff.PatchApplication;
import student.web.internal.PersistentStorageManager.FakePrintWriter;
import student.web.internal.Snapshot;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.collections.AbstractCollectionConverter;
import com.thoughtworks.xstream.converters.collections.TreeMapConverter;
import com.thoughtworks.xstream.core.JVM;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;
/**
* Converts most common Collections (Lists and Sets) to XML, specifying a nested
* element for each item.
* <p/>
* <p>
* Supports java.util.ArrayList, java.util.HashSet, java.util.LinkedList,
* java.util.Vector and java.util.LinkedHashSet.
* </p>
*
* @author Joe Walnes
*/
public class CollectionConverter extends AbstractCollectionConverter
{
// private ReflectionProvider rp;
private TreeMapConverter mapConverter;
/**
* Create a new CollectionConverter.
*
* @param mapper
* The mapper to use.
*/
public CollectionConverter( Mapper mapper )
{
super( mapper );
mapConverter = new TreeMapConverter( mapper );
// this.rp = rp;
}
@SuppressWarnings("rawtypes")
public boolean canConvert( Class type )
{
if ( type == null )
return false;
return type.equals( ArrayList.class )
|| type.equals( HashSet.class )
|| type.equals( LinkedList.class )
|| type.equals( Vector.class )
|| ( JVM.is14() && type.getName()
.equals( "java.util.LinkedHashSet" ) );
}
private class ObjectIdComparator implements Comparator<Object>
{
public int compare( Object o1, Object o2 )
{
if ( o1 == null || o2 == null )
return -1;
if ( !o1.getClass().equals( o2.getClass() ) )
return -1;
UUID id1 = Snapshot.lookupId( o1, false );
UUID id2 = Snapshot.lookupId( o2, false );
if ( id1 != null && id1.equals( id2 ) )
return 0;
return -1;
}
}
public void marshal(
Object source,
HierarchicalStreamWriter writer,
MarshallingContext context )
{
UUID collectionId = Snapshot.lookupId( source, false );
// Convert the source into a list for processing
List<Object> localCollection = convertToList( source );
marshal0( collectionId, source, writer, context, localCollection );
// If we are writing this list for real, then update the baseline
// because all future changes are going to be against whatever we put in
// there.
if ( !( writer instanceof FakePrintWriter ) )
{
Collection<Object> baseLine = new ArrayList<Object>();
baseLine.addAll( localCollection );
Snapshot.getLocal().resolveObject( collectionId,
localCollection,
baseLine );
}
}
protected void marshal0(
UUID collectionId,
Object source,
HierarchicalStreamWriter writer,
MarshallingContext context,
List<Object> localCollection )
{
// The Result of processing if any is required.
List<Object> patchedSnapshot = new ArrayList<Object>();
// Check if this is a new list. If so, merge with existing lists.
if ( collectionId != null )
{
// Grab what we saw from the persistence layer the very first time
// we saw it.
Object baseCollection = Snapshot.getLocal()
.getBaseObject( collectionId );
// Convert the collection into a list.
List<Object> baseList = convertToList( baseCollection );
// If the base list is null then we know we have never seen this
// list before.
if ( baseList == null )
baseList = Collections.<Object> emptyList();
// Get the newest version of this collection we have ever seen.
Object newestCollection = Snapshot.getNewest()
.findObject( collectionId );
// Convert the newest version of this collection to a list.
List<Object> newestList = convertToList( newestCollection );
// If there is no newest list set it to the base list.
if ( newestList == null )
newestList = baseList;
// See what has changed in the list locally.
Differ<Object> localDiff = new Differ<Object>( baseList,
localCollection,
new ObjectIdComparator() );
// Compute Differences
DiffList<Object> diffList = localDiff.getDifferences();
// Prepare a patcher to patch the new local items onto the most
// current list from the store.
DiffPatcher<Object> patcher = new DiffPatcher<Object>( baseList,
diffList,
new ObjectIdComparator() );
// Patching app result
PatchApplication<Object> patchResult = patcher.apply( newestList );
// The actual result list!
patchedSnapshot = patchResult.getResult();
// Back to the marshaling stuff. Here I write the id as an attribute
}
else
{
collectionId = Snapshot.lookupId( source, true );
if ( localCollection != null )
patchedSnapshot.addAll( localCollection );
}
writer.addAttribute( XMLConstants.ID_ATTRIBUTE, collectionId.toString() );
// Clear out the old collection. I am going to load it up with the new
// values
localCollection.clear();
if ( source instanceof Collection )
( (Collection<?>)source ).clear();
for ( Iterator<Object> iterator = patchedSnapshot.iterator(); iterator.hasNext();
/* no increment needed */)
{
Object item = iterator.next();
UUID objId = Snapshot.lookupId( item, false );
if ( objId != null )
{
Object localVersion = Snapshot.getLocal().findObject( objId );
if ( localVersion != null )
item = localVersion;
}
localCollection.add( item );
if ( source instanceof Collection )
{
@SuppressWarnings("unchecked")
Collection<Object> sourceCollection =
(Collection<Object>)source;
sourceCollection.add( item );
}
objId = Snapshot.lookupId( item, true );
ExtendedHierarchicalStreamWriterHelper.startNode( writer,
"_item",
null );
writer.addAttribute( XMLConstants.ID_ATTRIBUTE, objId.toString() );
if ( item instanceof NullableClass )
( (NullableClass)item ).writeHiddenClass( mapConverter,
writer,
context );
else
writeItem( item, context, writer );
writer.endNode();
}
}
private List<Object> convertToList( Object source )
{
if ( source == null )
{
return null;
}
if (source instanceof Collection)
{
@SuppressWarnings("unchecked")
Collection<Object> col = (Collection<Object>)source;
List<Object> result = new ArrayList<Object>();
result.addAll( col );
return result;
}
if ( source.getClass().isArray() )
{
List<Object> computed = new ArrayList<Object>();
for ( int i = 0; i < Array.getLength( source ); i++ )
{
Object item = Array.get( source, i );
computed.add( item );
}
return computed;
}
return null;
}
public Object unmarshal(
HierarchicalStreamReader reader,
UnmarshallingContext context )
{
// Snapshot local = Snapshot.getLocal();
Class<?> type = context.getRequiredType();
UUID id = UUID.fromString( reader.getAttribute( XMLConstants.ID_ATTRIBUTE ) );
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>)createCollection( type );
Collection<Object> baseCollection = new ArrayList<Object>();
populateCollection( reader, context, collection, baseCollection );
Snapshot.getLocal().resolveObject( id, collection, baseCollection );
return collection;
}
// protected void populateArray(HierarchicalStreamReader reader,
// UnmarshallingContext context, Object collection) {
// int i =0;
// while (reader.hasMoreChildren()) {
// Object item = readItem(reader, context, collection);
// Array.set(collection, i, item);
// resetLevel(reader);
// i++;
// }
// }
protected void populateCollection(
HierarchicalStreamReader reader,
UnmarshallingContext context,
Collection<Object> collection,
Collection<Object> baseCollection )
{
while ( reader.hasMoreChildren() )
{
Object item = readItemInCollection( reader, context, collection );
collection.add( item );
if ( baseCollection != null )
baseCollection.add( item );
resetLevel( reader );
}
}
private void resetLevel( HierarchicalStreamReader reader )
{
reader.moveUp();
reader.moveUp();
}
private Object readItemInCollection(
HierarchicalStreamReader reader,
UnmarshallingContext context,
Collection<Object> collection )
{
reader.moveDown();
String idAttr = reader.getAttribute( XMLConstants.ID_ATTRIBUTE );
UUID id = null;
id = UUID.fromString( idAttr );
Object item = null;
if ( reader.hasMoreChildren() )
{
reader.moveDown();
item = readItem( reader, context, collection );
// If after the read, the item isnt id'd (AKA it is a primitive)
// then grab the id stored in the _item tag
UUID readGenId = Snapshot.lookupId( item, false );
if ( readGenId == null )
Snapshot.getLocal().resolveObject( id,
item,
(Map<String, Object>)null );
}
return item;
}
}