/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.fudgemsg;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.fudgemsg.FudgeField;
import org.fudgemsg.FudgeMsg;
import org.fudgemsg.FudgeMsgFactory;
import org.fudgemsg.MutableFudgeMsg;
import org.fudgemsg.mapping.FudgeBuilder;
import org.fudgemsg.mapping.FudgeDeserializer;
import org.fudgemsg.mapping.FudgeSerializer;
import org.fudgemsg.mapping.GenericFudgeBuilderFor;
import org.fudgemsg.types.IndicatorType;
import org.fudgemsg.wire.types.FudgeWireType;
import com.opengamma.financial.currency.AbstractCurrencyMatrix;
import com.opengamma.financial.currency.CurrencyMatrix;
import com.opengamma.financial.currency.CurrencyMatrixValue;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixCross;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixFixed;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixValueRequirement;
import com.opengamma.financial.currency.CurrencyMatrixValueVisitor;
import com.opengamma.id.UniqueId;
import com.opengamma.util.money.Currency;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Fudge builder for a {@link CurrencyMatrix}. This handles the general case - matrices may typically be sparse so
* there may be more efficient encodings possible. In those cases, serialize and add class headers directly.
*/
@GenericFudgeBuilderFor(CurrencyMatrix.class)
public class CurrencyMatrixFudgeBuilder implements FudgeBuilder<CurrencyMatrix> {
/** Field name. */
public static final String UNIQUE_ID_FIELD_NAME = "uniqueId";
/** Field name. */
public static final String FIXED_RATE_FIELD_NAME = "fixedRate";
/** Field name. */
public static final String VALUE_REQUIREMENTS_FIELD_NAME = "valueReq";
/** Field name. */
public static final String CROSS_CONVERT_FIELD_NAME = "crossConvert";
private static MutableFudgeMsg getOrCreateMessage(final FudgeMsgFactory factory, final String name, final Map<String, MutableFudgeMsg> map) {
MutableFudgeMsg msg = map.get(name);
if (msg == null) {
msg = factory.newMessage();
map.put(name, msg);
}
return msg;
}
private static FudgeMsg mapToMessage(final FudgeMsgFactory factory, final Map<String, MutableFudgeMsg> map) {
final MutableFudgeMsg msg = factory.newMessage();
for (final Map.Entry<String, MutableFudgeMsg> entry : map.entrySet()) {
msg.add(entry.getKey(), null, FudgeWireType.SUB_MESSAGE, entry.getValue());
}
return msg;
}
@Override
public MutableFudgeMsg buildMessage(final FudgeSerializer serializer, final CurrencyMatrix object) {
// Inverses are only written if they are not the expected calculated value. This happens (17% empirically) due to
// rounding errors on fixed rates and we don't want the matrix to degrade after repeated serialization/deserialization.
final MutableFudgeMsg msg = serializer.newMessage();
msg.add(0, CurrencyMatrix.class.getName());
final Collection<Currency> sourceCurrencies = object.getSourceCurrencies();
final Collection<Currency> targetCurrencies = object.getTargetCurrencies();
final Map<String, MutableFudgeMsg> fixedValues = new HashMap<String, MutableFudgeMsg>();
final Map<String, MutableFudgeMsg> crossValues = new HashMap<String, MutableFudgeMsg>();
final Map<String, MutableFudgeMsg> reqValues = new HashMap<String, MutableFudgeMsg>();
for (final Currency sourceCurrency : sourceCurrencies) {
final String sourceISO = sourceCurrency.getCode();
for (final Currency targetCurrency : targetCurrencies) {
final String targetISO = targetCurrency.getCode();
final int cmp = sourceISO.compareTo(targetISO);
if (cmp == 0) {
continue;
}
final CurrencyMatrixValue value = object.getConversion(sourceCurrency, targetCurrency);
if (value == null) {
continue;
}
final boolean suppressInverse;
if (targetCurrencies.contains(sourceCurrency) && sourceCurrencies.contains(targetCurrency)) {
final CurrencyMatrixValue inverse = object.getConversion(targetCurrency, sourceCurrency);
if (inverse == null) {
suppressInverse = true;
} else {
if (cmp < 0) {
suppressInverse = !value.getReciprocal().equals(inverse);
} else {
if (inverse.getReciprocal().equals(value)) {
continue;
}
suppressInverse = true;
}
}
} else {
suppressInverse = true;
}
value.accept(new CurrencyMatrixValueVisitor<Void>() {
@Override
public Void visitCross(final CurrencyMatrixCross cross) {
final MutableFudgeMsg entries = getOrCreateMessage(serializer, cross.getCrossCurrency().getCode(), crossValues);
if (suppressInverse) {
final MutableFudgeMsg subMsg = serializer.newMessage();
subMsg.add(targetISO, null, FudgeWireType.INDICATOR, IndicatorType.INSTANCE);
entries.add(sourceISO, null, FudgeWireType.SUB_MESSAGE, subMsg);
} else {
entries.add(sourceISO, null, FudgeWireType.STRING, targetISO);
}
return null;
}
@Override
public Void visitFixed(final CurrencyMatrixFixed fixedValue) {
final MutableFudgeMsg entries = getOrCreateMessage(serializer, sourceISO, fixedValues);
entries.add(targetISO, null, FudgeWireType.DOUBLE, fixedValue.getFixedValue());
if (suppressInverse) {
entries.add(targetISO, null, FudgeWireType.INDICATOR, IndicatorType.INSTANCE);
}
return null;
}
@Override
public Void visitValueRequirement(final CurrencyMatrixValueRequirement valueRequirement) {
final MutableFudgeMsg entries = getOrCreateMessage(serializer, sourceISO, reqValues);
serializer.addToMessage(entries, targetISO, null, valueRequirement);
if (suppressInverse) {
entries.add(targetISO, null, FudgeWireType.INDICATOR, IndicatorType.INSTANCE);
}
return null;
}
});
}
}
if (!fixedValues.isEmpty()) {
msg.add(FIXED_RATE_FIELD_NAME, null, FudgeWireType.SUB_MESSAGE, mapToMessage(serializer, fixedValues));
}
if (!reqValues.isEmpty()) {
msg.add(VALUE_REQUIREMENTS_FIELD_NAME, null, FudgeWireType.SUB_MESSAGE, mapToMessage(serializer, reqValues));
}
if (!crossValues.isEmpty()) {
msg.add(CROSS_CONVERT_FIELD_NAME, null, FudgeWireType.SUB_MESSAGE, mapToMessage(serializer, crossValues));
}
serializer.addToMessage(msg, UNIQUE_ID_FIELD_NAME, null, object.getUniqueId());
return msg;
}
private static class MatrixImpl extends AbstractCurrencyMatrix {
private void loadFixed(final FudgeMsg message) {
final Map<Pair<Currency, Currency>, CurrencyMatrixValue> values = new HashMap<Pair<Currency, Currency>, CurrencyMatrixValue>();
for (final FudgeField field : message) {
final Currency source = Currency.of(field.getName());
final FudgeMsg message2 = message.getFieldValue(FudgeMsg.class, field);
for (final FudgeField field2 : message2) {
final Currency target = Currency.of(field2.getName());
if (field2.getValue() instanceof Double) {
final CurrencyMatrixValue value = CurrencyMatrixValue.of((Double) field2.getValue());
values.put(Pairs.of(source, target), value);
values.put(Pairs.of(target, source), value.getReciprocal());
} else {
values.remove(Pairs.of(target, source));
}
}
for (final Map.Entry<Pair<Currency, Currency>, CurrencyMatrixValue> valueEntry : values.entrySet()) {
addConversion(valueEntry.getKey().getFirst(), valueEntry.getKey().getSecond(), valueEntry.getValue());
}
values.clear();
}
}
private void loadReq(final FudgeDeserializer deserializer, final FudgeMsg message) {
final Map<Pair<Currency, Currency>, CurrencyMatrixValue> values = new HashMap<Pair<Currency, Currency>, CurrencyMatrixValue>();
for (final FudgeField field : message) {
final Currency source = Currency.of(field.getName());
for (final FudgeField field2 : message.getFieldValue(FudgeMsg.class, field)) {
final Currency target = Currency.of(field2.getName());
if (field2.getValue() instanceof FudgeMsg) {
final CurrencyMatrixValue value = deserializer.fieldValueToObject(CurrencyMatrixValueRequirement.class, field2);
values.put(Pairs.of(source, target), value);
values.put(Pairs.of(target, source), value.getReciprocal());
} else {
values.remove(Pairs.of(target, source));
}
}
for (final Map.Entry<Pair<Currency, Currency>, CurrencyMatrixValue> valueEntry : values.entrySet()) {
addConversion(valueEntry.getKey().getFirst(), valueEntry.getKey().getSecond(), valueEntry.getValue());
}
values.clear();
}
}
private void loadCross(final FudgeMsg message) {
final Map<Pair<Currency, Currency>, CurrencyMatrixValue> values = new HashMap<Pair<Currency, Currency>, CurrencyMatrixValue>();
for (final FudgeField field : message) {
final CurrencyMatrixValue cross = CurrencyMatrixValue.of(Currency.of(field.getName()));
for (final FudgeField field2 : (FudgeMsg) field.getValue()) {
final Currency source = Currency.of(field2.getName());
if (field2.getValue() instanceof FudgeMsg) {
final Currency target = Currency.of(((FudgeMsg) field2.getValue()).iterator().next().getName());
values.put(Pairs.of(source, target), cross);
} else {
final Currency target = Currency.of((String) field2.getValue());
values.put(Pairs.of(source, target), cross);
values.put(Pairs.of(target, source), cross);
}
}
for (final Map.Entry<Pair<Currency, Currency>, CurrencyMatrixValue> valueEntry : values.entrySet()) {
addConversion(valueEntry.getKey().getFirst(), valueEntry.getKey().getSecond(), valueEntry.getValue());
}
values.clear();
}
}
}
@Override
public CurrencyMatrix buildObject(final FudgeDeserializer deserializer, final FudgeMsg message) {
final MatrixImpl matrix = new MatrixImpl();
FudgeField field = message.getByName(UNIQUE_ID_FIELD_NAME);
if (field != null) {
matrix.setUniqueId(deserializer.fieldValueToObject(UniqueId.class, field));
}
field = message.getByName(CROSS_CONVERT_FIELD_NAME);
if (field != null) {
matrix.loadCross(message.getFieldValue(FudgeMsg.class, field));
}
field = message.getByName(FIXED_RATE_FIELD_NAME);
if (field != null) {
matrix.loadFixed(message.getFieldValue(FudgeMsg.class, field));
}
field = message.getByName(VALUE_REQUIREMENTS_FIELD_NAME);
if (field != null) {
matrix.loadReq(deserializer, message.getFieldValue(FudgeMsg.class, field));
}
return matrix;
}
}