com.opengamma.web.analytics.blotter.BlotterUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.opengamma.web.analytics.blotter.BlotterUtils.java

Source

/**
 * Copyright (C) 2013 - 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.text.DecimalFormat;
import java.text.ParseException;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;
import org.joda.beans.BeanBuilder;
import org.joda.beans.MetaBean;
import org.joda.beans.MetaProperty;
import org.joda.convert.StringConvert;
import org.joda.convert.StringConverter;
import org.threeten.bp.LocalDate;
import org.threeten.bp.LocalTime;
import org.threeten.bp.OffsetTime;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.analytics.financial.credit.DebtSeniority;
import com.opengamma.core.id.ExternalSchemes;
import com.opengamma.financial.convention.StubType;
import com.opengamma.financial.conversion.JodaBeanConverters;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.financial.security.LongShort;
import com.opengamma.financial.security.capfloor.CapFloorCMSSpreadSecurity;
import com.opengamma.financial.security.capfloor.CapFloorSecurity;
import com.opengamma.financial.security.cash.CashSecurity;
import com.opengamma.financial.security.cds.CreditDefaultSwapIndexSecurity;
import com.opengamma.financial.security.cds.CreditDefaultSwapSecurity;
import com.opengamma.financial.security.cds.LegacyFixedRecoveryCDSSecurity;
import com.opengamma.financial.security.cds.LegacyRecoveryLockCDSSecurity;
import com.opengamma.financial.security.cds.LegacyVanillaCDSSecurity;
import com.opengamma.financial.security.cds.StandardFixedRecoveryCDSSecurity;
import com.opengamma.financial.security.cds.StandardRecoveryLockCDSSecurity;
import com.opengamma.financial.security.cds.StandardVanillaCDSSecurity;
import com.opengamma.financial.security.equity.EquityVarianceSwapSecurity;
import com.opengamma.financial.security.fra.FRASecurity;
import com.opengamma.financial.security.fx.FXForwardSecurity;
import com.opengamma.financial.security.fx.NonDeliverableFXForwardSecurity;
import com.opengamma.financial.security.option.BarrierDirection;
import com.opengamma.financial.security.option.BarrierType;
import com.opengamma.financial.security.option.CreditDefaultSwapOptionSecurity;
import com.opengamma.financial.security.option.FXBarrierOptionSecurity;
import com.opengamma.financial.security.option.FXDigitalOptionSecurity;
import com.opengamma.financial.security.option.FXOptionSecurity;
import com.opengamma.financial.security.option.MonitoringType;
import com.opengamma.financial.security.option.NonDeliverableFXOptionSecurity;
import com.opengamma.financial.security.option.OptionType;
import com.opengamma.financial.security.option.SamplingFrequency;
import com.opengamma.financial.security.option.SwaptionSecurity;
import com.opengamma.financial.security.swap.FixedInflationSwapLeg;
import com.opengamma.financial.security.swap.FixedInterestRateLeg;
import com.opengamma.financial.security.swap.FloatingGearingIRLeg;
import com.opengamma.financial.security.swap.FloatingInterestRateLeg;
import com.opengamma.financial.security.swap.FloatingSpreadIRLeg;
import com.opengamma.financial.security.swap.InflationIndexSwapLeg;
import com.opengamma.financial.security.swap.InterestRateNotional;
import com.opengamma.financial.security.swap.SwapLeg;
import com.opengamma.financial.security.swap.SwapSecurity;
import com.opengamma.financial.security.swap.YearOnYearInflationSwapSecurity;
import com.opengamma.financial.security.swap.ZeroCouponInflationSwapSecurity;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.master.security.ManageableSecurity;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.time.Expiry;

/**
 *
 */
