package br.com.caelum.vraptor.serialization.xstream;
import static com.google.common.base.Preconditions.checkNotNull;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import net.vidageek.mirror.dsl.Mirror;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
public class Serializee {
private Object root;
private Class<?> rootClass;
private final Multimap<String, Class<?>> includes = LinkedListMultimap.create();
private final Multimap<String, Class<?>> excludes = LinkedListMultimap.create();
private Set<Class<?>> elementTypes;
private boolean recursive;
public Object getRoot() {
return root;
}
public void setRoot(Object root) {
this.root = root;
}
public Class<?> getRootClass() {
return rootClass;
}
public void setRootClass(Class<?> rootClass) {
this.rootClass = rootClass;
}
public Multimap<String, Class<?>> getIncludes() {
return includes;
}
public Multimap<String, Class<?>> getExcludes() {
return excludes;
}
public Set<Class<?>> getElementTypes() {
return elementTypes;
}
public void setElementTypes(Set<Class<?>> elementTypes) {
this.elementTypes = elementTypes;
}
public boolean isRecursive() {
return recursive;
}
public void setRecursive(boolean recursive) {
this.recursive = recursive;
}
public void excludeAll(String... names) {
for (String name : names) {
excludes.putAll(name, getParentTypesFor(name));
}
}
public void excludeAll() {
Set<Class<?>> types = new HashSet<Class<?>>();
if (isCollection(getRootClass()))
types.addAll(getElementTypes());
else
types.add(getRootClass());
for (Class<?> type : types) {
for(Field field : new Mirror().on(type).reflectAll().fields()) {
excludes.putAll(field.getName(), getParentTypes(field.getName(), type));
}
}
}
public void includeAll(String... names) {
for (String name : names) {
includes.putAll(name, getParentTypesFor(name));
}
}
private Set<Class<?>> getParentTypesFor(String name) {
if (getElementTypes() == null) {
Class<?> type = getRootClass();
return getParentTypes(name, type);
} else {
Set<Class<?>> result = new HashSet<Class<?>>();
for (Class<?> type : getElementTypes()) {
result.addAll(getParentTypes(name, type));
}
return result;
}
}
private Set<Class<?>> getParentTypes(String name, Class<?> type) {
String[] path = name.split("\\.");
try {
for (int i = 0; i < path.length - 1; i++) {
Field field = checkNotNull(new Mirror().on(type).reflect().field(path[i]));
type = getActualType(field.getGenericType());
}
checkNotNull(new Mirror().on(type).reflect().field(path[path.length -1]));
} catch (NullPointerException e) {
throw new IllegalArgumentException("Field path '" + name + "' doesn't exists in " + type, e);
}
Set<Class<?>> types = Sets.newHashSet();
while (type != Object.class) {
types.add(type);
type = type.getSuperclass();
}
return types;
}
static Class<?> getActualType(Type genericType) {
if (genericType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) genericType;
if (isCollection(type)) {
Type actualType = type.getActualTypeArguments()[0];
if (actualType instanceof TypeVariable<?>) {
return (Class<?>) type.getRawType();
}
return (Class<?>) actualType;
}
}
return (Class<?>) genericType;
}
private static boolean isCollection(Type type) {
if (type instanceof ParameterizedType) {
ParameterizedType ptype = (ParameterizedType) type;
return Collection.class.isAssignableFrom((Class<?>) ptype.getRawType())
|| Map.class.isAssignableFrom((Class<?>) ptype.getRawType());
}
return Collection.class.isAssignableFrom((Class<?>) type);
}
}