/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.currency; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import com.opengamma.id.MutableUniqueIdentifiable; import com.opengamma.id.UniqueId; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; /** * A simple base class for a {@link CurrencyMatrix}. */ public abstract class AbstractCurrencyMatrix implements CurrencyMatrix, MutableUniqueIdentifiable { private final ConcurrentHashMap<Currency, ConcurrentHashMap<Currency, CurrencyMatrixValue>> _values = new ConcurrentHashMap<Currency, ConcurrentHashMap<Currency, CurrencyMatrixValue>>(); private final ConcurrentHashMap<Currency, AtomicInteger> _targets = new ConcurrentHashMap<Currency, AtomicInteger>(); private UniqueId _uniqueId; // MutableUniqueIdentifiable @Override public void setUniqueId(final UniqueId uniqueId) { _uniqueId = uniqueId; } // UniqueIdentifiable @Override public UniqueId getUniqueId() { return _uniqueId; } // CurrencyMatrix @Override public Set<Currency> getSourceCurrencies() { return Collections.unmodifiableSet(_values.keySet()); } @Override public Set<Currency> getTargetCurrencies() { return Collections.unmodifiableSet(_targets.keySet()); } @Override public CurrencyMatrixValue getConversion(final Currency source, final Currency target) { if (source.equals(target)) { // This shouldn't happen in sensible code return CurrencyMatrixValue.of(1.0); } ConcurrentHashMap<Currency, CurrencyMatrixValue> conversions = _values.get(source); if (conversions == null) { return null; } else { CurrencyMatrixValue currMtxVal = conversions.get(target); return currMtxVal; } } // Helper methods for sub-classes protected void addConversion(final Currency source, final Currency target, final CurrencyMatrixValue rate) { ArgumentChecker.notNull(source, "source"); ArgumentChecker.notNull(target, "target"); ArgumentChecker.notNull(rate, "rate"); ConcurrentHashMap<Currency, CurrencyMatrixValue> conversions = _values.get(source); if (conversions == null) { conversions = new ConcurrentHashMap<Currency, CurrencyMatrixValue>(); final ConcurrentHashMap<Currency, CurrencyMatrixValue> newConversions = _values.putIfAbsent(source, conversions); if (newConversions != null) { conversions = newConversions; } } if (conversions.put(target, rate) == null) { // Added something to the map, so increase the target's reference count AtomicInteger targetCount = _targets.get(target); if (targetCount == null) { targetCount = new AtomicInteger(1); targetCount = _targets.putIfAbsent(target, targetCount); if (targetCount != null) { // Another thread already inserted the reference count if (targetCount.incrementAndGet() == 1) { // Another thread may have removed the last reference, confirm and re-insert atomically against "remove" synchronized (targetCount) { if (targetCount.get() > 0) { _targets.putIfAbsent(target, targetCount); } } } } } else { if (targetCount.incrementAndGet() == 1) { // Another thread may have removed the last reference, confirm and re-insert atomically against "remove" synchronized (targetCount) { if (targetCount.get() > 0) { _targets.putIfAbsent(target, targetCount); } } } } } } protected CurrencyMatrixValue removeConversion(final Currency source, final Currency target) { ArgumentChecker.notNull(source, "source"); ArgumentChecker.notNull(target, "target"); ConcurrentHashMap<Currency, CurrencyMatrixValue> conversions = _values.get(source); if (conversions == null) { // Nothing from that source return null; } final CurrencyMatrixValue value = conversions.remove(target); if (value == null) { // No conversion from source to target return null; } // Removed a value, so need to decrease the target's reference count AtomicInteger targetCount = _targets.get(target); if (targetCount != null) { // Target count should never be null at this point if (targetCount.decrementAndGet() == 0) { // This was the last reference to the target, confirm and remove atomically against the "add" operation synchronized (targetCount) { if (targetCount.get() == 0) { _targets.remove(target); } } } } return value; } @Override public int hashCode() { return new HashCodeBuilder().append(getUniqueId()).append(getSourceCurrencies()).append(getTargetCurrencies()).toHashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (obj instanceof CurrencyMatrix) { CurrencyMatrix other = (CurrencyMatrix) obj; EqualsBuilder equalsBuider = new EqualsBuilder().append(getUniqueId(), other.getUniqueId()) .append(getSourceCurrencies(), other.getSourceCurrencies()) .append(getTargetCurrencies(), other.getTargetCurrencies()); for (Currency source : getSourceCurrencies()) { for (Currency target : getTargetCurrencies()) { equalsBuider.append(getConversion(source, target), other.getConversion(source, target)); } } return equalsBuider.isEquals(); } return false; } }