/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.basics;
import java.io.Serializable;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import org.joda.beans.Bean;
import org.joda.beans.BeanBuilder;
import org.joda.beans.BeanDefinition;
import org.joda.beans.ImmutableBean;
import org.joda.beans.ImmutableConstructor;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaProperty;
import org.joda.beans.Property;
import org.joda.beans.PropertyDefinition;
import org.joda.beans.impl.direct.DirectFieldsBeanBuilder;
import org.joda.beans.impl.direct.DirectMetaBean;
import org.joda.beans.impl.direct.DirectMetaProperty;
import org.joda.beans.impl.direct.DirectMetaPropertyMap;
import org.joda.convert.FromString;
import org.joda.convert.ToString;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ComparisonChain;
import com.google.common.net.PercentEscaper;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Messages;
/**
* An immutable standard identifier for an item.
* <p>
* A standard identifier is used to uniquely identify domain objects.
* It is formed from two parts, the scheme and value.
* <p>
* The scheme defines a single way of identifying items, while the value is an identifier
* within that scheme. A value from one scheme may refer to a completely different
* real-world item than the same value from a different scheme.
* <p>
* Real-world examples of {@code StandardId} include instances of:
* <ul>
* <li>Cusip</li>
* <li>Isin</li>
* <li>Reuters RIC</li>
* <li>Bloomberg BUID</li>
* <li>Bloomberg Ticker</li>
* <li>Trading system OTC trade ID</li>
* </ul>
* <p>
* This class is immutable and thread-safe.
*/
@BeanDefinition(builderScope = "private")
public final class StandardId
implements Comparable<StandardId>, ImmutableBean, Serializable {
/** Serialization version. */
private static final long serialVersionUID = 1L;
/**
* Matcher for checking the scheme.
* It must only contains the characters A-Z, a-z, 0-9 and selected special characters.
*/
private static final CharMatcher SCHEME_MATCHER =
CharMatcher.inRange('A', 'Z')
.or(CharMatcher.inRange('a', 'z'))
.or(CharMatcher.inRange('0', '9'))
.or(CharMatcher.is(':'))
.or(CharMatcher.is('/'))
.or(CharMatcher.is('+'))
.or(CharMatcher.is('.'))
.or(CharMatcher.is('='))
.or(CharMatcher.is('_'))
.or(CharMatcher.is('-'))
.precomputed();
/**
* Matcher for checking the value.
* It must contain ASCII printable characters excluding curly brackets, pipe and tilde.
*/
private static final CharMatcher VALUE_MATCHER = CharMatcher.inRange(' ', 'z').precomputed();
/**
* The escaper.
*/
private static final PercentEscaper SCHEME_ESCAPER = new PercentEscaper(":/+.=_-", false);
/**
* The scheme that categorizes the identifier value.
* <p>
* This provides the universe within which the identifier value has meaning.
*/
@PropertyDefinition(validate = "notNull")
private final String scheme;
/**
* The value of the identifier within the scheme.
*/
@PropertyDefinition(validate = "notNull")
private final String value;
/**
* The hash code.
*/
private final transient int hashCode;
//-------------------------------------------------------------------------
/**
* Obtains an instance from a scheme and value.
* <p>
* The scheme must be non-empty and match the regular expression '{@code [A-Za-z0-9:/+.=_-]*}'.
* This permits letters, numbers, colon, forward-slash, plus, dot, equals, underscore and dash.
* If necessary, the scheme can be encoded using {@link StandardId#encodeScheme(String)}.
* <p>
* The value must be non-empty and match the regular expression '{@code [!-z][ -z]*}'.
* This includes all standard printable ASCII characters excluding curly brackets, pipe and tilde.
*
* @param scheme the scheme of the identifier, not empty
* @param value the value of the identifier, not empty
* @return the identifier
*/
public static StandardId of(String scheme, String value) {
return new StandardId(scheme, value);
}
/**
* Parses an {@code StandardId} from a formatted scheme and value.
* <p>
* This parses the identifier from the form produced by {@code toString()}
* which is '{@code $scheme~$value}'.
*
* @param str the identifier to parse
* @return the identifier
* @throws IllegalArgumentException if the identifier cannot be parsed
*/
@FromString
public static StandardId parse(String str) {
int pos = ArgChecker.notNull(str, "str").indexOf("~");
if (pos < 0) {
throw new IllegalArgumentException("Invalid identifier format: " + str);
}
return new StandardId(str.substring(0, pos), str.substring(pos + 1));
}
/**
* Encode a string suitable for use as the scheme.
* <p>
* This uses percent encoding, just like URI.
*
* @param scheme the scheme to encode
* @return the encoded scheme
*/
public static String encodeScheme(String scheme) {
return SCHEME_ESCAPER.escape(scheme);
}
//-------------------------------------------------------------------------
/**
* Creates an identifier.
*
* @param scheme the scheme of the identifier, not empty
* @param value the value of the identifier, not empty
*/
@ImmutableConstructor
private StandardId(String scheme, String value) {
ArgChecker.matches(SCHEME_MATCHER, 1, Integer.MAX_VALUE, scheme, "scheme", "[A-Za-z0-9:/+.=_-]+");
ArgChecker.matches(VALUE_MATCHER, 1, Integer.MAX_VALUE, value, "value", "[!-z][ -z]+");
if (value.charAt(0) == ' ') {
throw new IllegalArgumentException(Messages.format(
"Invalid initial space in value '{}' must match regex '[!-z][ -z]*'", value));
}
this.scheme = scheme;
this.value = value;
this.hashCode = scheme.hashCode() ^ value.hashCode();
}
// resolve after deserialization
private Object readResolve() {
return new StandardId(scheme, value);
}
//-------------------------------------------------------------------------
/**
* Compares the external identifiers, sorting alphabetically by scheme followed by value.
*
* @param other the other external identifier
* @return negative if this is less, zero if equal, positive if greater
*/
@Override
public int compareTo(StandardId other) {
return ComparisonChain.start()
.compare(scheme, other.scheme)
.compare(value, other.value)
.result();
}
/**
* Checks if this identifier equals another, comparing the scheme and value.
*
* @param obj the other object
* @return true if equal
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof StandardId) {
StandardId other = (StandardId) obj;
return scheme.equals(other.scheme) && value.equals(other.value);
}
return false;
}
/**
* Returns a suitable hash code, based on the scheme and value.
*
* @return the hash code
*/
@Override
public int hashCode() {
return hashCode;
}
/**
* Returns the identifier in a standard string format.
* <p>
* The returned string is in the form '{@code $scheme~$value}'.
* This is suitable for use with {@link #parse(String)}.
*
* @return a parsable representation of the identifier
*/
@Override
@ToString
public String toString() {
return scheme + "~" + value;
}
//------------------------- AUTOGENERATED START -------------------------
///CLOVER:OFF
/**
* The meta-bean for {@code StandardId}.
* @return the meta-bean, not null
*/
public static StandardId.Meta meta() {
return StandardId.Meta.INSTANCE;
}
static {
JodaBeanUtils.registerMetaBean(StandardId.Meta.INSTANCE);
}
@Override
public StandardId.Meta metaBean() {
return StandardId.Meta.INSTANCE;
}
@Override
public <R> Property<R> property(String propertyName) {
return metaBean().<R>metaProperty(propertyName).createProperty(this);
}
@Override
public Set<String> propertyNames() {
return metaBean().metaPropertyMap().keySet();
}
//-----------------------------------------------------------------------
/**
* Gets the scheme that categorizes the identifier value.
* <p>
* This provides the universe within which the identifier value has meaning.
* @return the value of the property, not null
*/
public String getScheme() {
return scheme;
}
//-----------------------------------------------------------------------
/**
* Gets the value of the identifier within the scheme.
* @return the value of the property, not null
*/
public String getValue() {
return value;
}
//-----------------------------------------------------------------------
/**
* The meta-bean for {@code StandardId}.
*/
public static final class Meta extends DirectMetaBean {
/**
* The singleton instance of the meta-bean.
*/
static final Meta INSTANCE = new Meta();
/**
* The meta-property for the {@code scheme} property.
*/
private final MetaProperty<String> scheme = DirectMetaProperty.ofImmutable(
this, "scheme", StandardId.class, String.class);
/**
* The meta-property for the {@code value} property.
*/
private final MetaProperty<String> value = DirectMetaProperty.ofImmutable(
this, "value", StandardId.class, String.class);
/**
* The meta-properties.
*/
private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap(
this, null,
"scheme",
"value");
/**
* Restricted constructor.
*/
private Meta() {
}
@Override
protected MetaProperty<?> metaPropertyGet(String propertyName) {
switch (propertyName.hashCode()) {
case -907987547: // scheme
return scheme;
case 111972721: // value
return value;
}
return super.metaPropertyGet(propertyName);
}
@Override
public BeanBuilder<? extends StandardId> builder() {
return new StandardId.Builder();
}
@Override
public Class<? extends StandardId> beanType() {
return StandardId.class;
}
@Override
public Map<String, MetaProperty<?>> metaPropertyMap() {
return metaPropertyMap$;
}
//-----------------------------------------------------------------------
/**
* The meta-property for the {@code scheme} property.
* @return the meta-property, not null
*/
public MetaProperty<String> scheme() {
return scheme;
}
/**
* The meta-property for the {@code value} property.
* @return the meta-property, not null
*/
public MetaProperty<String> value() {
return value;
}
//-----------------------------------------------------------------------
@Override
protected Object propertyGet(Bean bean, String propertyName, boolean quiet) {
switch (propertyName.hashCode()) {
case -907987547: // scheme
return ((StandardId) bean).getScheme();
case 111972721: // value
return ((StandardId) bean).getValue();
}
return super.propertyGet(bean, propertyName, quiet);
}
@Override
protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) {
metaProperty(propertyName);
if (quiet) {
return;
}
throw new UnsupportedOperationException("Property cannot be written: " + propertyName);
}
}
//-----------------------------------------------------------------------
/**
* The bean-builder for {@code StandardId}.
*/
private static final class Builder extends DirectFieldsBeanBuilder<StandardId> {
private String scheme;
private String value;
/**
* Restricted constructor.
*/
private Builder() {
}
//-----------------------------------------------------------------------
@Override
public Object get(String propertyName) {
switch (propertyName.hashCode()) {
case -907987547: // scheme
return scheme;
case 111972721: // value
return value;
default:
throw new NoSuchElementException("Unknown property: " + propertyName);
}
}
@Override
public Builder set(String propertyName, Object newValue) {
switch (propertyName.hashCode()) {
case -907987547: // scheme
this.scheme = (String) newValue;
break;
case 111972721: // value
this.value = (String) newValue;
break;
default:
throw new NoSuchElementException("Unknown property: " + propertyName);
}
return this;
}
@Override
public Builder set(MetaProperty<?> property, Object value) {
super.set(property, value);
return this;
}
@Override
public Builder setString(String propertyName, String value) {
setString(meta().metaProperty(propertyName), value);
return this;
}
@Override
public Builder setString(MetaProperty<?> property, String value) {
super.setString(property, value);
return this;
}
@Override
public Builder setAll(Map<String, ? extends Object> propertyValueMap) {
super.setAll(propertyValueMap);
return this;
}
@Override
public StandardId build() {
return new StandardId(
scheme,
value);
}
//-----------------------------------------------------------------------
@Override
public String toString() {
StringBuilder buf = new StringBuilder(96);
buf.append("StandardId.Builder{");
buf.append("scheme").append('=').append(JodaBeanUtils.toString(scheme)).append(',').append(' ');
buf.append("value").append('=').append(JodaBeanUtils.toString(value));
buf.append('}');
return buf.toString();
}
}
///CLOVER:ON
//-------------------------- AUTOGENERATED END --------------------------
}