package org.zstack.header.message;
import org.apache.commons.lang.StringUtils;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.GsonTransient;
import org.zstack.header.message.NoJsonSchema;
import org.zstack.utils.FieldUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import static java.util.Arrays.asList;
/**
* Created by xing5 on 2016/12/12.
*/
public class JsonSchemaBuilder {
Object object;
private LinkedHashMap<String, String> schema = new LinkedHashMap<>();
public JsonSchemaBuilder(Object object) {
this.object = object;
}
private boolean isSkip(Field f) {
return f.isAnnotationPresent(NoJsonSchema.class) || Modifier.isStatic(f.getModifiers())
|| f.isAnnotationPresent(GsonTransient.class);
}
private void build(Object o, Stack<String> paths) throws IllegalAccessException {
List<Field> fields = FieldUtils.getAllFields(o.getClass());
for (Field f : fields) {
if (isSkip(f)) {
continue;
}
f.setAccessible(true);
Object value = f.get(o);
if (value == null) {
// null value
continue;
}
if (value.getClass().getCanonicalName().startsWith("java.")) {
// for JRE classes, only deal with Collection and Map
if (value instanceof Collection) {
Collection c = (Collection) value;
Class gtype = FieldUtils.getGenericType(f);
if (gtype != null && !gtype.getName().startsWith("java.")) {
int i = 0;
for (Object co : c) {
paths.push(String.format("%s[%s]", f.getName(), i++));
build(co, paths);
paths.pop();
}
}
} else if (value instanceof Map) {
Class gtype = FieldUtils.getGenericType(f);
if (gtype != null && !gtype.getName().startsWith("java.")) {
for (Object me : ((Map) value).entrySet()) {
Map.Entry e = (Map.Entry) me;
paths.push(String.format("%s.%s", f.getName(), e.getKey().toString()));
build(e.getValue(), paths);
paths.pop();
}
}
}
// don't record standard JRE classes
} else if (value.getClass().getCanonicalName().startsWith("org.zstack")) {
paths.push(f.getName());
build(value, paths);
paths.pop();
}
}
if (!paths.isEmpty()) {
schema.put(StringUtils.join(paths, "."), o.getClass().getName());
}
}
// support Map and org.zstack.* objects
public Map<String, String> build() {
try {
if (!object.getClass().getName().startsWith("org.zstack") && !(object instanceof Map)) {
throw new CloudRuntimeException(String.format("only a org.zstack.* object can be built schema, %s is not", object.getClass()));
}
if (object instanceof Map) {
Map m = (Map) object;
for (Object o : m.entrySet()) {
Map.Entry e = (Map.Entry) o;
if (e.getValue() == null) {
continue;
}
if (e.getValue().getClass().getName().startsWith("java.") &&
!Collection.class.isAssignableFrom(e.getValue().getClass())) {
continue;
}
if (Collection.class.isAssignableFrom(e.getValue().getClass())) {
Collection c = (Collection) e.getValue();
int i = 0;
for (Object it : c) {
Stack<String> path = new Stack<>();
path.add(String.format("%s[%s]", e.getKey(), i++));
build(it, path);
}
} else {
build(e.getValue(), new Stack<String>() {
{
add(e.getKey().toString());
}
});
}
}
} else {
build(object, new Stack<>());
}
List<String> keys = new ArrayList<>(schema.keySet());
Collections.reverse(keys);
LinkedHashMap ret = new LinkedHashMap(schema.size());
for (String key : keys) {
ret.put(key, schema.get(key));
}
return ret;
} catch (IllegalAccessException e) {
throw new CloudRuntimeException(e);
}
}
}