/** * Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.web.analytics.formatting; import static com.opengamma.web.analytics.formatting.ResultsFormatter.CurrencyDisplay.DISPLAY_CURRENCY; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.opengamma.engine.calcnode.MissingValue; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.util.ClassMap; import com.opengamma.web.analytics.ValueTypes; /** * Formats analytics data for display in a grid in the user interface or for transmitting to the client as history. * Data structures bigger than a single value are encoded as JSON. */ @SuppressWarnings("rawtypes") public class ResultsFormatter { /** * Enum indicating whether the currency code should be included in the output * when values are formatted. */ public enum CurrencyDisplay { /** * Include the currency code in formatted outputs (if one is available). */ DISPLAY_CURRENCY, /** * Do not include the currency code in formatted outputs. */ SUPPRESS_CURRENCY } /** * Marker value returned to indicate there is no formatted value available for a combination of value, formatter * and inline key. This can happen for cells displaying inline values where the underlying value has no entry * for the column's key. It is also possible for complex values (e.g. vectors) where the history is stored * but can only be formatted when displayed inline as single values. */ public static final Object VALUE_UNAVAILABLE = new Object() { /** * @return An empty string - this means the grid shows an empty cell for an unavailable value. */ @Override public String toString() { return ""; } }; private static final Logger s_logger = LoggerFactory.getLogger(ResultsFormatter.class); /** For formatting null values. */ private final TypeFormatter _nullFormatter = new NullFormatter(); /** For formatting values with no specific formatter. */ private final TypeFormatter _defaultFormatter = new DefaultFormatter(); /** Formatters keyed on the type of value they can format. */ private final ClassMap<TypeFormatter<?>> _formatters = new ClassMap<>(); /** Formatter for values whose type isn't know in advance or whose type can changes between calculation cycles. */ private final UnknownTypeFormatter _unknownTypeFormatter = new UnknownTypeFormatter(); public ResultsFormatter() { this(DISPLAY_CURRENCY); } public ResultsFormatter(CurrencyDisplay currencyDisplay) { BigDecimalFormatter bigDecimalFormatter = new BigDecimalFormatter(currencyDisplay); DoubleFormatter doubleFormatter = new DoubleFormatter(bigDecimalFormatter); CurrencyAmountFormatter currencyAmountFormatter = new CurrencyAmountFormatter(currencyDisplay, bigDecimalFormatter); ZonedDateTimeFormatter zonedDateTimeFormatter = new ZonedDateTimeFormatter(); LocalDateDoubleTimeSeriesFormatter localDateDoubleTimeSeriesFormatter = new LocalDateDoubleTimeSeriesFormatter(); RateFormatter rateFormatter = new RateFormatter(); BasisPointsFormatter basisPointFormatter = new BasisPointsFormatter(); addFormatters(doubleFormatter, bigDecimalFormatter, currencyAmountFormatter, zonedDateTimeFormatter, localDateDoubleTimeSeriesFormatter, new YieldCurveFormatter(), new PriceIndexCurveFormatter(), new ISDACompliantYieldCurveFormatter(), new ISDACompliantCurveFormatter(), new NodalObjectsCurveFormatter(), //TODO is not a general formatter - used only for (Tenor, Double) curves new VolatilityCubeDataFormatter(), new VolatilitySurfaceDataFormatter(), new VolatilitySurfaceFormatter(), new LabelledMatrix1DFormatter(doubleFormatter), new LocalDateLabelledMatrix1DFormatter(doubleFormatter), new LabelledMatrix2DFormatter(doubleFormatter), new LabelledMatrix3DFormatter(), new TenorLabelledLocalDateDoubleTimeSeriesMatrix1DFormatter(localDateDoubleTimeSeriesFormatter), new TenorFormatter(), new MultipleCurrencyAmountFormatter(doubleFormatter), new MissingInputFormatter(), new MissingOutputFormatter(), new ForwardCurveFormatter(), new BlackVolatilitySurfaceMoneynessFormatter(), new LocalVolatilitySurfaceMoneynessFormatter(), new BucketedGreekResultCollectionFormatter(), new DoublesCurveFormatter(), new HistoricalTimeSeriesFormatter(), new DoubleArrayFormatter(), new DoubleObjectArrayFormatter(), new FudgeMsgFormatter(), new ListDoubleArrayFormatter(), new PresentValueForexBlackVolatilitySensitivityFormatter(), new SnapshotDataBundleFormatter(doubleFormatter), new InterpolatedYieldCurveSpecificationWithSecuritiesFormatter(), new HistoricalTimeSeriesBundleFormatter(), new VolatilitySurfaceSpecificationFormatter(), new CurrencyPairsFormatter(), new NodeTargetFormatter(), new PositionTargetFormatter(), new FungibleTradeTargetFormatter(), new OtcTradeTargetFormatter(), new BlackVolatilitySurfaceMoneynessFcnBackedByGridFormatter(), new FrequencyFormatter(), new FXAmountsFormatter(doubleFormatter), new ExpiryFormatter(zonedDateTimeFormatter), new ValuePropertiesFormatter(), new FixedPaymentMatrixFormatter(currencyAmountFormatter), new FloatingPaymentMatrixFormatter(currencyAmountFormatter), new FixedSwapLegDetailsFormatter(new CurrencyAmountFormatter(CurrencyDisplay.SUPPRESS_CURRENCY, bigDecimalFormatter), rateFormatter), new FloatingSwapLegDetailsFormatter(new CurrencyAmountFormatter(CurrencyDisplay.SUPPRESS_CURRENCY, bigDecimalFormatter), rateFormatter, basisPointFormatter), new FXMatrixFormatter(), new YieldCurveDataFormatter(doubleFormatter)); } private void addFormatters(TypeFormatter<?>... formatters) { for (TypeFormatter<?> formatter : formatters) { _formatters.put(formatter.getType(), formatter); } } private TypeFormatter getFormatter(Object value, ValueSpecification valueSpec) { if (value == null) { return _nullFormatter; } else if (isError(value) || valueSpec == null) { return getFormatterForType(value.getClass()); } else { Class<?> type = ValueTypes.getTypeForValueName(valueSpec.getValueName()); if (type != null) { if (type.isInstance(value)) { return getFormatterForType(type); } else { // this happens if ValueTypes has a type for a value name but the actual value produced has a different type. // there are several possible causes: // 1) the type produced for a value has been changed (e.g. the function that produces it has been modified) // but the ValueTypes config hasn't been updated to match // 2) the type produced for the value can change from cycle to cycle. to fix this the ValueTypes config // should be modified to specify a supertype of all possible types. if there isn't a common supertype // with a formatter that works for all possible values then the value name can be removed from the // ValueTypes config. this will give the value name a type of UNKNOWN and the type and formatting will // be decided from the value after every cycle // 3) the type produced for the value is always the same but the value is converted to a different type // by Fudge depending on the value. e.g. an integer will be encoded as a byte if it is small enough // but will be encoded as an integer if it won't fit into a byte. the fix for this scenario is the same // as #2 above s_logger.warn("Unexpected type for value. Value name: '{}', expected type: {}, actual type: {}, value: {}", new Object[]{valueSpec.getValueName(), type.getName(), value.getClass().getName(), value}); } } return getFormatterForType(value.getClass()); } } private TypeFormatter getFormatterForType(Class<?> type) { if (type == null) { return _unknownTypeFormatter; } TypeFormatter formatter = _formatters.get(type); if (formatter == null) { return _defaultFormatter; } else { return formatter; } } private static boolean isError(Object value) { return value instanceof MissingValue; } /** * Formats a value for conversion to JSON and sending to the client. * @param value The value to be formatted, possibly null * @param valueSpec The specification of the value, null if the value wasn't calculated by the engine * @param format The type of formatting * @param inlineKey The key for extracting a single value from the value, possibly null. This is used for values * that can be displayed inline across multiple cells, e.g. vectors of doubles that are displayed across multiple * columns of double values. * @return The formatted value. Can be null (indicating an error) or {@link #VALUE_UNAVAILABLE} if the object * can't be formatted as requested or if the value doesn't have an entry for the specified key. */ @SuppressWarnings("unchecked") public Object format(Object value, ValueSpecification valueSpec, TypeFormatter.Format format, Object inlineKey) { TypeFormatter formatter = getFormatter(value, valueSpec); return formatter.format(value, valueSpec, format, inlineKey); } /** * Returns the format type for a value type. * @param type The value type * @return The formatter used for formatting the type */ public DataType getDataType(Class<?> type) { return getFormatterForType(type).getDataType(); } /** * Returns the format type for a value. * @param value The value, possibly null * @param valueSpec The value's specification, possibly null * @return The format type for the value, not null */ @SuppressWarnings("unchecked") public DataType getDataTypeForValue(Object value, ValueSpecification valueSpec) { return getFormatter(value, valueSpec).getDataTypeForValue(value); } }