/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.util.fudgemsg;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.fudgemsg.FudgeField;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeRuntimeException;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeBuilder;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.fudgemsg.types.IndicatorType;
import org.fudgemsg.wire.types.FudgeWireType;
import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.collect.TreeMultimap;
/**
* Builder to convert DirectBean to and from Fudge.
*
* @param <T> the bean type
*/
public final class DirectBeanFudgeBuilder<T extends Bean> implements FudgeBuilder<T> {
/**
* The meta bean for this instance.
*/
private final MetaBean _metaBean;
/**
* Creates a builder from a class, using reflection to find the meta-bean.
* @param <R> the bean type
* @param cls the class to get the builder for, not null
* @return the bean builder, not null
*/
public static <R extends Bean> DirectBeanFudgeBuilder<R> of(final Class<R> cls) {
MetaBean meta = JodaBeanUtils.metaBean(cls);
return new DirectBeanFudgeBuilder<R>(meta);
}
/**
* Constructor.
* @param metaBean the meta-bean, not null
*/
public DirectBeanFudgeBuilder(MetaBean metaBean) {
_metaBean = metaBean;
}
//-------------------------------------------------------------------------
// TODO: FudgeFieldName and Ordinal annotations
@Override
public MutableFudgeMsg buildMessage(FudgeSerializer serializer, T bean) {
try {
MutableFudgeMsg msg = serializer.newMessage();
for (MetaProperty<?> prop : bean.metaBean().metaPropertyIterable()) {
if (prop.style().isReadable()) {
Object obj = prop.get(bean);
if (obj instanceof List<?>) {
MutableFudgeMsg subMsg = buildMessageCollection(serializer, prop, bean.getClass(), (List<?>) obj);
msg.add(prop.name(), null, FudgeWireType.SUB_MESSAGE, subMsg);
} else if (obj instanceof Set<?>) {
MutableFudgeMsg subMsg = buildMessageCollection(
serializer, prop, bean.getClass(), new ArrayList<Object>((Set<?>) obj));
msg.add(prop.name(), null, FudgeWireType.SUB_MESSAGE, subMsg);
} else if (obj instanceof Map<?, ?>) {
MutableFudgeMsg subMsg = buildMessageMap(serializer, bean.getClass(), prop, (Map<?, ?>) obj);
msg.add(prop.name(), null, FudgeWireType.SUB_MESSAGE, subMsg);
} else if (obj instanceof Multimap<?, ?>) {
MutableFudgeMsg subMsg = buildMessageMultimap(serializer, bean.getClass(), prop, (Multimap<?, ?>) obj);
msg.add(prop.name(), null, FudgeWireType.SUB_MESSAGE, subMsg);
} else {
serializer.addToMessageWithClassHeaders(msg, prop.name(), null, obj, prop.propertyType()); // ignores null
}
}
}
return msg;
} catch (RuntimeException ex) {
throw new FudgeRuntimeException("Unable to serialize: " + _metaBean.beanName(), ex);
}
}
private MutableFudgeMsg buildMessageCollection(
FudgeSerializer serializer, MetaProperty<?> prop, Class<?> beanType, List<?> list) {
Class<?> contentType = JodaBeanUtils.collectionType(prop, beanType);
MutableFudgeMsg msg = serializer.newMessage();
for (Object entry : list) {
if (entry == null) {
msg.add(null, null, FudgeWireType.INDICATOR, IndicatorType.INSTANCE);
} else if (contentType == null) {
serializer.addToMessage(msg, null, null, entry);
} else {
serializer.addToMessageWithClassHeaders(msg, null, null, entry, contentType);
}
}
return msg;
}
private MutableFudgeMsg buildMessageMap(FudgeSerializer serializer, Class<?> beanType, MetaProperty<?> prop,
Map<?, ?> map) {
return buildMessageMapFromEntries(map.entrySet(), serializer, beanType, prop);
}
private MutableFudgeMsg buildMessageMultimap(FudgeSerializer serializer, Class<?> beanType, MetaProperty<?> prop,
Multimap<?, ?> multimap) {
return buildMessageMapFromEntries(multimap.entries(), serializer, beanType, prop);
}
private MutableFudgeMsg buildMessageMapFromEntries(Collection<? extends Map.Entry<?, ?>> entries,
FudgeSerializer serializer,
Class<?> beanType,
MetaProperty<?> prop) {
Class<?> keyType = JodaBeanUtils.mapKeyType(prop, beanType);
Class<?> valueType = JodaBeanUtils.mapValueType(prop, beanType);
MutableFudgeMsg msg = serializer.newMessage();
for (Map.Entry<?, ?> entry : entries) {
if (entry.getKey() == null) {
msg.add(null, 1, FudgeWireType.INDICATOR, IndicatorType.INSTANCE);
} else if (keyType == null) {
serializer.addToMessage(msg, null, 1, entry.getKey());
} else {
serializer.addToMessageWithClassHeaders(msg, null, 1, entry.getKey(), keyType);
}
if (entry.getValue() == null) {
msg.add(null, 2, FudgeWireType.INDICATOR, IndicatorType.INSTANCE);
} else if (valueType == null) {
serializer.addToMessage(msg, null, 2, entry.getValue());
} else {
serializer.addToMessageWithClassHeaders(msg, null, 2, entry.getValue(), valueType);
}
}
return msg;
}
//-------------------------------------------------------------------------
@SuppressWarnings("unchecked")
@Override
public T buildObject(FudgeDeserializer deserializer, FudgeMsg msg) {
try {
BeanBuilder<T> builder = (BeanBuilder<T>) _metaBean.builder();
for (MetaProperty<?> mp : _metaBean.metaPropertyIterable()) {
if (mp.style().isBuildable()) {
final FudgeField field = msg.getByName(mp.name());
if (field != null) {
Object value = null;
if (List.class.isAssignableFrom(mp.propertyType())) {
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectList(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value);
}
} else if (SortedSet.class.isAssignableFrom(mp.propertyType())) {
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectSet(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value, new TreeSet<>());
}
} else if (Set.class.isAssignableFrom(mp.propertyType())) {
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectSet(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value, new LinkedHashSet<>());
}
} else if (Map.class.isAssignableFrom(mp.propertyType())) {
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectMap(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value);
}
} else if (ListMultimap.class.isAssignableFrom(mp.propertyType())) {
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectMultimap(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value, ArrayListMultimap.create());
}
} else if (SortedSetMultimap.class.isAssignableFrom(mp.propertyType())) {
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectMultimap(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value, TreeMultimap.create());
}
} else if (Multimap.class.isAssignableFrom(mp.propertyType())) {
// In the absence of other information we'll create a hash multimap
value = field.getValue();
if (value instanceof FudgeMsg) {
value = buildObjectMultimap(
deserializer, mp, _metaBean.beanType(), (FudgeMsg) value, HashMultimap.create());
}
}
if (value == null) {
try {
if (mp.propertyType() == Object.class) {
value = deserializer.fieldValueToObject(field);
} else {
value = deserializer.fieldValueToObject(mp.propertyType(), field);
}
} catch (IllegalArgumentException ex) {
if (field.getValue() instanceof String == false) {
throw ex;
}
value = JodaBeanUtils.stringConverter().convertFromString(mp.propertyType(), (String) field.getValue());
}
}
if (value != null || mp.propertyType().isPrimitive() == false) {
builder.set(mp.name(), value);
}
}
}
}
return builder.build();
} catch (RuntimeException ex) {
throw new FudgeRuntimeException("Unable to deserialize: " + _metaBean.beanName(), ex);
}
}
private List<Object> buildObjectList(FudgeDeserializer deserializer, MetaProperty<?> prop, Class<?> type, FudgeMsg msg) {
Class<?> contentType = JodaBeanUtils.collectionType(prop, type);
List<Object> list = new ArrayList<Object>(); // should be List<contentType>
for (FudgeField field : msg) {
if (field.getOrdinal() != null && field.getOrdinal() != 1) {
throw new IllegalArgumentException("Sub-message doesn't contain a list (bad field " + field + ")");
}
Object obj = buildField(deserializer, contentType, field);
list.add((obj instanceof IndicatorType) ? null : obj);
}
return list;
}
private Set<Object> buildObjectSet(FudgeDeserializer deserializer, MetaProperty<?> prop, Class<?> type, FudgeMsg msg, Set<Object> set) {
Class<?> contentType = JodaBeanUtils.collectionType(prop, type);
for (FudgeField field : msg) {
if (field.getOrdinal() != null && field.getOrdinal() != 1) {
throw new IllegalArgumentException("Sub-message doesn't contain a set (bad field " + field + ")");
}
Object obj = buildField(deserializer, contentType, field);
set.add((obj instanceof IndicatorType) ? null : obj);
}
return set;
}
private Map<Object, Object> buildObjectMap(FudgeDeserializer deserializer, MetaProperty<?> prop, Class<?> type, FudgeMsg msg) {
Class<?> keyType = JodaBeanUtils.mapKeyType(prop, type);
Class<?> valueType = JodaBeanUtils.mapValueType(prop, type);
Map<Object, Object> map; // should be Map<keyType,contentType>
if (SortedMap.class.isAssignableFrom(prop.propertyType())) {
map = new TreeMap<>();
} else {
map = Maps.newHashMap();
}
Queue<Object> keys = new LinkedList<>();
Queue<Object> values = new LinkedList<>();
for (FudgeField field : msg) {
if (field.getOrdinal() == 1) {
Object fieldValue = buildField(deserializer, keyType, field);
if (fieldValue instanceof IndicatorType) {
fieldValue = null;
}
if (values.isEmpty()) {
// no values ready, so store the key till next time
keys.add(fieldValue);
} else {
// store key along with next value
map.put(fieldValue, values.remove());
}
} else if (field.getOrdinal() == 2) {
Object fieldValue = buildField(deserializer, valueType, field);
if (fieldValue instanceof IndicatorType) {
fieldValue = null;
}
if (keys.isEmpty()) {
// no keys ready, so store the value till next time
values.add(fieldValue);
} else {
// store value along with next key
map.put(keys.remove(), fieldValue);
}
} else {
throw new IllegalArgumentException("Sub-message doesn't contain a map (bad field " + field + ")");
}
}
return map;
}
@SuppressWarnings({"unchecked", "rawtypes" })
private Multimap<Object, Object> buildObjectMultimap(FudgeDeserializer deserializer,
MetaProperty<?> prop,
Class<?> type,
FudgeMsg msg,
Multimap multimap) {
Class<?> keyType = JodaBeanUtils.mapKeyType(prop, type);
Class<?> valueType = JodaBeanUtils.mapValueType(prop, type);
Queue<Object> keys = new LinkedList<>();
Queue<Object> values = new LinkedList<>();
for (FudgeField field : msg) {
if (field.getOrdinal() == 1) {
Object fieldValue = buildField(deserializer, keyType, field);
if (fieldValue instanceof IndicatorType) {
fieldValue = null;
}
if (values.isEmpty()) {
// no values ready, so store the key till next time
keys.add(fieldValue);
} else {
// store key along with next value
multimap.put(fieldValue, values.remove());
}
} else if (field.getOrdinal() == 2) {
Object fieldValue = buildField(deserializer, valueType, field);
if (fieldValue instanceof IndicatorType) {
fieldValue = null;
}
if (keys.isEmpty()) {
// no keys ready, so store the value till next time
values.add(fieldValue);
} else {
// store value along with next key
multimap.put(keys.remove(), fieldValue);
}
} else {
throw new IllegalArgumentException("Sub-message doesn't contain a map (bad field " + field + ")");
}
}
return multimap;
}
private Object buildField(FudgeDeserializer deserializer, Class<?> type, FudgeField field) {
if (isAbstractLike(type)) {
return deserializer.fieldValueToObject(field);
}
return deserializer.fieldValueToObject(type, field);
}
private boolean isAbstractLike(Class<?> type) {
return type == null ||
type.isInterface() ||
(Modifier.isAbstract(type.getModifiers()) && type.isPrimitive() == false) ||
type == Object.class;
}
}