/* package */ class BlotterUtils {

    // TODO this should be configurable, should be able to add from client projects
    /** All the securities and related types supported by the blotter. */
    private static final Set<MetaBean> s_metaBeans = Sets.<MetaBean>newHashSet(FXForwardSecurity.meta(),
            SwapSecurity.meta(), SwaptionSecurity.meta(), CapFloorCMSSpreadSecurity.meta(),
            NonDeliverableFXOptionSecurity.meta(), FXOptionSecurity.meta(), FRASecurity.meta(),
            CapFloorSecurity.meta(), EquityVarianceSwapSecurity.meta(), FXBarrierOptionSecurity.meta(),
            FXDigitalOptionSecurity.meta(), FixedInterestRateLeg.meta(), FloatingInterestRateLeg.meta(),
            FloatingSpreadIRLeg.meta(), FloatingGearingIRLeg.meta(), FixedInflationSwapLeg.meta(),
            InflationIndexSwapLeg.meta(), InterestRateNotional.meta(), LegacyVanillaCDSSecurity.meta(),
            LegacyRecoveryLockCDSSecurity.meta(), LegacyFixedRecoveryCDSSecurity.meta(),
            StandardVanillaCDSSecurity.meta(), StandardRecoveryLockCDSSecurity.meta(),
            StandardFixedRecoveryCDSSecurity.meta(), CreditDefaultSwapIndexSecurity.meta(),
            CreditDefaultSwapOptionSecurity.meta(), YearOnYearInflationSwapSecurity.meta(),
            ZeroCouponInflationSwapSecurity.meta());

    /** Meta bean factory for looking up meta beans by type name. */
    private static final MetaBeanFactory s_metaBeanFactory = new MapMetaBeanFactory(s_metaBeans);
    /** Formatter for decimal numbers, DecimalFormat isn't thread safe. */
    private static final ThreadLocal<DecimalFormat> s_decimalFormat = new ThreadLocal<DecimalFormat>() {
        @Override
        protected DecimalFormat initialValue() {
            DecimalFormat decimalFormat = new DecimalFormat("#,###.#####");
            decimalFormat.setParseBigDecimal(true);
            return decimalFormat;
        }
    };

    /**
     * For traversing trade and security {@link MetaBean}s and building instances from the data sent from the blotter.
     * The security type name is filtered out because it is a read-only property. The external ID bundle is filtered
     * out because it is always empty for trades and securities entered via the blotter but isn't nullable. Therefore
     * it has to be explicitly set to an empty bundle after the client data is processed but before the object is built.
     */
    private static final BeanTraverser s_beanBuildingTraverser = new BeanTraverser(
            new PropertyFilter(FinancialSecurity.meta().externalIdBundle()),
            new PropertyFilter(ManageableSecurity.meta().securityType()));

    /** For converting between strings values used by the UI and real objects. */
    private static final StringConvert s_stringConvert;
    /** For converting property values when creating trades and securities from JSON. */
    private static final Converters s_beanBuildingConverters;
    /** For converting property values when creating JSON objects from trades and securities. */
    private static final Converters s_jsonBuildingConverters;

    static {
        StringToRegionIdConverter stringToRegionIdConverter = new StringToRegionIdConverter();
        // for building beans from JSON
        Map<MetaProperty<?>, Converter<?, ?>> beanRegionConverters = Maps.newHashMap();
        beanRegionConverters
                .putAll(ImmutableMap.<MetaProperty<?>, Converter<?, ?>>of(CashSecurity.meta().regionId(),
                        stringToRegionIdConverter, CreditDefaultSwapSecurity.meta().regionId(),
                        stringToRegionIdConverter, EquityVarianceSwapSecurity.meta().regionId(),
                        stringToRegionIdConverter, FRASecurity.meta().regionId(), stringToRegionIdConverter,
                        SwapLeg.meta().regionId(), stringToRegionIdConverter));
        beanRegionConverters.putAll(ImmutableMap.<MetaProperty<?>, Converter<?, ?>>of(
                FXForwardSecurity.meta().regionId(), new FXRegionConverter(),
                NonDeliverableFXForwardSecurity.meta().regionId(), new FXRegionConverter()));

        // for building JSON from beans
        RegionIdToStringConverter regionIdToStringConverter = new RegionIdToStringConverter();
        Map<MetaProperty<?>, Converter<?, ?>> jsonRegionConverters = ImmutableMap
                .<MetaProperty<?>, Converter<?, ?>>of(CashSecurity.meta().regionId(), regionIdToStringConverter,
                        CreditDefaultSwapSecurity.meta().regionId(), regionIdToStringConverter,
                        EquityVarianceSwapSecurity.meta().regionId(), regionIdToStringConverter,
                        FRASecurity.meta().regionId(), regionIdToStringConverter, SwapLeg.meta().regionId(),
                        regionIdToStringConverter);

        s_stringConvert = new StringConvert();
        s_stringConvert.register(BigDecimal.class, new BigDecimalConverter());
        s_stringConvert.register(Double.class, new DoubleConverter());
        s_stringConvert.register(Double.TYPE, new DoubleConverter());
        s_stringConvert.register(ExternalIdBundle.class, new JodaBeanConverters.ExternalIdBundleConverter());
        s_stringConvert.register(Expiry.class, new ExpiryConverter());
        s_stringConvert.register(MonitoringType.class, new EnumConverter<MonitoringType>());
        s_stringConvert.register(BarrierType.class, new EnumConverter<BarrierType>());
        s_stringConvert.register(BarrierDirection.class, new EnumConverter<BarrierDirection>());
        s_stringConvert.register(SamplingFrequency.class, new EnumConverter<SamplingFrequency>());
        s_stringConvert.register(LongShort.class, new EnumConverter<LongShort>());
        s_stringConvert.register(OptionType.class, new EnumConverter<OptionType>());
        s_stringConvert.register(ZonedDateTime.class, new ZonedDateTimeConverter());
        s_stringConvert.register(OffsetTime.class, new OffsetTimeConverter());
        s_stringConvert.register(DebtSeniority.class, new EnumConverter<DebtSeniority>());
        s_stringConvert.register(StubType.class, new EnumConverter<StubType>());

        s_jsonBuildingConverters = new Converters(jsonRegionConverters, s_stringConvert);
        s_beanBuildingConverters = new Converters(beanRegionConverters, s_stringConvert);
    }

    /**
     * Filters out region ID for FX forwards when building JSON for the security and HTML screens showing the structure.
     * The property value is hard-coded to {@code FINANCIAL_REGION~GB} for FX forwards so its value is of no interest
     * to the client and it can't be updated.
     */
    private static final PropertyFilter s_fxRegionFilter = new PropertyFilter(FXForwardSecurity.meta().regionId(),
            NonDeliverableFXForwardSecurity.meta().regionId());

    /**
     * Filters out the {@code externalIdBundle} property from OTC securities when building the HTML showing the security
     * structure. OTC security details are passed to the blotter back end which generates the ID so this
     * info is irrelevant to the client.
     */
    private static final BeanVisitorDecorator s_externalIdBundleFilter = new PropertyNameFilter("externalIdBundle");

    /**
     * Filters out the underlying ID field of {@link SwaptionSecurity} when building the HTML showing the security
     * structure. The back end creates the underlying security and fills this field in so it's of no interest
     * to the client.
     */
    private static final PropertyFilter s_swaptionUnderlyingFilter = new PropertyFilter(
            SwaptionSecurity.meta().underlyingId());

    /**
     * Filters out the underlying ID field of {@link CreditDefaultSwapOptionSecurity} when building the HTML showing the security
     * structure. The back end creates the underlying security and fills this field in so it's of no interest
     * to the client.
     */
    private static final PropertyFilter s_cdsOptionUnderlyingFilter = new PropertyFilter(
            CreditDefaultSwapOptionSecurity.meta().underlyingId());

    /**
     * Filters out the {@code securityType} field for all securities when building the HTML showing the security
     * structure. This value is read-only in each security type and is of no interest to the client.
     */
    private static final PropertyFilter s_securityTypeFilter = new PropertyFilter(
            ManageableSecurity.meta().securityType());

    /**
     * @return A thread-local formatter instance set to parse numbers into BigDecimals.
     */
    /* package */ static DecimalFormat getDecimalFormat() {
        return s_decimalFormat.get();
    }

    /* package */ static FinancialSecurity buildSecurity(BeanDataSource data) {
        return buildSecurity(data, ExternalIdBundle.EMPTY);
    }

    @SuppressWarnings("unchecked")
    /* package */ static FinancialSecurity buildSecurity(BeanDataSource data, ExternalIdBundle idBundle) {
        BeanVisitor<BeanBuilder<FinancialSecurity>> visitor = new BeanBuildingVisitor<>(data, s_metaBeanFactory,
                s_beanBuildingConverters);
        MetaBean metaBean = s_metaBeanFactory.beanFor(data);
        // TODO check it's a FinancialSecurity metaBean
        if (!(metaBean instanceof FinancialSecurity.Meta)) {
            throw new IllegalArgumentException("MetaBean " + metaBean + " isn't for a FinancialSecurity");
        }
        BeanBuilder<FinancialSecurity> builder = (BeanBuilder<FinancialSecurity>) s_beanBuildingTraverser
                .traverse(metaBean, visitor);
        // externalIdBundle needs to be specified or building fails because it's not nullable
        builder.set(FinancialSecurity.meta().externalIdBundle(), idBundle);
        return builder.build();
    }

    // TODO move to BlotterUtils
    /* package */ static StringConvert getStringConvert() {
        return s_stringConvert;
    }

    /* package */ static Converters getJsonBuildingConverters() {
        return s_jsonBuildingConverters;
    }

    /* package */ static Converters getBeanBuildingConverters() {
        return s_beanBuildingConverters;
    }

    /* package */ static Set<MetaBean> getMetaBeans() {
        return s_metaBeans;
    }

    /* package */ static BeanTraverser structureBuildingTraverser() {
        return new BeanTraverser(s_externalIdBundleFilter, s_securityTypeFilter, s_swaptionUnderlyingFilter,
                s_cdsOptionUnderlyingFilter, s_fxRegionFilter);
    }

    /* package */
    static BeanTraverser securityJsonBuildingTraverser() {
        return new BeanTraverser(s_securityTypeFilter, s_fxRegionFilter);
    }
}

