package org.zstack.core.keyvalue;
import org.apache.commons.lang.StringUtils;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.utils.DebugUtils;
import org.zstack.utils.FieldUtils;
import org.zstack.utils.FieldUtils.CollectionGenericType;
import org.zstack.utils.FieldUtils.MapGenericType;
import org.zstack.utils.TypeUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.*;
import java.util.Map.Entry;
/**
*/
public class KeyValueSerializer {
private List<KeyValueStruct> ret = new ArrayList<KeyValueStruct>();
private Stack<String> trace = new Stack<String>();
private Stack<Object> paths = new Stack<Object>();
private boolean canDeserialize(Class type) {
return KeyValueUtils.isPrimitiveTypeForKeyValue(type);
}
private Object getValue(Field f, Object obj) throws IllegalAccessException {
f.setAccessible(true);
return f.get(obj);
}
private void take(Field f, Object obj) throws IllegalAccessException {
f.setAccessible(true);
Object val = f.get(obj);
ret.add(new KeyValueStruct(makePath(), val.toString(), val.getClass()));
}
private void take(Object obj) {
ret.add(new KeyValueStruct(makePath(), obj.toString(), obj.getClass()));
}
private boolean isNullValue(Field f, Object obj) throws IllegalAccessException {
return getValue(f, obj) == null;
}
private void buildObject(Object obj) throws IllegalAccessException {
if (paths.contains(obj)) {
paths.push(obj);
throw new CloudRuntimeException(String.format("recursive object graph: %s", StringUtils.join(paths, " --> ")));
}
paths.push(obj);
List<Field> fs = FieldUtils.getAllFields(obj.getClass());
for (Field f : fs) {
if (Modifier.isStatic(f.getModifiers())) {
continue;
}
Object val = getValue(f, obj);
if (val == null) {
continue;
}
if (canDeserialize(f.getType())) {
trace.push(f.getName());
take(f, obj);
trace.pop();
continue;
}
if (Map.class.isAssignableFrom(f.getType())) {
buildMap(f, obj);
continue;
}
if (Collection.class.isAssignableFrom(f.getType())) {
buildList(f, obj);
continue;
}
trace.push(f.getName());
buildObject(val);
trace.pop();
}
paths.pop();
}
private void buildList(Field f, Object obj) throws IllegalAccessException {
CollectionGenericType type = (CollectionGenericType) FieldUtils.inferGenericTypeOnMapOrCollectionField(f);
DebugUtils.Assert(List.class.isAssignableFrom(f.getType()), String.format("Collection must be List, but %s is %s", makePath(), type.getValueType().getName()));
DebugUtils.Assert(type.isInferred(), String.format("Collection must use Generic, %s is not", makePath()));
if (isNullValue(f, obj)) {
return;
}
Object value = getValue(f, obj);
List col = (List) value;
if (col.isEmpty()) {
return;
}
if (paths.contains(value)) {
paths.push(value);
throw new CloudRuntimeException(String.format("recursive object graph: %s", StringUtils.join(paths, " --> ")));
}
paths.push(value);
for (Object item : col) {
String itemName = String.format("%s[%s]", f.getName(), col.indexOf(item));
trace.push(itemName);
if (canDeserialize(type.getValueType())) {
take(item);
} else {
buildObject(item);
}
trace.pop();
}
paths.pop();
}
public List<KeyValueStruct> build(Object entity) {
try {
buildObject(entity);
return ret;
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}
private String makePath() {
return StringUtils.join(trace, ".");
}
private void buildMap(Field f, Object obj) throws IllegalAccessException {
MapGenericType type = (MapGenericType) FieldUtils.inferGenericTypeOnMapOrCollectionField(f);
DebugUtils.Assert(type.isInferred(), String.format("Map must use Generic where key is type of String and value is not type of Map or Collection"));
DebugUtils.Assert(type.getKeyType() == String.class, String.format("Map must use String as key, but %s use %s", makePath(), type.getKeyType().getName()));
DebugUtils.Assert(type.getNestedGenericValue() == null, String.format("Map cannot have nested map or collection, %s", makePath()));
if (isNullValue(f, obj)) {
return;
}
Object value = getValue(f, obj);
Map map = (Map) value;
if (map.isEmpty()) {
return;
}
if (paths.contains(value)) {
paths.push(value);
throw new CloudRuntimeException(String.format("recursive object graph: %s", StringUtils.join(paths, " --> ")));
}
paths.push(obj);
Iterator<Entry> it = map.entrySet().iterator();
while (it.hasNext()) {
Entry e = it.next();
String key = e.getKey().toString();
Object item = e.getValue();
String itemName = String.format("%s[\"%s\"]", f.getName(), key);
trace.push(itemName);
if (canDeserialize(type.getValueType())) {
take(item);
} else {
buildObject(item);
}
trace.pop();
}
paths.pop();
}
}