/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.collect.id; 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.ImmutablePreBuild; 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 com.opengamma.collect.ArgChecker; /** * Standard implementation of a link to a target object using an identifier. * <p> * A link provides loose coupling between different parts of the object model. * For example, an equity trade can hold a link to the underlying equity. * The two objects can be updated independently of each other. * If the link resolver supports historic versions, then the correct version of the * target will be returned when resolved. * The target object should be immutable to ensure the safety of the link. * <p> * A link can be in one of two states, resolvable and resolved. * <p> * In the resolvable state, the link contains the identifier and type of the target object. * The target can only be obtained using a {@link LinkResolver}. * <p> * In the resolved state, the link directly embeds the target object. * <p> * Links are expected to be resolvable. It is reasonable to expect that when * {@link #resolve(LinkResolver)} is called, the target of the link is available. * For this reason, if the target is not found, a {@link LinkResolutionException} will be thrown. * <p> * This is the standard implementation of {@link Link}. * It is suitable for all link targets except those that have generic parameters. * * @param <T> the type of the target */ @BeanDefinition(builderScope = "private") public final class StandardLink<T extends IdentifiableBean> implements Link<T>, Resolvable<StandardLink<T>>, StandardIdentifiable, ImmutableBean, Serializable { /** * The primary standard identifier of the target. * <p> * The standard identifier is used to identify the target. * It will typically be an identifier in an external data system. * <p> * This is used when the link is in the resolvable state. */ @PropertyDefinition(get = "field") private final StandardId standardId; /** * The type of the link target. * <p> * This is used when the link is in the resolvable state. */ @PropertyDefinition(get = "field") private final Class<T> targetType; /** * The embedded link target. * <p> * This is used when the link is in the resolved state. */ @PropertyDefinition(get = "field") private final T target; //------------------------------------------------------------------------- /** * Create a resolvable link for the specified identifier and type. * <p> * The returned link will be in the resolvable state, containing the identifier and type. * When the {@link #resolve(LinkResolver)} method is called, the resolver will be used * to find the target, for example from a database. * <p> * The type is expressed as a standard {@link Class} object. * * @param identifier the primary identifier of the target * @param targetType the type of the target * @return a new resolvable link */ public static <R extends IdentifiableBean> StandardLink<R> resolvable(StandardId identifier, Class<R> targetType) { return new StandardLink<>(identifier, targetType); } /** * Create a link with the link target embedded directly. * <p> * The returned link will be in the resolved state, directly embedding the target object. * When the {@link #resolve(LinkResolver)} method is called, the embedded target will be * returned without needing to use the resolver. * * @param target the link target * @return a new resolved link */ public static <R extends IdentifiableBean> StandardLink<R> resolved(R target) { return new StandardLink<>(target); } //------------------------------------------------------------------------- // constructor for resolvable state private StandardLink(StandardId identifier, Class<T> targetType) { this.standardId = ArgChecker.notNull(identifier, "identifier"); this.targetType = ArgChecker.notNull(targetType, "targetType"); this.target = null; } // constructor for resolved state private StandardLink(T target) { this.standardId = null; this.targetType = null; this.target = ArgChecker.notNull(target, "target"); } // validate the internal state, allowing the builder to set more than necessary so long as it is valid @SuppressWarnings({"rawtypes", "unchecked"}) @ImmutablePreBuild private static void preBuild(Builder builder) { if (builder.target != null) { if (builder.standardId != null) { if (builder.standardId.equals(builder.target.getStandardId())) { builder.standardId = null; } else { throw new IllegalArgumentException("Both target and identifier specified but with different identifiers"); } } if (builder.targetType != null) { if (builder.targetType.isAssignableFrom(builder.target.getClass())) { builder.targetType = null; } else { throw new IllegalArgumentException("Both target and targetType specified but with incompatible types"); } } } else { if (builder.standardId == null || builder.targetType == null) { throw new IllegalArgumentException("Either the target, or the identifer and target type must be specified"); } } } //------------------------------------------------------------------------- @Override public boolean isResolved() { return (target != null); } @Override public StandardId getStandardId() { return (isResolved() ? target.getStandardId() : standardId); } @Override @SuppressWarnings("unchecked") public Class<T> getTargetType() { return (isResolved() ? (Class<T>) target.getClass() : targetType); } //------------------------------------------------------------------------- /** * Resolves this link using the specified resolver. * <p> * The resolver is used to find the linked target. * In the resolvable state, the resolver is used to find the target, throwing * an exception of the target cannot be found. * In the resolved state, the directly embedded target is returned, * without using the resolver. * <p> * The returned target may contain other unresolved links. * * @param resolver the resolver to use for the resolution * @return the target * @throws LinkResolutionException if the target cannot be resolved */ @Override @SuppressWarnings("unchecked") public T resolve(LinkResolver resolver) { return (isResolved() ? target : resolver.resolve(standardId, targetType)); } /** * Resolves this link, and any links that the target contains, using the specified resolver. * <p> * First, the target is resolved using {@link #resolve(LinkResolver)}. * Second, if the target implements {@link Resolvable}, it is resolved as well. * The result is wrapped using {@link StandardLink#resolved(IdentifiableBean)}. * The returned target should not contain any unresolved links. * * @param linkResolver the resolver to use for the resolution * @return the fully resolved link * @throws LinkResolutionException if a link cannot be resolved */ @Override public StandardLink<T> resolveLinks(LinkResolver resolver) { T resolvedTarget = resolve(resolver); if (resolvedTarget instanceof Resolvable) { @SuppressWarnings("unchecked") Resolvable<T> resolvableTarget = (Resolvable<T>) resolvedTarget; resolvedTarget = resolvableTarget.resolveLinks(resolver); } return (resolvedTarget == this.target ? this : new StandardLink<>(resolvedTarget)); } //------------------------- AUTOGENERATED START ------------------------- ///CLOVER:OFF /** * The meta-bean for {@code StandardLink}. * @return the meta-bean, not null */ @SuppressWarnings("rawtypes") public static StandardLink.Meta meta() { return StandardLink.Meta.INSTANCE; } /** * The meta-bean for {@code StandardLink}. * @param <R> the bean's generic type * @param cls the bean's generic type * @return the meta-bean, not null */ @SuppressWarnings("unchecked") public static <R extends IdentifiableBean> StandardLink.Meta<R> metaStandardLink(Class<R> cls) { return StandardLink.Meta.INSTANCE; } static { JodaBeanUtils.registerMetaBean(StandardLink.Meta.INSTANCE); } /** * The serialization version id. */ private static final long serialVersionUID = 1L; private StandardLink( StandardId standardId, Class<T> targetType, T target) { this.standardId = standardId; this.targetType = targetType; this.target = target; } @SuppressWarnings("unchecked") @Override public StandardLink.Meta<T> metaBean() { return StandardLink.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(); } //----------------------------------------------------------------------- @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj.getClass() == this.getClass()) { StandardLink<?> other = (StandardLink<?>) obj; return JodaBeanUtils.equal(standardId, other.standardId) && JodaBeanUtils.equal(targetType, other.targetType) && JodaBeanUtils.equal(target, other.target); } return false; } @Override public int hashCode() { int hash = getClass().hashCode(); hash = hash * 31 + JodaBeanUtils.hashCode(standardId); hash = hash * 31 + JodaBeanUtils.hashCode(targetType); hash = hash * 31 + JodaBeanUtils.hashCode(target); return hash; } @Override public String toString() { StringBuilder buf = new StringBuilder(128); buf.append("StandardLink{"); buf.append("standardId").append('=').append(standardId).append(',').append(' '); buf.append("targetType").append('=').append(targetType).append(',').append(' '); buf.append("target").append('=').append(JodaBeanUtils.toString(target)); buf.append('}'); return buf.toString(); } //----------------------------------------------------------------------- /** * The meta-bean for {@code StandardLink}. * @param <T> the type */ public static final class Meta<T extends IdentifiableBean> extends DirectMetaBean { /** * The singleton instance of the meta-bean. */ @SuppressWarnings("rawtypes") static final Meta INSTANCE = new Meta(); /** * The meta-property for the {@code standardId} property. */ private final MetaProperty<StandardId> standardId = DirectMetaProperty.ofImmutable( this, "standardId", StandardLink.class, StandardId.class); /** * The meta-property for the {@code targetType} property. */ @SuppressWarnings({"unchecked", "rawtypes" }) private final MetaProperty<Class<T>> targetType = DirectMetaProperty.ofImmutable( this, "targetType", StandardLink.class, (Class) Class.class); /** * The meta-property for the {@code target} property. */ @SuppressWarnings({"unchecked", "rawtypes" }) private final MetaProperty<T> target = (DirectMetaProperty) DirectMetaProperty.ofImmutable( this, "target", StandardLink.class, Object.class); /** * The meta-properties. */ private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap( this, null, "standardId", "targetType", "target"); /** * Restricted constructor. */ private Meta() { } @Override protected MetaProperty<?> metaPropertyGet(String propertyName) { switch (propertyName.hashCode()) { case -1284477768: // standardId return standardId; case 486622315: // targetType return targetType; case -880905839: // target return target; } return super.metaPropertyGet(propertyName); } @Override public BeanBuilder<? extends StandardLink<T>> builder() { return new StandardLink.Builder<T>(); } @SuppressWarnings({"unchecked", "rawtypes" }) @Override public Class<? extends StandardLink<T>> beanType() { return (Class) StandardLink.class; } @Override public Map<String, MetaProperty<?>> metaPropertyMap() { return metaPropertyMap$; } //----------------------------------------------------------------------- /** * The meta-property for the {@code standardId} property. * @return the meta-property, not null */ public MetaProperty<StandardId> standardId() { return standardId; } /** * The meta-property for the {@code targetType} property. * @return the meta-property, not null */ public MetaProperty<Class<T>> targetType() { return targetType; } /** * The meta-property for the {@code target} property. * @return the meta-property, not null */ public MetaProperty<T> target() { return target; } //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { switch (propertyName.hashCode()) { case -1284477768: // standardId return ((StandardLink<?>) bean).standardId; case 486622315: // targetType return ((StandardLink<?>) bean).targetType; case -880905839: // target return ((StandardLink<?>) bean).target; } 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 StandardLink}. * @param <T> the type */ private static final class Builder<T extends IdentifiableBean> extends DirectFieldsBeanBuilder<StandardLink<T>> { private StandardId standardId; private Class<T> targetType; private T target; /** * Restricted constructor. */ private Builder() { } //----------------------------------------------------------------------- @Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case -1284477768: // standardId return standardId; case 486622315: // targetType return targetType; case -880905839: // target return target; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } @SuppressWarnings("unchecked") @Override public Builder<T> set(String propertyName, Object newValue) { switch (propertyName.hashCode()) { case -1284477768: // standardId this.standardId = (StandardId) newValue; break; case 486622315: // targetType this.targetType = (Class<T>) newValue; break; case -880905839: // target this.target = (T) newValue; break; default: throw new NoSuchElementException("Unknown property: " + propertyName); } return this; } @Override public Builder<T> set(MetaProperty<?> property, Object value) { super.set(property, value); return this; } @Override public Builder<T> setString(String propertyName, String value) { setString(meta().metaProperty(propertyName), value); return this; } @Override public Builder<T> setString(MetaProperty<?> property, String value) { super.setString(property, value); return this; } @Override public Builder<T> setAll(Map<String, ? extends Object> propertyValueMap) { super.setAll(propertyValueMap); return this; } @Override public StandardLink<T> build() { preBuild(this); return new StandardLink<T>( standardId, targetType, target); } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(128); buf.append("StandardLink.Builder{"); buf.append("standardId").append('=').append(JodaBeanUtils.toString(standardId)).append(',').append(' '); buf.append("targetType").append('=').append(JodaBeanUtils.toString(targetType)).append(',').append(' '); buf.append("target").append('=').append(JodaBeanUtils.toString(target)); buf.append('}'); return buf.toString(); } } ///CLOVER:ON //-------------------------- AUTOGENERATED END -------------------------- }