// ----------------------------------------------------------------------------------

/**
 * For converting between enum instances and strings. The enum value names are made more readable by downcasing
 * and capitalizing them and replacing underscores with spaces.
 * @param <T> Type of the enum
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
/* package */ class EnumConverter<T extends Enum> implements StringConverter<T> {

    @Override
    public T convertFromString(Class<? extends T> type, String str) {
        // IntelliJ says this cast is redundant but javac disagrees
        //noinspection RedundantCast
        return (T) Enum.valueOf(type, str.toUpperCase().replace(' ', '_'));
    }

    @Override
    public String convertToString(T e) {
        return WordUtils.capitalize(e.name().toLowerCase().replace('_', ' '));
    }
}

/**
 * Converts {@link ZonedDateTime} to a local date string (e.g. 2012-12-21) and creates a {@link ZonedDateTime} from
 * a local date string with a time of 11:00 and a zone of UTC.
 */
/* package */ class ZonedDateTimeConverter implements StringConverter<ZonedDateTime> {

    @Override
    public ZonedDateTime convertFromString(Class<? extends ZonedDateTime> cls, String localDateString) {
        LocalDate localDate = LocalDate.parse(localDateString);
        return localDate.atTime(11, 0).atZone(ZoneOffset.UTC);
    }

    @Override
    public String convertToString(ZonedDateTime dateTime) {
        return dateTime.toLocalDate().toString();
    }
}

