/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics.ircurve; import static com.google.common.collect.Maps.newHashMap; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.threeten.bp.Instant; import com.opengamma.DataNotFoundException; import com.opengamma.core.change.BasicChangeManager; import com.opengamma.core.change.ChangeManager; import com.opengamma.core.change.ChangeProvider; import com.opengamma.core.change.ChangeType; import com.opengamma.id.ObjectId; import com.opengamma.id.ObjectIdentifiable; import com.opengamma.id.UniqueId; import com.opengamma.id.VersionCorrection; import com.opengamma.master.MasterUtils; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * An in-memory master for yield curve definitions, backed by a hash-map. */ public class InMemoryInterpolatedYieldCurveDefinitionMaster implements InterpolatedYieldCurveDefinitionMaster, InterpolatedYieldCurveDefinitionSource, ChangeProvider { /** * Default scheme used for identifiers created. */ public static final String DEFAULT_SCHEME = "InMemoryInterpolatedYieldCurveDefinition"; /** * The in-memory definitions. */ private final Map<Pair<Currency, String>, TreeMap<Instant, YieldCurveDefinition>> _definitions = new HashMap<Pair<Currency, String>, TreeMap<Instant, YieldCurveDefinition>>(); /** * The change manager. */ private final ChangeManager _changeManager = new BasicChangeManager(); // TODO make possible to pass the change manager in constructor /** * The unique id scheme */ private String _uniqueIdScheme; /** * Creates an instance. */ public InMemoryInterpolatedYieldCurveDefinitionMaster() { setUniqueIdScheme(DEFAULT_SCHEME); } //------------------------------------------------------------------------- /** * Gets the scheme in use for unique identifier. * * @return the scheme, not null */ public String getUniqueIdScheme() { return _uniqueIdScheme; } /** * Sets the scheme in use for unique identifier. * * @param scheme the scheme for unique identifier, not null */ public void setUniqueIdScheme(final String scheme) { ArgumentChecker.notNull(scheme, "scheme"); _uniqueIdScheme = scheme; } //------------------------------------------------------------------------- @Override public synchronized YieldCurveDefinition getDefinition(Currency currency, String name) { return getDefinition(currency, name, VersionCorrection.LATEST); } @Override public YieldCurveDefinition getDefinition(final Currency currency, final String name, final VersionCorrection versionCorrection) { ArgumentChecker.notNull(currency, "currency"); ArgumentChecker.notNull(name, "name"); final TreeMap<Instant, YieldCurveDefinition> definitions = _definitions.get(Pairs.of(currency, name)); if (definitions == null) { return null; } final Map.Entry<Instant, YieldCurveDefinition> entry = (versionCorrection.getVersionAsOf() == null) ? definitions.lastEntry() : definitions .floorEntry(versionCorrection.getVersionAsOf()); if (entry == null) { return null; } return entry.getValue(); } //------------------------------------------------------------------------- @Override public synchronized YieldCurveDefinitionDocument add(YieldCurveDefinitionDocument document) { ArgumentChecker.notNull(document, "document"); ArgumentChecker.notNull(document.getYieldCurveDefinition(), "document.yieldCurveDefinition"); final Currency currency = document.getYieldCurveDefinition().getCurrency(); final String name = document.getYieldCurveDefinition().getName(); final Pair<Currency, String> key = Pairs.of(currency, name); if (_definitions.containsKey(key)) { throw new IllegalArgumentException("Duplicate definition"); } final TreeMap<Instant, YieldCurveDefinition> value = new TreeMap<Instant, YieldCurveDefinition>(); Instant now = Instant.now(); value.put(now, document.getYieldCurveDefinition()); _definitions.put(key, value); final UniqueId uid = UniqueId.of(getUniqueIdScheme(), name + "_" + currency.getCode()); document.setUniqueId(uid); changeManager().entityChanged(ChangeType.ADDED, document.getObjectId(), document.getVersionFromInstant(), document.getVersionToInstant(), now); return document; } @Override public synchronized YieldCurveDefinitionDocument addOrUpdate(YieldCurveDefinitionDocument document) { ArgumentChecker.notNull(document, "document"); ArgumentChecker.notNull(document.getYieldCurveDefinition(), "document.yieldCurveDefinition"); final Currency currency = document.getYieldCurveDefinition().getCurrency(); final String name = document.getYieldCurveDefinition().getName(); final Pair<Currency, String> key = Pairs.of(currency, name); TreeMap<Instant, YieldCurveDefinition> value = _definitions.get(key); final UniqueId uid = UniqueId.of(getUniqueIdScheme(), name + "_" + currency.getCode()); Instant now = Instant.now(); if (value != null) { // TODO: Need to housekeep the map to release memory from old entries; this was previously done based on the latch version, but we've taken that out value.put(now, document.getYieldCurveDefinition()); changeManager().entityChanged(ChangeType.CHANGED, uid.getObjectId(), null, null, now); } else { value = new TreeMap<Instant, YieldCurveDefinition>(); value.put(now, document.getYieldCurveDefinition()); _definitions.put(key, value); changeManager().entityChanged(ChangeType.ADDED, uid.getObjectId(), document.getVersionFromInstant(), document.getVersionToInstant(), now); } document.setUniqueId(uid); return document; } @Override public YieldCurveDefinitionDocument correct(YieldCurveDefinitionDocument document) { throw new UnsupportedOperationException(); } @Override public synchronized YieldCurveDefinitionDocument get(UniqueId uid) { ArgumentChecker.notNull(uid, "objectIdentifiable"); if (!uid.isLatest()) { throw new IllegalArgumentException("Only latest version supported by '" + getUniqueIdScheme() + "'"); } if (!getUniqueIdScheme().equals(uid.getScheme())) { throw new DataNotFoundException("Scheme '" + uid.getScheme() + "' not valid for '" + getUniqueIdScheme() + "'"); } final int i = uid.getValue().indexOf('_'); if (i <= 0) { throw new DataNotFoundException("Identifier '" + uid.getValue() + "' not valid for '" + getUniqueIdScheme() + "'"); } final String name = uid.getValue().substring(0, i); final String iso = uid.getValue().substring(i + 1); final Currency currency; try { currency = Currency.of(iso); } catch (IllegalArgumentException e) { throw new DataNotFoundException("Identifier '" + uid.getValue() + "' not valid for '" + getUniqueIdScheme() + "'", e); } final TreeMap<Instant, YieldCurveDefinition> definitions = _definitions.get(Pairs.of(currency, name)); if (definitions == null) { throw new DataNotFoundException("Curve definition not found"); } final YieldCurveDefinition definition = definitions.lastEntry().getValue(); if (definition == null) { throw new DataNotFoundException("Curve definition not found"); } return new YieldCurveDefinitionDocument(uid, definition); } @Override public synchronized YieldCurveDefinitionDocument get(ObjectIdentifiable objectIdable, VersionCorrection versionCorrection) { ArgumentChecker.notNull(objectIdable, "objectIdable"); ObjectId objectId = objectIdable.getObjectId(); if (!getUniqueIdScheme().equals(objectId.getScheme())) { throw new DataNotFoundException("Scheme '" + objectId.getScheme() + "' not valid for '" + getUniqueIdScheme() + "'"); } final int i = objectId.getValue().indexOf('_'); if (i <= 0) { throw new DataNotFoundException("Identifier '" + objectId.getValue() + "' not valid for '" + getUniqueIdScheme() + "'"); } final String name = objectId.getValue().substring(0, i); final String iso = objectId.getValue().substring(i + 1); final Currency currency; try { currency = Currency.of(iso); } catch (IllegalArgumentException e) { throw new DataNotFoundException("Identifier '" + objectId.getValue() + "' not valid for '" + getUniqueIdScheme() + "'", e); } final TreeMap<Instant, YieldCurveDefinition> definitions = _definitions.get(Pairs.of(currency, name)); if (definitions == null) { throw new DataNotFoundException("Curve definition not found"); } final YieldCurveDefinition definition = definitions.lastEntry().getValue(); if (definition == null) { throw new DataNotFoundException("Curve definition not found"); } return new YieldCurveDefinitionDocument(objectId.atLatestVersion(), definition); } @Override public synchronized void remove(ObjectIdentifiable objectIdentifiable) { ArgumentChecker.notNull(objectIdentifiable, "objectIdentifiable"); if (!getUniqueIdScheme().equals(objectIdentifiable.getObjectId().getScheme())) { throw new DataNotFoundException("Scheme '" + objectIdentifiable.getObjectId().getScheme() + "' not valid for '" + getUniqueIdScheme() + "'"); } final int i = objectIdentifiable.getObjectId().getValue().indexOf('_'); if (i <= 0) { throw new DataNotFoundException("Identifier '" + objectIdentifiable.getObjectId().getValue() + "' not valid for '" + getUniqueIdScheme() + "'"); } final String name = objectIdentifiable.getObjectId().getValue().substring(0, i); final String iso = objectIdentifiable.getObjectId().getValue().substring(i + 1); final Currency currency; try { currency = Currency.of(iso); } catch (IllegalArgumentException e) { throw new DataNotFoundException("Identifier '" + objectIdentifiable.getObjectId().getValue() + "' not valid for '" + getUniqueIdScheme() + "'", e); } final Pair<Currency, String> key = Pairs.of(currency, name); // TODO: Need to housekeep the map to release memory from old entries; this was previously done based on the latch version, but we've taken that out final TreeMap<Instant, YieldCurveDefinition> value = _definitions.get(key); if (value == null) { throw new DataNotFoundException("Curve definition not found"); } // Store a null to indicate the delete final Instant now = Instant.now(); value.put(now, null); changeManager().entityChanged(ChangeType.REMOVED, objectIdentifiable.getObjectId(), null, null, now); } @Override public synchronized YieldCurveDefinitionDocument update(YieldCurveDefinitionDocument document) { ArgumentChecker.notNull(document, "document"); ArgumentChecker.notNull(document.getYieldCurveDefinition(), "document.yieldCurveDefinition"); final Currency currency = document.getYieldCurveDefinition().getCurrency(); final String name = document.getYieldCurveDefinition().getName(); final UniqueId uid = UniqueId.of(getUniqueIdScheme(), name + "_" + currency.getCode()); if (!uid.equals(document.getUniqueId())) { throw new IllegalArgumentException("Invalid unique identifier"); } final Pair<Currency, String> key = Pairs.of(currency, name); final TreeMap<Instant, YieldCurveDefinition> value = _definitions.get(key); if (value == null) { throw new DataNotFoundException("UID '" + uid + "' not found"); } // TODO: Need to housekeep the map to release memory from old entries; this was previously done based on the latch version, but we've taken that out Instant now = Instant.now(); value.put(now, document.getYieldCurveDefinition()); document.setUniqueId(uid); changeManager().entityChanged(ChangeType.CHANGED, uid.getObjectId(), null, null, now); return document; } @Override public ChangeManager changeManager() { return _changeManager; } @Override public List<UniqueId> replaceVersions(ObjectIdentifiable objectIdentifiable, List<YieldCurveDefinitionDocument> replacementDocuments) { ArgumentChecker.notNull(replacementDocuments, "replacementDocuments"); ArgumentChecker.notNull(objectIdentifiable, "objectIdentifiable"); final Instant now = Instant.now(); for (YieldCurveDefinitionDocument replacementDocument : replacementDocuments) { ArgumentChecker.notNull(replacementDocument, "document"); ArgumentChecker.notNull(replacementDocument.getYieldCurveDefinition(), "document.yieldCurveDefinition"); final Currency currency = replacementDocument.getYieldCurveDefinition().getCurrency(); final String name = replacementDocument.getYieldCurveDefinition().getName(); final UniqueId id = UniqueId.of(getUniqueIdScheme(), name + "_" + currency.getCode()); ArgumentChecker.isTrue(id.equals(objectIdentifiable), "Invalid object identifier"); } YieldCurveDefinitionDocument storedDocument = get(objectIdentifiable, null); if (storedDocument == null) { throw new DataNotFoundException("Document not found: " + objectIdentifiable); } final Currency currency = storedDocument.getYieldCurveDefinition().getCurrency(); final String name = storedDocument.getYieldCurveDefinition().getName(); Pair<Currency, String> key = Pairs.of(currency, name); final TreeMap<Instant, YieldCurveDefinition> value = _definitions.get(key); if (value == null) { throw new DataNotFoundException("OID '" + objectIdentifiable + "' not found"); } // TODO: Need to housekeep the map to release memory from old entries; this was previously done based on the latch version, but we've taken that out Instant lowestCurrentVersionFrom = value.firstKey(); List<YieldCurveDefinitionDocument> orderedReplacementDocuments = MasterUtils.adjustVersionInstants(now, lowestCurrentVersionFrom, null, replacementDocuments); final Instant lowestVersionFrom = orderedReplacementDocuments.get(0).getVersionFromInstant(); final Instant highestVersionTo = orderedReplacementDocuments.get(orderedReplacementDocuments.size() - 1).getVersionToInstant(); if (orderedReplacementDocuments.size() > 0) { value.subMap(lowestVersionFrom, true, highestVersionTo, false).clear(); } for (YieldCurveDefinitionDocument replacementDocument : orderedReplacementDocuments) { value.put(replacementDocument.getVersionFromInstant(), replacementDocument.getYieldCurveDefinition()); changeManager().entityChanged(ChangeType.CHANGED, replacementDocument.getObjectId(), null, null, now); } return MasterUtils.mapToUniqueIDs(orderedReplacementDocuments); } @Override public List<UniqueId> replaceAllVersions(ObjectIdentifiable objectIdentifiable, List<YieldCurveDefinitionDocument> replacementDocuments) { ArgumentChecker.notNull(replacementDocuments, "replacementDocuments"); ArgumentChecker.notNull(objectIdentifiable, "objectIdentifiable"); final Instant now = Instant.now(); for (YieldCurveDefinitionDocument replacementDocument : replacementDocuments) { ArgumentChecker.notNull(replacementDocument, "document"); ArgumentChecker.notNull(replacementDocument.getYieldCurveDefinition(), "document.yieldCurveDefinition"); final Currency currency = replacementDocument.getYieldCurveDefinition().getCurrency(); final String name = replacementDocument.getYieldCurveDefinition().getName(); final UniqueId id = UniqueId.of(getUniqueIdScheme(), name + "_" + currency.getCode()); ArgumentChecker.isTrue(id.equals(objectIdentifiable), "Invalid object identifier"); } YieldCurveDefinitionDocument storedDocument = get(objectIdentifiable, null); if (storedDocument == null) { throw new DataNotFoundException("Document not found: " + objectIdentifiable); } final Currency currency = storedDocument.getYieldCurveDefinition().getCurrency(); final String name = storedDocument.getYieldCurveDefinition().getName(); Pair<Currency, String> key = Pairs.of(currency, name); final TreeMap<Instant, YieldCurveDefinition> value = _definitions.get(key); if (value == null) { throw new DataNotFoundException("OID '" + objectIdentifiable + "' not found"); } value.clear(); List<YieldCurveDefinitionDocument> orderedReplacementDocuments = MasterUtils.adjustVersionInstants(now, null, null, replacementDocuments); final Instant lowestVersionFrom = orderedReplacementDocuments.get(0).getVersionFromInstant(); ArgumentChecker.notNull(lowestVersionFrom, "You must define version from of the first document"); for (YieldCurveDefinitionDocument replacementDocument : orderedReplacementDocuments) { value.put(replacementDocument.getVersionFromInstant(), replacementDocument.getYieldCurveDefinition()); changeManager().entityChanged(ChangeType.CHANGED, replacementDocument.getObjectId(), null, null, now); } return MasterUtils.mapToUniqueIDs(orderedReplacementDocuments); } @Override public List<UniqueId> replaceVersion(UniqueId uniqueId, List<YieldCurveDefinitionDocument> replacementDocuments) { return replaceVersions(uniqueId.getObjectId(), replacementDocuments); } @Override public void removeVersion(final UniqueId uniqueId) { replaceVersion(uniqueId, Collections.<YieldCurveDefinitionDocument>emptyList()); } @Override public UniqueId replaceVersion(YieldCurveDefinitionDocument replacementDocument) { List<UniqueId> result = replaceVersion(replacementDocument.getUniqueId(), Collections.singletonList(replacementDocument)); if (result.isEmpty()) { return null; } else { return result.get(0); } } @Override public UniqueId addVersion(ObjectIdentifiable objectId, YieldCurveDefinitionDocument documentToAdd) { List<UniqueId> result = replaceVersions(objectId, Collections.singletonList(documentToAdd)); if (result.isEmpty()) { return null; } else { return result.get(0); } } @Override public Map<UniqueId, YieldCurveDefinitionDocument> get(Collection<UniqueId> uniqueIds) { Map<UniqueId, YieldCurveDefinitionDocument> map = newHashMap(); for (UniqueId uniqueId : uniqueIds) { map.put(uniqueId, get(uniqueId)); } return map; } }