/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.web.analytics.blotter;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.beans.Bean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.beans.PropertyDefinition;
import org.joda.convert.StringConvert;
import org.json.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.util.ArgumentChecker;
/**
* TODO can this be deleted?
* TODO how should read-only properties be handled? I guess it depends on the use case of the data
*/
// TODO do this as HTML, easier to consume
/* package */ class JsonBeanStructureVisitor implements BeanVisitor<JSONObject> {
private static final Map<Class<?>, String> s_types = Maps.newHashMap();
private static final String NUMBER = "number";
private static final String BOOLEAN = "boolean";
private static final String STRING = "string";
static {
s_types.put(Double.TYPE, NUMBER);
s_types.put(Double.class, NUMBER);
s_types.put(Float.TYPE, NUMBER);
s_types.put(Float.class, NUMBER);
s_types.put(Long.TYPE, NUMBER);
s_types.put(Long.class, NUMBER);
s_types.put(Short.TYPE, NUMBER);
s_types.put(Short.class, NUMBER);
s_types.put(Integer.TYPE, NUMBER);
s_types.put(Integer.class, NUMBER);
s_types.put(Byte.TYPE, NUMBER);
s_types.put(Byte.class, NUMBER);
s_types.put(BigDecimal.class, NUMBER);
s_types.put(Boolean.TYPE, BOOLEAN);
s_types.put(Boolean.class, BOOLEAN);
s_types.put(Character.TYPE, STRING);
s_types.put(Character.class, STRING);
s_types.put(String.class, STRING);
}
private final Map<String, Object> _json = Maps.newHashMap();
private final BeanHierarchy _beanHierarchy;
private final StringConvert _stringConvert;
/* package */ JsonBeanStructureVisitor(Set<MetaBean> metaBeans) {
ArgumentChecker.notNull(metaBeans, "metaBeans");
_beanHierarchy = new BeanHierarchy(metaBeans);
// TODO parameter for this
_stringConvert = JodaBeanUtils.stringConverter();
}
@Override
public void visitMetaBean(MetaBean metaBean) {
// TODO configurable field name
_json.clear();
_json.put("type", metaBean.beanType().getSimpleName());
}
@Override
public void visitBeanProperty(MetaProperty<?> property, BeanTraverser traverser) {
Set<Class<? extends Bean>> argumentTypes = _beanHierarchy.subtypes(property.propertyType());
if (argumentTypes.isEmpty()) {
throw new OpenGammaRuntimeException("No bean types are available to satisfy property " + property);
}
List<String> beanTypeNames = Lists.newArrayListWithCapacity(argumentTypes.size());
for (Class<? extends Bean> argumentType : argumentTypes) {
beanTypeNames.add(argumentType.getSimpleName());
}
_json.put(property.name(), optional(property, StringUtils.join(beanTypeNames, "|")));
}
@Override
public void visitCollectionProperty(MetaProperty<?> property, BeanTraverser traverser) {
_json.put(property.name(), arrayType(property));
}
@Override
public void visitSetProperty(MetaProperty<?> property, BeanTraverser traverser) {
_json.put(property.name(), arrayType(property));
}
@Override
public void visitListProperty(MetaProperty<?> property, BeanTraverser traverser) {
_json.put(property.name(), arrayType(property));
}
@Override
public void visitMapProperty(MetaProperty<?> property, BeanTraverser traverser) {
Class<? extends Bean> beanType = property.metaBean().beanType();
Class<?> keyType = JodaBeanUtils.mapKeyType(property, beanType);
Class<?> valueType = JodaBeanUtils.mapValueType(property, beanType);
_json.put(property.name(), optional(property, "{" + typeFor(keyType) + ":" + typeFor(valueType) + "}"));
}
@Override
public void visitProperty(MetaProperty<?> property, BeanTraverser traverser) {
_json.put(property.name(), optional(property, typeFor(property)));
}
private static String optional(MetaProperty<?> property, String type) {
if (nullable(property)) {
return type + "?";
} else {
return type;
}
}
@Override
public JSONObject finish() {
return new JSONObject(_json);
}
private String arrayType(MetaProperty<?> property) {
return optional(property, "[" + typeFor(property.propertyType()) + "]");
}
private String typeFor(MetaProperty<?> property) {
return typeFor(property.propertyType());
}
private String typeFor(Class<?> type) {
String typeName = s_types.get(type);
if (typeName != null) {
return typeName;
} else {
try {
_stringConvert.findConverter(type);
return STRING;
} catch (Exception e) {
throw new OpenGammaRuntimeException("No type mapping found for class " + type.getName(), e);
}
}
}
private static boolean nullable(MetaProperty<?> property) {
if (property.propertyType().isPrimitive()) {
return false;
} else {
PropertyDefinition definitionAnnotation = property.annotation(PropertyDefinition.class);
return !definitionAnnotation.validate().equals("notNull");
}
}
}