/** * 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.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.beans.PropertyStyle; import org.joda.convert.StringConvert; import org.threeten.bp.ZonedDateTime; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.OpenGammaClock; /** * Builds an HTML page containing a description of a type's attributes. It's intended to help with the development * of the blotter, not to be a long-term feature of the platform (hence the hacky nature of it). * Its output is used to populate bean-structure.ftl. * TODO decide if this is worth keeping and clean it up or delete it when it's not useful any more */ /* package */ class BeanStructureBuilder implements BeanVisitor<Map<String, Object>> { 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 StringConvert _stringConvert; private final Map<String, Object> _beanData = Maps.newHashMap(); private final BeanHierarchy _beanHierarchy; private final List<Map<String, Object>> _propertyData = Lists.newArrayList(); private final Map<Class<?>, Class<?>> _underlyingSecurityTypes; private final Map<Class<?>, String> _endpoints; /* package */ BeanStructureBuilder(Set<MetaBean> metaBeans, Map<Class<?>, Class<?>> underlyingSecurityTypes, Map<Class<?>, String> endpoints, StringConvert stringConvert) { ArgumentChecker.notNull(underlyingSecurityTypes, "underlyingSecurityTypes"); ArgumentChecker.notNull(metaBeans, "metaBeans"); ArgumentChecker.notNull(endpoints, "endpoints"); ArgumentChecker.notNull(stringConvert, "stringConvert"); _endpoints = endpoints; _underlyingSecurityTypes = underlyingSecurityTypes; _beanHierarchy = new BeanHierarchy(metaBeans); _stringConvert = stringConvert; } @Override public void visitMetaBean(MetaBean metaBean) { _beanData.clear(); String typeName = metaBean.beanType().getSimpleName(); _beanData.put("type", typeName); Map<String, Object> typeProperty = Maps.newHashMap(); typeProperty.put("name", "type"); typeProperty.put("type", PropertyType.SINGLE.name().toLowerCase()); typeProperty.put("types", ImmutableList.of(typeInfo("string", null, null, false))); typeProperty.put("optional", false); typeProperty.put("readOnly", false); typeProperty.put("value", typeName); _propertyData.add(typeProperty); Class<?> underlyingType = _underlyingSecurityTypes.get(metaBean.beanType()); if (underlyingType != null) { _beanData.put("underlyingTypes", typesFor(underlyingType)); } } @Override public void visitBeanProperty(MetaProperty<?> property, BeanTraverser traverser) { Class<?> type = property.propertyType(); _propertyData.add(property(property, typesFor(type), null, PropertyType.SINGLE)); } @Override public void visitCollectionProperty(MetaProperty<?> property, BeanTraverser traverser) { _propertyData.add(arrayType(property)); } @Override public void visitSetProperty(MetaProperty<?> property, BeanTraverser traverser) { _propertyData.add(arrayType(property)); } @Override public void visitListProperty(MetaProperty<?> property, BeanTraverser traverser) { _propertyData.add(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); _propertyData.add(property(property, typesFor(keyType), typesFor(valueType), PropertyType.MAP)); } @Override public void visitProperty(MetaProperty<?> property, BeanTraverser traverser) { _propertyData.add(property(property, typesFor(property.propertyType()), null, PropertyType.SINGLE)); } @Override public Map<String, Object> finish() { _beanData.put("properties", _propertyData); _beanData.put("now", ZonedDateTime.now(OpenGammaClock.getInstance())); return _beanData; } private Map<String, Object> arrayType(MetaProperty<?> property) { return property(property, typesFor(property.propertyType()), null, PropertyType.ARRAY); } private boolean isConvertible(Class<?> type) { boolean canConvert; try { _stringConvert.findConverter(type); canConvert = true; } catch (Exception e) { canConvert = false; } return canConvert; } private static boolean isNullable(MetaProperty<?> property) { if (property.propertyType().isPrimitive()) { return false; } else { PropertyDefinition definitionAnnotation = property.annotation(PropertyDefinition.class); return !definitionAnnotation.validate().equals("notNull"); } } /* package */ List<Map<String, Object>> typesFor(Class<?> type) { String typeName = s_types.get(type); if (typeName != null) { return ImmutableList.of(typeInfo(typeName, null, null, false)); } else { boolean canConvert; canConvert = isConvertible(type); if (canConvert) { return ImmutableList.of(typeInfo(STRING, type.getSimpleName(), _endpoints.get(type), false)); } else { // TODO deal with (potentially multiple) bean types Set<Class<? extends Bean>> subtypes = _beanHierarchy.subtypes(type); if (subtypes.isEmpty()) { throw new OpenGammaRuntimeException("No type mapping found for class " + type.getName()); } List<Map<String, Object>> types = Lists.newArrayListWithCapacity(subtypes.size()); for (Class<? extends Bean> subtype : subtypes) { types.add(typeInfo(subtype.getSimpleName(), null, _endpoints.get(subtype), true)); } return types; } } } /* package */ static Map<String, Object> property(MetaProperty<?> property, List<Map<String, Object>> types, List<Map<String, Object>> valueTypes, PropertyType propertyType) { Map<String, Object> result = Maps.newHashMap(); // TODO this is *really* dirty and not supposed to be anything else. fix or remove boolean readOnly = property.style() == PropertyStyle.READ_ONLY || property.name().equals("uniqueId"); // TODO this is obviously a poor choice of names result.put("type", propertyType.name().toLowerCase()); result.put("types", types); result.put("name", property.name()); result.put("optional", isNullable(property)); result.put("readOnly", readOnly); if (valueTypes != null) { result.put("valueTypes", types); } return result; } // TODO replace with strings public enum PropertyType { SINGLE, ARRAY, MAP } /* package */ static Map<String, Object> typeInfo(String expectedType, String actualType, String endpoint, boolean isBeanType) { Map<String, Object> results = Maps.newHashMap(); results.put("expectedType", expectedType); results.put("actualType", actualType); results.put("endpoint", endpoint); results.put("beanType", isBeanType); return results; } }