package com.vercer.engine.persist.translator;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import com.google.appengine.api.datastore.Blob;
import com.google.common.collect.Lists;
import com.vercer.engine.persist.Path;
import com.vercer.engine.persist.Property;
import com.vercer.engine.persist.PropertyTranslator;
import com.vercer.engine.persist.Path.Part;
import com.vercer.engine.persist.conversion.DefaultTypeConverter.BlobToAnything;
import com.vercer.engine.persist.conversion.DefaultTypeConverter.SerializableToBlob;
import com.vercer.engine.persist.standard.StrategyObjectDatastore;
import com.vercer.engine.persist.util.SimpleProperty;
import com.vercer.engine.persist.util.SinglePropertySet;
import com.vercer.engine.persist.util.generic.GenericTypeReflector;
public class ListTranslator extends DecoratingTranslator
{
public ListTranslator(PropertyTranslator chained)
{
super(chained);
}
public Object propertiesToTypesafe(final Set<Property> properties, final Path path, Type type)
{
// only handle lists
if (!GenericTypeReflector.erase(type).isAssignableFrom(ArrayList.class))
{
// pass on all other types down the chain
return chained.propertiesToTypesafe(properties, path, type);
}
if (properties.isEmpty())
{
return NULL_VALUE;
}
// need to adapt a set of property lists into a list of property sets
List<Set<Property>> propertySets = Lists.newArrayList();
boolean complete = false;
for (int index = 0; !complete; index++)
{
complete = true;
Set<Property> result = new HashSet<Property>(properties.size());
for (Property property : properties)
{
List<?> values;
Path itemPath = property.getPath();
Object list = property.getValue();
// every property should be of the same type but just repeat check
if (list instanceof List<?>)
{
// we have a list of property values
values = (List<?>) list;
}
else
{
// we could not handle this value so pass the whole thing down the line
return chained.propertiesToTypesafe(properties, path, type);
}
if (values.size() > index)
{
Object value = values.get(index);
// null values are place holders for missing properties
if (value != null)
{
result.add(new SimpleProperty(itemPath, value, true));
}
// at least one property has items so we are not done yet
complete = false;
}
}
if (complete == false)
{
propertySets.add(result);
}
}
// handles the tricky task of finding what type of list we have
Type exact = GenericTypeReflector.getExactSuperType(type, List.class);
Type componentType = ((ParameterizedType) exact).getActualTypeArguments()[0];
// decode each item of the list
List<Object> objects = new ArrayList<Object>();
for (Set<Property> itemProperties : propertySets)
{
Object convertedChild = chained.propertiesToTypesafe(itemProperties, path, componentType);
// if we cannot convert every member of the list we fail
if (convertedChild == null)
{
return null;
}
objects.add(convertedChild);
}
// result will be converted to actual collection or array type
return objects;
}
public Set<Property> typesafeToProperties(Object object, Path path, final boolean indexed)
{
if (object instanceof List<?>)
{
List<?> list = (List<?>) object;
if (list.isEmpty())
{
return Collections.emptySet();
}
final Map<Path, List<Object>> lists = new HashMap<Path, List<Object>>(8);
int count = 0;
for (Object item : list)
{
if (item != null)
{
Set<Property> properties = chained.typesafeToProperties(item, path, indexed);
if (properties == null)
{
// we could not handle so continue up the chain
return chained.typesafeToProperties(object, path, indexed);
}
for (Property property : properties)
{
Object value = property.getValue();
Path itemPath = property.getPath();
List<Object> values = lists.get(itemPath);
if (values == null)
{
values = new ArrayList<Object>(4);
lists.put(itemPath, values);
}
// need to pad the list with nulls if any values are missing
while (values.size() < count)
{
values.add(null);
}
values.add(value);
}
}
else
{
Collection<List<Object>> values = lists.values();
for (List<Object> value : values)
{
value.add(null);
}
}
count++;
}
// optimise for case of single properties
if (lists.size() == 1)
{
Path childPath = lists.keySet().iterator().next();
List<?> values = lists.get(childPath);
return new SinglePropertySet(childPath, values, indexed);
}
else
{
return new AbstractSet<Property>()
{
@Override
public Iterator<Property> iterator()
{
return new Iterator<Property>()
{
Iterator<Entry<Path, List<Object>>> iterator = lists.entrySet().iterator();
public boolean hasNext()
{
return iterator.hasNext();
}
public Property next()
{
Entry<Path, List<Object>> next = iterator.next();
return new SimpleProperty(next.getKey(), next.getValue(), indexed);
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
@Override
public int size()
{
return lists.size();
}
};
}
}
else
{
// we could not handle value as a collection so continue up the chain
return chained.typesafeToProperties(object, path, indexed);
}
}
}