Java tutorial
/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.engine.config; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Parameter; 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.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.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.primitives.Primitives; import com.opengamma.strata.basics.CalculationTarget; import com.opengamma.strata.collect.Messages; import com.opengamma.strata.engine.calculation.function.CalculationSingleFunction; /** * Configuration of a function that performs a calculation. * <p> * The configuration includes the function type and may include constructor arguments used when creating * function instances. Any constructor arguments not included in the configuration can be provided as * an argument to {@link #createFunction(Map)}. * <p> * Constructor arguments passed to {@code createFunction} are not permitted to override * arguments in the configuration. An exception will be thrown if any argument passed to {@code createFunction} * has the same name as an argument in the configuration. * * @param <T> the type of the calculation target */ @BeanDefinition(builderScope = "private", constructorScope = "package") public final class FunctionConfig<T extends CalculationTarget> implements ImmutableBean, Serializable { private static final Logger log = LoggerFactory.getLogger(FunctionConfig.class); /** Configuration used when there is none defined for a calculation. Creates {@link MissingConfigCalculationFunction}. */ private static final FunctionConfig<? extends CalculationTarget> MISSING = FunctionConfig .of(MissingConfigCalculationFunction.class); // TODO FunctionMetadata instead of function type - includes type and set of calculated measures /** The type of the function. */ @PropertyDefinition(validate = "notNull", get = "private") private final Class<? extends CalculationSingleFunction<T, ?>> functionType; /** Constructor arguments used for building function instances, keyed by parameter name. */ @PropertyDefinition(validate = "notNull", get = "private") private final Map<String, Object> arguments; /** * Returns configuration for a function that doesn't contain any constructor arguments. * <p> * The function must have one public constructor. If the constructor requires arguments, they * must be passed to {@link #createFunction}. * <p> * To create configuration that includes constructor arguments, use a {@linkplain #builder(Class) builder}. * * @param functionType the type of the function * @return configuration for a function that doesn't contain any constructor arguments */ public static <T extends CalculationTarget> FunctionConfig<T> of( Class<? extends CalculationSingleFunction<T, ?>> functionType) { return new FunctionConfig<>(functionType, ImmutableMap.of()); } /** * Returns a mutable builder for building {@code FunctionConfig}. * * @param functionType the type of the function * @param <T> the type of the calculation target * @return a mutable builder for building {@code FunctionConfig} */ public static <T extends CalculationTarget> FunctionConfigBuilder<T> builder( Class<? extends CalculationSingleFunction<T, ?>> functionType) { return new FunctionConfigBuilder<>(functionType); } /** * Returns configuration for a function that is used when no function is configured to calculate a value. * <p> * The function always returns a failure result. * * @param <T> the type of the calculation target * @return configuration for a function that always returns a failure */ @SuppressWarnings("unchecked") public static <T extends CalculationTarget> FunctionConfig<T> missing() { return (FunctionConfig<T>) MISSING; } // TODO Method returning parameter metadata for the required constructor arguments? /** * Returns a function instance created using the specified constructor arguments. * <p> * Throws an exception if the function requires constructor arguments that have not been provided * or if any of the supplied arguments have the same name as the arguments in the configuration. * * @param arguments constructor arguments for the function instance * @return a function instance created using the specified constructor arguments * @throws IllegalArgumentException if the function requires constructor arguments that have not been provided * or if any of the supplied arguments have the same name as the arguments in the configuration */ @SuppressWarnings("unchecked") public CalculationSingleFunction<T, ?> createFunction(Map<String, Object> arguments) { Map<String, Object> mergedArguments = mergedArguments(arguments); Constructor<?> constructor = constructor(functionType); Object[] argumentArray = constructorArguments(constructor, mergedArguments); try { return (CalculationSingleFunction<T, ?>) constructor.newInstance(argumentArray); } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { log.warn("Failed to create engine function", e); return (CalculationSingleFunction<T, ?>) new MissingConfigCalculationFunction(); } } /** * Returns a function instance created using the constructor arguments from the configuration. * <p> * Throws an exception if the function requires constructor arguments that are not available in the configuration. * * @return a function instance * @throws IllegalArgumentException if the function requires constructor arguments that have not been provided */ @SuppressWarnings("unchecked") public CalculationSingleFunction<T, ?> createFunction() { return createFunction(ImmutableMap.of()); } private Map<String, Object> mergedArguments(Map<String, Object> arguments) { Set<String> intersection = Sets.intersection(this.arguments.keySet(), arguments.keySet()); if (!intersection.isEmpty()) { throw new IllegalArgumentException( Messages.format("Built-in function arguments cannot be overridden: {}", intersection)); } return ImmutableMap.<String, Object>builder().putAll(this.arguments).putAll(arguments).build(); } /** * Returns a constructor for creating a function instance. * <p> * The function type must have one public constructor. * * @param functionType a function type * @return a constructor for creating a function instance * @throws IllegalArgumentException if the function doesn't have exactly one public constructor */ private static Constructor<?> constructor(Class<?> functionType) { Constructor<?>[] constructors = functionType.getConstructors(); if (constructors.length == 1) { return constructors[0]; } else { throw new IllegalArgumentException(Messages.format( "Functions must have one public constructor, {} has {}", functionType, constructors.length)); } } /** * Takes a map of constructor arguments keyed by parameter name and returns an array of arguments suitable * for passing to {@code Constructor.newInstance}. * * @param constructor a constructor * @param arguments arguments for the constructor, keyed by parameter name * @return an array of arguments suitable for passing to {@code Constructor.newInstance} * @throws IllegalArgumentException if the constructor requires arguments that aren't in the map, or * if an argument type is not compatible with the parameter type */ private static Object[] constructorArguments(Constructor<?> constructor, Map<String, Object> arguments) { Parameter[] parameters = constructor.getParameters(); Object[] argumentArray = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; String parameterName = parameter.getName(); Class<?> parameterType = parameter.getType(); Object argument = arguments.get(parameterName); if (argument == null) { throw new IllegalArgumentException( Messages.format("No argument found with name '{}'", parameterName)); } if (!isArgumentCompatible(parameterType, argument.getClass())) { throw new IllegalArgumentException(Messages.format( "Argument is not compatible with the parameter type. name={}, value={}, type={}. Parameter type={}, " + "constructor={}", parameterName, argument, argument.getClass().getName(), parameterType.getName(), constructor)); } argumentArray[i] = argument; } return argumentArray; } /** * Returns true if the argument type is compatible with the parameter type. * <p> * An argument type is compatible if the parameter type is assignable from the argument type. If the * parameter type is primitive, they are compatible if the argument type is the boxed type for the primitive. * * @param parameterType the type of a constructor parameter * @param argumentType the type of the constructor argument * @return true if the argument type is compatible with the parameter type */ private static boolean isArgumentCompatible(Class<?> parameterType, Class<?> argumentType) { if (parameterType.isAssignableFrom(argumentType)) { return true; } // If the parameter type isn't primitive and they're not assignable then they're definitely not compatible if (!parameterType.isPrimitive()) { return false; } return isArgumentCompatible(Primitives.wrap(parameterType), argumentType); } //------------------------- AUTOGENERATED START ------------------------- ///CLOVER:OFF /** * The meta-bean for {@code FunctionConfig}. * @return the meta-bean, not null */ @SuppressWarnings("rawtypes") public static FunctionConfig.Meta meta() { return FunctionConfig.Meta.INSTANCE; } /** * The meta-bean for {@code FunctionConfig}. * @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 CalculationTarget> FunctionConfig.Meta<R> metaFunctionConfig(Class<R> cls) { return FunctionConfig.Meta.INSTANCE; } static { JodaBeanUtils.registerMetaBean(FunctionConfig.Meta.INSTANCE); } /** * The serialization version id. */ private static final long serialVersionUID = 1L; /** * Creates an instance. * @param functionType the value of the property, not null * @param arguments the value of the property, not null */ FunctionConfig(Class<? extends CalculationSingleFunction<T, ?>> functionType, Map<String, Object> arguments) { JodaBeanUtils.notNull(functionType, "functionType"); JodaBeanUtils.notNull(arguments, "arguments"); this.functionType = functionType; this.arguments = ImmutableMap.copyOf(arguments); } @SuppressWarnings("unchecked") @Override public FunctionConfig.Meta<T> metaBean() { return FunctionConfig.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 type of the function. * @return the value of the property, not null */ private Class<? extends CalculationSingleFunction<T, ?>> getFunctionType() { return functionType; } //----------------------------------------------------------------------- /** * Gets constructor arguments used for building function instances, keyed by parameter name. * @return the value of the property, not null */ private Map<String, Object> getArguments() { return arguments; } //----------------------------------------------------------------------- @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj != null && obj.getClass() == this.getClass()) { FunctionConfig<?> other = (FunctionConfig<?>) obj; return JodaBeanUtils.equal(getFunctionType(), other.getFunctionType()) && JodaBeanUtils.equal(getArguments(), other.getArguments()); } return false; } @Override public int hashCode() { int hash = getClass().hashCode(); hash = hash * 31 + JodaBeanUtils.hashCode(getFunctionType()); hash = hash * 31 + JodaBeanUtils.hashCode(getArguments()); return hash; } @Override public String toString() { StringBuilder buf = new StringBuilder(96); buf.append("FunctionConfig{"); buf.append("functionType").append('=').append(getFunctionType()).append(',').append(' '); buf.append("arguments").append('=').append(JodaBeanUtils.toString(getArguments())); buf.append('}'); return buf.toString(); } //----------------------------------------------------------------------- /** * The meta-bean for {@code FunctionConfig}. * @param <T> the type */ public static final class Meta<T extends CalculationTarget> extends DirectMetaBean { /** * The singleton instance of the meta-bean. */ @SuppressWarnings("rawtypes") static final Meta INSTANCE = new Meta(); /** * The meta-property for the {@code functionType} property. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private final MetaProperty<Class<? extends CalculationSingleFunction<T, ?>>> functionType = DirectMetaProperty .ofImmutable(this, "functionType", FunctionConfig.class, (Class) Class.class); /** * The meta-property for the {@code arguments} property. */ @SuppressWarnings({ "unchecked", "rawtypes" }) private final MetaProperty<Map<String, Object>> arguments = DirectMetaProperty.ofImmutable(this, "arguments", FunctionConfig.class, (Class) Map.class); /** * The meta-properties. */ private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap(this, null, "functionType", "arguments"); /** * Restricted constructor. */ private Meta() { } @Override protected MetaProperty<?> metaPropertyGet(String propertyName) { switch (propertyName.hashCode()) { case -211170510: // functionType return functionType; case -2035517098: // arguments return arguments; } return super.metaPropertyGet(propertyName); } @Override public BeanBuilder<? extends FunctionConfig<T>> builder() { return new FunctionConfig.Builder<T>(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Class<? extends FunctionConfig<T>> beanType() { return (Class) FunctionConfig.class; } @Override public Map<String, MetaProperty<?>> metaPropertyMap() { return metaPropertyMap$; } //----------------------------------------------------------------------- /** * The meta-property for the {@code functionType} property. * @return the meta-property, not null */ public MetaProperty<Class<? extends CalculationSingleFunction<T, ?>>> functionType() { return functionType; } /** * The meta-property for the {@code arguments} property. * @return the meta-property, not null */ public MetaProperty<Map<String, Object>> arguments() { return arguments; } //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { switch (propertyName.hashCode()) { case -211170510: // functionType return ((FunctionConfig<?>) bean).getFunctionType(); case -2035517098: // arguments return ((FunctionConfig<?>) bean).getArguments(); } 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 FunctionConfig}. * @param <T> the type */ private static final class Builder<T extends CalculationTarget> extends DirectFieldsBeanBuilder<FunctionConfig<T>> { private Class<? extends CalculationSingleFunction<T, ?>> functionType; private Map<String, Object> arguments = ImmutableMap.of(); /** * Restricted constructor. */ private Builder() { } //----------------------------------------------------------------------- @Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case -211170510: // functionType return functionType; case -2035517098: // arguments return arguments; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } @SuppressWarnings("unchecked") @Override public Builder<T> set(String propertyName, Object newValue) { switch (propertyName.hashCode()) { case -211170510: // functionType this.functionType = (Class<? extends CalculationSingleFunction<T, ?>>) newValue; break; case -2035517098: // arguments this.arguments = (Map<String, Object>) 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 FunctionConfig<T> build() { return new FunctionConfig<T>(functionType, arguments); } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(96); buf.append("FunctionConfig.Builder{"); buf.append("functionType").append('=').append(JodaBeanUtils.toString(functionType)).append(',') .append(' '); buf.append("arguments").append('=').append(JodaBeanUtils.toString(arguments)); buf.append('}'); return buf.toString(); } } ///CLOVER:ON //-------------------------- AUTOGENERATED END -------------------------- }