/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.master.impl; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import com.google.common.collect.Sets; import com.opengamma.id.ExternalBundleIdentifiable; import com.opengamma.id.ExternalId; import com.opengamma.id.ExternalIdBundle; import com.opengamma.id.ExternalIdSearch; import com.opengamma.id.ExternalIdentifiable; import com.opengamma.util.ArgumentChecker; /** * Holds instances of {@link ExternalIdentifiable} for the purpose of improving searching in * any of the In Memory Masters. * * @param <T> the type of element stored * @param <V> the containing document */ public class InMemoryExternalIdCache<T extends ExternalBundleIdentifiable, V> { private final Map<T, V> _allItems = new ConcurrentHashMap<T, V>(); private final ConcurrentMap<ExternalId, Map<T, V>> _store = new ConcurrentHashMap<ExternalId, Map<T, V>>(); public void add(T element, V document) { ArgumentChecker.notNull(element, "element"); ArgumentChecker.notNull(document, "document"); ExternalIdBundle bundle = element.getExternalIdBundle(); if (bundle == null) { return; } for (ExternalId externalId : bundle) { Map<T, V> elements = _store.get(externalId); if (elements == null) { Map<T, V> fresh = new ConcurrentHashMap<T, V>(); elements = _store.putIfAbsent(externalId, fresh); elements = (elements == null) ? fresh : elements; } elements.put(element, document); } _allItems.put(element, document); } public void remove(T element) { ArgumentChecker.notNull(element, "element"); ExternalIdBundle bundle = element.getExternalIdBundle(); if (bundle == null) { return; } for (ExternalId externalId : bundle) { Map<T, V> elements = _store.get(externalId); if (elements == null) { continue; } elements.remove(element); } _allItems.remove(element); } /** * Obtain all items that <em>might</em> match the search request. * By design, this will return false positives so that a subsequent * call to {@link ExternalIdSearch#matches(Iterable)} can be performed * for further analysis, typically in the context of a larger check. * That being said, this method will attempt to minimize false * positives. * False negatives will not be returned. * * @param search the search to evaluate * @return items that are likely to match the search */ public Set<V> getMatches(ExternalIdSearch search) { ArgumentChecker.notNull(search, "search"); switch (search.getSearchType()) { case NONE: return getMatchesNone(search); case EXACT: // Intentional fall-through. case ALL: return getMatchesAll(search); case ANY: return getMatchesAny(search); } throw new IllegalStateException("All branches should have been handled in the switch statement."); } private Set<V> getMatchesAny(ExternalIdSearch search) { Set<V> result = new HashSet<V>(); for (ExternalId id : search.getExternalIds()) { Map<T, V> matches = _store.get(id); if (matches == null) { continue; } result.addAll(matches.values()); } return result; } private Set<V> getMatchesAll(ExternalIdSearch search) { Set<V> result = null; for (ExternalId id : search.getExternalIds()) { Map<T, V> matches = _store.get(id); if (matches == null) { // We can short circuit it here because by definition // these can't be satisfied. return Collections.emptySet(); } if (result == null) { result = new HashSet<V>(matches.values()); } else { result = Sets.intersection(result, new HashSet<V>(matches.values())); } } return result; } private Set<V> getMatchesNone(ExternalIdSearch search) { Set<V> result = new HashSet<V>(_allItems.values()); for (ExternalId id : search.getExternalIds()) { Map<T, V> matches = _store.get(id); if (matches != null) { result.removeAll(matches.values()); } } return result; } }