package org.zstack.core.keyvalue;
import net.sf.cglib.proxy.*;
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.StringDSL;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
*/
public class KeyValueEntityProxy<T> {
private static Map<Class, Enhancer> enhancers = new HashMap<Class, Enhancer>();
public class KeyValueMapProxy {
private Object proxyMap;
private void makePath() {
paths.add(StringUtils.join(trace, "."));
trace.clear();
}
KeyValueMapProxy(final Class valueType, final String fieldName, final List<String> paths, final Stack<String> trace) {
Enhancer e = getEnhancer(Map.class);
proxyMap = e.create();
Factory f = (Factory) proxyMap;
f.setCallback(0, new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (!"get".equals(method.getName())) {
throw new CloudRuntimeException(String.format("only get() can be called on KeyValueMapProxy, current path is %s", StringUtils.join(paths, ".")));
}
String key = (String) objects[0];
if (key == null) {
trace.push(String.format("%s[\"%%\"]", fieldName));
} else {
trace.push(String.format("%s[\"%s\"]", fieldName, key));
}
if (KeyValueUtils.isPrimitiveTypeForKeyValue(valueType)) {
makePath();
return null;
}
return new KeyValueEntityProxy(valueType, paths, trace).getProxyEntity();
}
});
}
public Object getProxyMap() {
return proxyMap;
}
}
public class KeyValueListProxy {
private Object proxyList;
private void makePath() {
paths.add(StringUtils.join(trace, "."));
trace.clear();
}
KeyValueListProxy(final Class valueType, final String fieldName, final List<String> paths, final Stack<String> trace) {
Enhancer e = getEnhancer(List.class);
proxyList = e.create();
Factory f = (Factory) proxyList;
f.setCallback(0 ,new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
if (!"get".equals(method.getName())) {
throw new CloudRuntimeException(String.format("only get() can be called on KeyValueListProxy, current path is %s", StringUtils.join(paths, ".")));
}
Integer index = (Integer) objects[0];
if (index == -1) {
trace.push(String.format("%s[%%]", fieldName));
} else {
trace.push(String.format("%s[%s]", fieldName, index));
}
if (KeyValueUtils.isPrimitiveTypeForKeyValue(valueType)) {
makePath();
return null;
}
return new KeyValueEntityProxy(valueType, paths, trace).getProxyEntity();
}
});
}
public Object getProxyList() {
return proxyList;
}
}
private T proxyEntity;
private List<String> paths;
private Stack<String> trace;
private static Enhancer getEnhancer(Class clz) {
if (Enhancer.isEnhanced(clz)) {
clz = clz.getSuperclass();
}
Enhancer e = enhancers.get(clz);
if (e == null) {
e = new Enhancer();
e.setSuperclass(clz);
e.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
throw new CloudRuntimeException("should not be here");
}
});
enhancers.put(clz, e);
}
return e;
}
private Field getFieldFromMethod(Object o, Method method) {
DebugUtils.Assert(method.getName().startsWith("get"), String.format("only getter can be called on KeyValueEntityProxy, but %s is called", method.getName()));
String fieldName = StringDSL.stripStart(method.getName(), "get");
fieldName = StringUtils.uncapitalize(fieldName);
Field f = FieldUtils.getField(fieldName, o.getClass());
DebugUtils.Assert(f != null, String.format("cannot find field[%s] on class[%s], %s is a wrong getter", fieldName, o.getClass().getName(), method.getName()));
return f;
}
private void makePath() {
paths.add(StringUtils.join(trace, "."));
trace.clear();
}
private Object doInvoke(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Field f = getFieldFromMethod(o, method);
if (KeyValueUtils.isPrimitiveTypeForKeyValue(f.getType())) {
trace.push(f.getName());
makePath();
return methodProxy.invokeSuper(o, objects);
}
if (Map.class.isAssignableFrom(f.getType())) {
return handleMap(f);
}
if (List.class.isAssignableFrom(f.getType())) {
return handleList(f);
}
DebugUtils.Assert(!Collection.class.isAssignableFrom(f.getType()), String.format("collection can only be List, but get %s", f.getType().getName()));
return handleObject(f, o);
}
private Object handleObject(Field f, Object o) {
trace.push(f.getName());
return new KeyValueEntityProxy(o.getClass(), paths, trace).getProxyEntity();
}
private Object handleList(Field f) {
CollectionGenericType type = (CollectionGenericType) FieldUtils.inferGenericTypeOnMapOrCollectionField(f);
return new KeyValueListProxy(type.getValueType(), f.getName(), paths, trace).getProxyList();
}
private Object handleMap(Field f) {
MapGenericType type = (MapGenericType) FieldUtils.inferGenericTypeOnMapOrCollectionField(f);
return new KeyValueMapProxy(type.getValueType(), f.getName(), paths, trace).getProxyMap();
}
private void createEnhancer(Class<T> entityClass) {
Enhancer enhancer = getEnhancer(entityClass);
proxyEntity = (T) enhancer.create();
Factory f = (Factory) proxyEntity;
f.setCallback(0, new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return doInvoke(o, method, objects, methodProxy);
}
});
}
public KeyValueEntityProxy(Class<T> entityClass) {
paths = new ArrayList<String>();
trace = new Stack<String>();
createEnhancer(entityClass);
}
private KeyValueEntityProxy(Class<T> entityClass, List<String> paths, Stack<String> trace) {
this.paths = paths;
this.trace = trace;
createEnhancer(entityClass);
}
public T getProxyEntity() {
return proxyEntity;
}
public List<String> getPaths() {
return paths;
}
}