/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.integration.marketdata.manipulator.dsl;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.opengamma.core.security.Security;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.engine.marketdata.manipulator.DistinctMarketDataSelector;
import com.opengamma.engine.marketdata.manipulator.SelectorResolver;
import com.opengamma.engine.target.resolver.PrimitiveResolver;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.ExternalScheme;
import com.opengamma.id.UniqueId;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.RegexUtils;
/**
* Selects raw market data points that will be manipulated.
*/
public class PointSelector implements DistinctMarketDataSelector {
/** Calc configs to which this selector will apply, null will match any config. */
private final Set<String> _calcConfigNames;
/** ID of the market data point to be manipulated. */
private final Set<ExternalId> _ids;
/** External ID scheme used when pattern matching ID value. */
private final ExternalScheme _idMatchScheme;
/** Regex pattern for matching ID value. */
private final PatternWrapper _idMatchPattern;
/** External ID scheme used when glob matching ID value. */
private final ExternalScheme _idLikeScheme;
/** Regex pattern for glob matching ID value. */
private final PatternWrapper _idLikePattern;
/** Security type names - matches if the ID identifies a security of the specified type. */
private final Set<String> _securityTypes;
/* package */ PointSelector(Set<String> calcConfigNames,
Set<ExternalId> ids,
ExternalScheme idMatchScheme,
Pattern idMatchPattern,
ExternalScheme idLikeScheme,
Pattern idLikePattern,
Set<String> securityTypes) {
if (idMatchScheme == null && idMatchPattern != null || idMatchScheme != null && idMatchPattern == null) {
throw new IllegalArgumentException("Scheme and pattern must both be specified to pattern match on ID");
}
if (idLikeScheme == null && idLikePattern != null || idLikeScheme != null && idLikePattern == null) {
throw new IllegalArgumentException("Scheme and pattern must both be specified to glob match on ID");
}
_idLikeScheme = idLikeScheme;
_idLikePattern = PatternWrapper.wrap(idLikePattern);
_securityTypes = securityTypes;
_idMatchScheme = idMatchScheme;
_idMatchPattern = PatternWrapper.wrap(idMatchPattern);
_calcConfigNames = calcConfigNames;
_ids = ids;
}
@Override
public boolean hasSelectionsDefined() {
return true;
}
@Override
public DistinctMarketDataSelector findMatchingSelector(ValueSpecification valueSpecification,
String calcConfigName,
final SelectorResolver resolver) {
if (_calcConfigNames != null && !_calcConfigNames.contains(calcConfigName)) {
return null;
}
if (!MarketDataRequirementNames.MARKET_VALUE.equals(valueSpecification.getValueName())) {
return null;
}
ExternalIdBundle specificationIdBundle = createIds(valueSpecification);
Set<ExternalId> specificationIds = new HashSet<>(specificationIdBundle.getExternalIds());
if (_ids != null) {
specificationIds.retainAll(_ids);
if (specificationIds.isEmpty()) {
return null;
}
}
if (_idMatchScheme != null && _idMatchPattern != null) {
if (FluentIterable.from(specificationIds).allMatch(new Predicate<ExternalId>() {
@Override
public boolean apply(ExternalId externalId) {
return !_idMatchScheme.equals(externalId.getScheme());
}
})) {
return null;
}
if (FluentIterable.from(specificationIds).allMatch(new Predicate<ExternalId>() {
@Override
public boolean apply(ExternalId externalId) {
return !_idMatchPattern.getPattern().matcher(externalId.getValue()).matches();
}
})) {
return null;
}
}
if (_idLikeScheme != null && _idLikePattern != null) {
if (FluentIterable.from(specificationIds).allMatch(new Predicate<ExternalId>() {
@Override
public boolean apply(ExternalId externalId) {
return !_idLikeScheme.equals(externalId.getScheme());
}
})) {
return null;
}
if (FluentIterable.from(specificationIds).allMatch(new Predicate<ExternalId>() {
@Override
public boolean apply(ExternalId externalId) {
return !_idLikePattern.getPattern().matcher(externalId.getValue()).matches();
}
})) {
return null;
}
}
if (_securityTypes != null) {
if (FluentIterable.from(specificationIds).allMatch(new Predicate<ExternalId>() {
@Override
public boolean apply(ExternalId externalId) {
Security security = resolver.resolveSecurity(externalId);
return !_securityTypes.contains(security.getSecurityType().toLowerCase());
}
})) {
return null;
}
}
return this;
}
private static ExternalIdBundle createIds(ValueSpecification valueSpecification) {
if (valueSpecification.getProperty("Id") != null) {
return ExternalIdBundle.of(ExternalId.parse(valueSpecification.getProperty("Id")));
} else {
// Id may not always be present - maybe with snapshots? (get External from UniqueId)
UniqueId uniqueId = valueSpecification.getTargetSpecification().getUniqueId();
String scheme = uniqueId.getScheme();
if (scheme.startsWith("ExternalId-")) {
return PrimitiveResolver.resolveExternalIds(uniqueId, "ExternalId-");
} else {
return ExternalIdBundle.of(scheme, uniqueId.getValue());
}
}
}
/* package */ Set<ExternalId> getIds() {
return _ids;
}
/* package */ Set<String> getCalculationConfigurationNames() {
return _calcConfigNames;
}
/* package */ ExternalScheme getIdMatchScheme() {
return _idMatchScheme;
}
/* package */ Pattern getIdMatchPattern() {
return _idMatchPattern == null ? null : _idMatchPattern.getPattern();
}
/* package */ Set<String> getSecurityTypes() {
return _securityTypes;
}
/* package */ ExternalScheme getIdLikeScheme() {
return _idLikeScheme;
}
/* package */ Pattern getIdLikePattern() {
return _idLikePattern == null ? null : _idLikePattern.getPattern();
}
@Override
public int hashCode() {
return Objects.hash(_calcConfigNames, _ids, _idMatchScheme, _idMatchPattern, _idLikeScheme, _idLikePattern, _securityTypes);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final PointSelector other = (PointSelector) obj;
return Objects.equals(this._calcConfigNames, other._calcConfigNames) &&
Objects.equals(this._ids, other._ids) &&
Objects.equals(this._idMatchScheme, other._idMatchScheme) &&
Objects.equals(this._idMatchPattern, other._idMatchPattern) &&
Objects.equals(this._idLikeScheme, other._idLikeScheme) &&
Objects.equals(this._idLikePattern, other._idLikePattern) &&
Objects.equals(this._securityTypes, other._securityTypes);
}
@Override
public String toString() {
return "PointSelector [" +
"_calcConfigNames=" + _calcConfigNames +
", _ids=" + _ids +
", _idMatchScheme=" + _idMatchScheme +
", _idMatchPattern=" + _idMatchPattern +
", _idLikeScheme=" + _idLikeScheme +
", _idLikePattern=" + _idLikePattern +
", _securityTypes=" + _securityTypes +
"]";
}
/**
* Mutable builder to create {@link PointSelector}s.
*/
public static class Builder {
/** Scenario that the transformation will be added to. */
private final Scenario _scenario;
/** ID of the market data point to be manipulated. */
private Set<ExternalId> _ids;
/** External ID scheme used when pattern matching ID value. */
private ExternalScheme _idMatchScheme;
/** Regex pattern for matching ID value. */
private Pattern _idMatchPattern;
/** External ID scheme used when glob matching ID value. */
private ExternalScheme _idLikeScheme;
/** Regex pattern for glob matching ID value. */
private Pattern _idLikePattern;
/** Security type names - matches if the ID identifies a security of the specified type. */
private Set<String> _securityTypes;
/* package */ Builder(Scenario scenario) {
_scenario = scenario;
}
/**
* @return A selector built from this object's data.
*/
public PointManipulatorBuilder apply() {
return new PointManipulatorBuilder(_scenario, getSelector());
}
/* package */ PointSelector getSelector() {
return new PointSelector(_scenario.getCalcConfigNames(),
_ids,
_idMatchScheme,
_idMatchPattern,
_idLikeScheme,
_idLikePattern,
_securityTypes);
}
/**
* Adds a test for the market data ID value to match exactly.
* @param scheme External ID scheme that must match the market data's ID scheme
* @param value External ID value that must match the market data's ID value
* @return This builder
*/
public Builder id(String scheme, String value) {
ArgumentChecker.notEmpty(scheme, "scheme");
ArgumentChecker.notEmpty(value, "value");
if (_ids != null) {
throw new IllegalStateException("id() or ids() can only be called once");
}
_ids = ImmutableSet.of(ExternalId.of(scheme, value));
return this;
}
/**
* Adds a test for the market data ID value to match exactly.
* @param ids The external IDs to match
* @return This builder
*/
public Builder ids(String... ids) {
ArgumentChecker.notEmpty(ids, "ids");
ArgumentChecker.notEmpty(ids, "ids");
if (_ids != null) {
throw new IllegalStateException("id() or ids() can only be called once");
}
Set<ExternalId> idSet = Sets.newHashSetWithExpectedSize(ids.length);
for (String id : ids) {
idSet.add(ExternalId.parse(id));
}
_ids = Collections.unmodifiableSet(idSet);
return this;
}
/**
* Adds a test for the market data ID value to match exactly.
* @param ids The IDs to match
* @return This builder
*/
public Builder ids(ExternalId... ids) {
ArgumentChecker.notEmpty(ids, "ids");
if (_ids != null) {
throw new IllegalStateException("id() or ids() can only be called once");
}
_ids = ImmutableSet.copyOf(ids);
return this;
}
/**
* Adds a test for the market data ID value to match a regular expression.
* @param scheme External ID scheme that must match the market data's ID scheme
* @param valueRegex Regular expression that must match the market data's ID value
* @return This builder
*/
public Builder idMatches(String scheme, String valueRegex) {
ArgumentChecker.notEmpty(scheme, "scheme");
ArgumentChecker.notEmpty(valueRegex, "valueRegex");
if (_idMatchScheme != null) {
throw new IllegalStateException("idMatches can only be called once");
}
_idMatchScheme = ExternalScheme.of(scheme);
_idMatchPattern = Pattern.compile(valueRegex);
return this;
}
/**
* Adds a test for the market data ID value to match a regular expression.
* @param scheme External ID scheme that must match the market data's ID scheme
* @param valueGlob Glob for matching the ID value
* @return This builder
*/
public Builder idLike(String scheme, String valueGlob) {
ArgumentChecker.notEmpty(scheme, "scheme");
ArgumentChecker.notEmpty(valueGlob, "valueGlob");
if (_idLikeScheme != null) {
throw new IllegalStateException("idLike can only be called once");
}
_idLikeScheme = ExternalScheme.of(scheme);
_idLikePattern = RegexUtils.globToPattern(valueGlob);
return this;
}
/**
* Limits the selection to the market value of IDs that identify a particular security type.
* @param types The security types to match, case insensitive.
* @return This builder
*/
public Builder securityTypes(String... types) {
ArgumentChecker.notEmpty(types, "types");
if (_securityTypes != null) {
throw new IllegalStateException("securityTypes can only be called once");
}
Set<String> securityTypes = Sets.newHashSet();
for (String type : types) {
if (type == null) {
throw new IllegalArgumentException("Security type names must be non-null");
}
// downcase here and also when comparing so comparison isn't case sensitive
securityTypes.add(type.toLowerCase());
}
_securityTypes = Collections.unmodifiableSet(securityTypes);
return this;
}
/* package */ Scenario getScenario() {
return _scenario;
}
}
}