/**
 * Converts an {@link OffsetTime} to a time string (e.g. 11:35) and discards the offset. Creates
 * an {@link OffsetTime} instance by parsing a local date string and using UTC as the offset.
 */
/* package */ class OffsetTimeConverter implements StringConverter<OffsetTime> {

    @Override
    public OffsetTime convertFromString(Class<? extends OffsetTime> cls, String timeString) {
        if (!StringUtils.isEmpty(timeString)) {
            return OffsetTime.of(LocalTime.parse(timeString.trim()), ZoneOffset.UTC);
        } else {
            return null;
        }
    }

    @Override
    public String convertToString(OffsetTime time) {
        return time.toLocalTime().toString();
    }
}

/**
 * Converts between an {@link Expiry} and a local date string (e.g. 2011-03-08).
 */
/* package */ class ExpiryConverter implements StringConverter<Expiry> {

    @Override
    public Expiry convertFromString(Class<? extends Expiry> cls, String localDateString) {
        LocalDate localDate = LocalDate.parse(localDateString);
        return new Expiry(localDate.atTime(11, 0).atZone(ZoneOffset.UTC));
    }

    @Override
    public String convertToString(Expiry expiry) {
        return expiry.getExpiry().toLocalDate().toString();
    }
}

/**
 * Converts doubles to strings in simple format (i.e. no scientific notation). Limits to 5DP.
 */
/* package */ class DoubleConverter implements StringConverter<Double> {

    @Override
    public Double convertFromString(Class<? extends Double> cls, String str) {
        try {
            return BlotterUtils.getDecimalFormat().parse(str).doubleValue();
        } catch (ParseException e) {
            throw new IllegalArgumentException("Failed to parse number", e);
        }
    }

    @Override
    public String convertToString(Double value) {
        return BlotterUtils.getDecimalFormat().format(value);
    }
}

/**
 * Converts big decimals to strings in simple format (i.e. no scientific notation). Limits to 5DP.
 */
/* package */ class BigDecimalConverter implements StringConverter<BigDecimal> {

    @Override
    public BigDecimal convertFromString(Class<? extends BigDecimal> cls, String str) {
        try {
            Number number = BlotterUtils.getDecimalFormat().parse(str);
            // bizarrely if you call setParseBigDecimal(true) on a DecimalFormat it returns a BigDecimal unless the number
            // is NaN or +/- infinity in which case it returns a Double
            if (number instanceof BigDecimal) {
                return (BigDecimal) number;
            } else {
                throw new IllegalArgumentException("Failed to parse number as BigDecimal: " + number);
            }
        } catch (ParseException e) {
            throw new IllegalArgumentException("Failed to parse number", e);
        }
    }

    @Override
    public String convertToString(BigDecimal value) {
        return BlotterUtils.getDecimalFormat().format(value);
    }
}

/**
 * Converts a string to an {@link ExternalId} with a scheme of {@link ExternalSchemes#FINANCIAL}.
 */
/* package */ class StringToRegionIdConverter implements Converter<String, ExternalId> {

    /**
     * Converts a string to an {@link ExternalId} with a scheme of {@link ExternalSchemes#FINANCIAL}.
     * @param region The region name, not empty
     * @return An {@link ExternalId} with a scheme of {@link ExternalSchemes#FINANCIAL} and a value of {@code region}.
     */
    @Override
    public ExternalId convert(String region) {
        if (StringUtils.isEmpty(region)) {
            throw new IllegalArgumentException("Region must not be empty");
        }
        return ExternalId.of(ExternalSchemes.FINANCIAL, region);
    }
}

/**
 * Converts an {@link ExternalId} to a string.
 */
/* package */ class RegionIdToStringConverter implements Converter<ExternalId, String> {

    /**
     * Converts an {@link ExternalId} to a string
     * @param regionId The region ID, not null
     * @return {@code regionId}'s value
     */
    @Override
    public String convert(ExternalId regionId) {
        ArgumentChecker.notNull(regionId, "regionId");
        return regionId.getValue();
    }
}