/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-2008, Open Source Geospatial Foundation (OSGeo) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. */ package org.geotools.parameter; import java.io.File; import java.net.URL; import java.net.URI; import java.net.URISyntaxException; import java.util.Set; import javax.measure.converter.UnitConverter; import javax.measure.unit.NonSI; import javax.measure.unit.SI; import javax.measure.unit.Unit; import org.opengis.parameter.InvalidParameterTypeException; import org.opengis.parameter.InvalidParameterValueException; import org.opengis.parameter.ParameterDescriptor; import org.opengis.parameter.ParameterValue; import org.opengis.util.CodeList; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.resources.Classes; import org.geotools.measure.Units; import org.geotools.util.Utilities; /** * A parameter value used by an operation method. * Most CRS parameter values are numeric, but other types of parameter values are possible. * The parameter type can be fetch with the * <code>{@linkplain #getValue()}.{@linkplain Object#getClass() getClass()}</code> idiom. * The {@link #getValue()} and {@link #setValue(Object)} methods can be invoked at any time. * Others getters and setters are parameter-type dependents. * * @param <T> The value type. * * @since 2.1 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * @author Jody Garnett (Refractions Research) * * @see DefaultParameterDescriptor * @see ParameterGroup */ public class Parameter<T> extends AbstractParameter implements ParameterValue<T> { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -5837826787089486776L; /** * The value. */ private T value; /** * The unit of measure for the value, or {@code null} if it doesn't apply. */ private Unit<?> unit; // /** // * Constructs a parameter from the specified name and value. This convenience // * constructor creates a {@link DefaultParameterDescriptor} object. But if such // * an object was available, then the preferred way to get a {@code ParameterValue} // * is to invokes {@link ParameterDescriptor#createValue}. // * // * @param name The parameter name. // * @param value The parameter value. // * // * @deprecated This constructor can not ensure type safety with parameterized types. // * Use the static {@code create} methods instead. // */ // @Deprecated // public Parameter(final String name, final int value) { // this(DefaultParameterDescriptor.create(name, 0, Integer.MIN_VALUE, Integer.MAX_VALUE)); // this.value = (T) (Object) value; // } // // /** // * Constructs a parameter from the specified name and value. This convenience // * constructor creates a {@link DefaultParameterDescriptor} object. But if such // * an object was available, then the preferred way to get a {@code ParameterValue} is // * to invokes {@link ParameterDescriptor#createValue}. // * // * @param name The parameter name. // * @param value The parameter value. // * @param unit The unit for the parameter value. // * // * @deprecated This constructor can not ensure type safety with parameterized types. // * Use the static {@code create} methods instead. // */ // @Deprecated // public Parameter(final String name, final double value, final Unit<?> unit) { // this(DefaultParameterDescriptor.create(name, Double.NaN, Double.NEGATIVE_INFINITY, // Double.POSITIVE_INFINITY, normalize(unit))); // this.value = (T) (Object) value; // this.unit = unit; // } // // /** // * Constructs a parameter from the specified enumeration. This convenience // * constructor creates a {@link DefaultParameterDescriptor} object. But if // * such an object was available, then the preferred way to get a {@code ParameterValue} // * is to invokes {@link ParameterDescriptor#createValue}. // * // * @param name The parameter name. // * @param value The parameter value. // * // * @deprecated This constructor can not ensure type safety with parameterized types. // * Use the static {@code create} methods instead. // */ // @Deprecated // public Parameter(final String name, final CodeList value) { // this(new DefaultParameterDescriptor(name, value.getClass(), null,(CodeList)null)); // this.value = (T) (Object) value; // } /** * Constructs a parameter value from the specified descriptor. * The value will be initialized to the default value, if any. * * @param descriptor The abstract definition of this parameter. */ public Parameter(final ParameterDescriptor<T> descriptor) { super(descriptor); value = descriptor.getDefaultValue(); unit = descriptor.getUnit(); } /** * Constructs a parameter value from the specified descriptor and value. * * @param descriptor The abstract definition of this parameter. * @param value The parameter value. * @throws InvalidParameterValueException if the type of {@code value} is inappropriate * for this parameter, or if the value is illegal for some other reason (for example * the value is numeric and out of range). */ public Parameter(final ParameterDescriptor<T> descriptor, final T value) throws InvalidParameterValueException { super(descriptor); unit = descriptor.getUnit(); setValue(value); } /** * Constructs a parameter from the specified name and value. This convenience * constructor creates a {@link DefaultParameterDescriptor} object. But if such * an object is available, then the preferred way to get a {@code ParameterValue} * is to invoke {@link ParameterDescriptor#createValue}. * * @param name The parameter name. * @param value The parameter value. * @return A new parameter instance for the given name and value. * * @since 2.5 */ public static Parameter<Integer> create(final String name, final int value) { final ParameterDescriptor<Integer> descriptor = DefaultParameterDescriptor.create( name, 0, Integer.MIN_VALUE, Integer.MAX_VALUE); final Parameter<Integer> parameter = new Parameter<Integer>(descriptor); parameter.value = value; return parameter; } /** * Constructs a parameter from the specified name and value. This convenience * constructor creates a {@link DefaultParameterDescriptor} object. But if such * an object is available, then the preferred way to get a {@code ParameterValue} * is to invoke {@link ParameterDescriptor#createValue}. * * @param name The parameter name. * @param value The parameter value. * @param unit The unit for the parameter value. * @return A new parameter instance for the given name and value. * * @since 2.5 */ public static Parameter<Double> create(final String name, final double value, Unit<?> unit) { // Normalizes the specified unit into one of "standard" units used in projections. if (unit != null) { if (SI.METER .isCompatible(unit)) unit = SI.METER; else if (NonSI.DAY .isCompatible(unit)) unit = NonSI.DAY; else if (NonSI.DEGREE_ANGLE.isCompatible(unit)) unit = NonSI.DEGREE_ANGLE; } final ParameterDescriptor<Double> descriptor = DefaultParameterDescriptor.create( name, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, unit); final Parameter<Double> parameter = new Parameter<Double>(descriptor); parameter.value = value; parameter.unit = unit; return parameter; } /** * Constructs a parameter from the specified code list. This convenience * constructor creates a {@link DefaultParameterDescriptor} object. But if * such an object is available, then the preferred way to get a {@code ParameterValue} * is to invoke {@link ParameterDescriptor#createValue}. * * @param <T> The parameter type. * @param name The parameter name. * @param type The parameter type. * @param value The parameter value. * @return A new parameter instance for the given name and value. * * @since 2.5 */ public static <T extends CodeList> Parameter<T> create( final String name, final Class<T> type, final T value) { final ParameterDescriptor<T> descriptor = DefaultParameterDescriptor.create(name, null, type, null, true); final Parameter<T> parameter = new Parameter<T>(descriptor); parameter.value = value; return parameter; } /** * Ensures that the given value is valid according the specified parameter descriptor. * This convenience method ensures that {@code value} is assignable to the * {@linkplain ParameterDescriptor#getValueClass expected class}, is between the * {@linkplain ParameterDescriptor#getMinimumValue minimum} and * {@linkplain ParameterDescriptor#getMaximumValue maximum} values and is one of the * {@linkplain ParameterDescriptor#getValidValues set of valid values}. * If the value fails any of those tests, then an exception is thrown. * <p> * This method is similar to <code>{@linkplain Parameters#isValid * Parameters#isValid}(descriptor, value)</code> except that the exception contains an * error message formatted with a description of the failure reason. * * @param <T> The type of parameter value. The given {@code value} should typically be an * instance of this class. This is not required by this method signature but is * checked by this method implementation. * @param descriptor The parameter descriptor to check against. * @param value The value to check, or {@code null}. * @return The value casted to the descriptor parameterized type. * @throws InvalidParameterValueException if the parameter value is invalid. */ public static <T> T ensureValidValue(final ParameterDescriptor<T> descriptor, final Object value) throws InvalidParameterValueException { if (value == null) { return null; } final String error; final Class<T> type = descriptor.getValueClass(); if (!type.isInstance(value)) { error = Errors.format(ErrorKeys.ILLEGAL_OPERATION_FOR_VALUE_CLASS_$1, Classes.getClass(value)); } else { @SuppressWarnings("unchecked") // Type checked in the above test case. final Comparable<Object> minimum = (Comparable) descriptor.getMinimumValue(); @SuppressWarnings("unchecked") final Comparable<Object> maximum = (Comparable) descriptor.getMaximumValue(); if ((minimum != null && minimum.compareTo(value) > 0) || (maximum != null && maximum.compareTo(value) < 0)) { error = Errors.format(ErrorKeys.VALUE_OUT_OF_BOUNDS_$3, value, minimum, maximum); } else { final Set<?> validValues = descriptor.getValidValues(); if (validValues!=null && !validValues.contains(value)) { error = Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, getName(descriptor), value); } else { return type.cast(value); } } } throw new InvalidParameterValueException(error, getName(descriptor), value); } /** * Format an error message for illegal method call for the current value type. */ private String getClassTypeError() { return Errors.format(ErrorKeys.ILLEGAL_OPERATION_FOR_VALUE_CLASS_$1, ((ParameterDescriptor) descriptor).getValueClass()); } /** * Returns the abstract definition of this parameter. */ @Override @SuppressWarnings("unchecked") // Type checked by the constructor. public ParameterDescriptor<T> getDescriptor() { return (ParameterDescriptor) super.getDescriptor(); } /** * Returns the unit of measure of the {@linkplain #doubleValue() parameter value}. * If the parameter value has no unit (for example because it is a {@link String} type), * then this method returns {@code null}. Note that "no unit" doesn't means * "dimensionless". * * @return The unit of measure, or {@code null} if none. * * @see #doubleValue() * @see #doubleValueList() * @see #getValue */ public Unit<?> getUnit() { return unit; } /** * Returns the unit type as one of error message code. Used for checking unit with a better * error message formatting if needed. * <p> * Note: It is difficult to differentiate scale and angular units, since both of them are * dimensionless. However, in EPSG database version 6.7, there is only 3 scale units * and all of them maps to {@link Unit#ONE} or {@link Units#PPM}. Consequently, they * are hard-coded and treated especially by this method. * * @todo Provides a better way to differentiate scale units (currently Unit.ONE) * and angular units. Both are dimensionless... */ static int getUnitMessageID(final Unit<?> unit) { // Note: ONE must be tested before RADIAN. if (Unit.ONE .equals (unit) || Units.PPM.equals (unit)) return ErrorKeys.NON_SCALE_UNIT_$1; if (SI.METER .isCompatible(unit)) return ErrorKeys.NON_LINEAR_UNIT_$1; if (SI.SECOND.isCompatible(unit)) return ErrorKeys.NON_TEMPORAL_UNIT_$1; if (SI.RADIAN.isCompatible(unit)) return ErrorKeys.NON_ANGULAR_UNIT_$1; return ErrorKeys.INCOMPATIBLE_UNIT_$1; } /** * Returns the numeric value of the coordinate operation parameter in the specified unit * of measure. This convenience method apply unit conversion on the fly as needed. * * @param unit The unit of measure for the value to be returned. * @return The numeric value represented by this parameter after conversion to type * {@code double} and conversion to {@code unit}. * @throws InvalidParameterTypeException if the value is not a numeric type. * @throws IllegalArgumentException if the specified unit is invalid for this parameter. * * @see #getUnit * @see #setValue(double,Unit) * @see #doubleValueList(Unit) */ public double doubleValue(final Unit<?> unit) throws InvalidParameterTypeException { if (this.unit == null) { throw unitlessParameter(descriptor); } ensureNonNull("unit", unit); final int expectedID = getUnitMessageID(this.unit); if (getUnitMessageID(unit) != expectedID) { throw new IllegalArgumentException(Errors.format(expectedID, unit)); } return this.unit.getConverterTo(unit).convert(doubleValue()); } /** * Returns the numeric value of the coordinate operation parameter with its * associated {@linkplain #getUnit unit of measure}. * * @return The numeric value represented by this parameter after conversion to type {@code double}. * @throws InvalidParameterTypeException if the value is not a numeric type. * * @see #getUnit * @see #setValue(double) * @see #doubleValueList() */ public double doubleValue() throws InvalidParameterTypeException { if (value instanceof Number) { return ((Number) value).doubleValue(); } final String name = getName(descriptor); if (value == null) { // This is the kind of exception expected by org.geotools.referencing.wkt.Formatter. throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } // Reminder: the following is a specialization of IllegalStateException. throw new InvalidParameterTypeException(getClassTypeError(), name); } /** * Returns the positive integer value of an operation parameter, usually used * for a count. An integer value does not have an associated unit of measure. * * @return The numeric value represented by this parameter after conversion to type {@code int}. * @throws InvalidParameterTypeException if the value is not an integer type. * * @see #setValue(int) * @see #intValueList */ public int intValue() throws InvalidParameterTypeException { if (value instanceof Number) { return ((Number) value).intValue(); } final String name = getName(descriptor); if (value == null) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } throw new InvalidParameterTypeException(getClassTypeError(), name); } /** * Returns the boolean value of an operation parameter. * A boolean value does not have an associated unit of measure. * * @return The boolean value represented by this parameter. * @throws InvalidParameterTypeException if the value is not a boolean type. * * @see #setValue(boolean) */ public boolean booleanValue() throws InvalidParameterTypeException { if (value instanceof Boolean) { return ((Boolean) value).booleanValue(); } final String name = getName(descriptor); if (value == null) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } throw new InvalidParameterTypeException(getClassTypeError(), name); } /** * Returns the string value of an operation parameter. * A string value does not have an associated unit of measure. * * @return The string value represented by this parameter. * @throws InvalidParameterTypeException if the value is not a string. * * @see #getValue * @see #setValue(Object) */ public String stringValue() throws InvalidParameterTypeException { if (value instanceof CharSequence) { return value.toString(); } final String name = getName(descriptor); if (value == null) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } throw new InvalidParameterTypeException(getClassTypeError(), name); } /** * Returns an ordered sequence of numeric values in the specified unit of measure. * This convenience method apply unit conversion on the fly as needed. * * @param unit The unit of measure for the value to be returned. * @return The sequence of values represented by this parameter after conversion to type * {@code double} and conversion to {@code unit}. * @throws InvalidParameterTypeException if the value is not an array of {@code double}s. * @throws IllegalArgumentException if the specified unit is invalid for this parameter. * * @see #getUnit * @see #setValue(double[],Unit) * @see #doubleValue(Unit) */ public double[] doubleValueList(final Unit<?> unit) throws InvalidParameterTypeException { if (this.unit == null) { throw unitlessParameter(descriptor); } ensureNonNull("unit", unit); final int expectedID = getUnitMessageID(this.unit); if (getUnitMessageID(unit) != expectedID) { throw new IllegalArgumentException(Errors.format(expectedID, unit)); } final UnitConverter converter = this.unit.getConverterTo(unit); final double[] values = doubleValueList().clone(); for (int i=0; i<values.length; i++) { values[i] = converter.convert(values[i]); } return values; } /** * Returns an ordered sequence of two or more numeric values of an operation parameter * list, where each value has the same associated {@linkplain Unit unit of measure}. * * @return The sequence of values represented by this parameter. * @throws InvalidParameterTypeException if the value is not an array of {@code double}s. * * @see #getUnit * @see #setValue(Object) * @see #doubleValue() */ public double[] doubleValueList() throws InvalidParameterTypeException { if (value instanceof double[]) { return (double[]) value; } final String name = getName(descriptor); if (value == null) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } throw new InvalidParameterTypeException(getClassTypeError(), name); } /** * Returns an ordered sequence of two or more integer values of an operation parameter list, * usually used for counts. These integer values do not have an associated unit of measure. * * @return The sequence of values represented by this parameter. * @throws InvalidParameterTypeException if the value is not an array of {@code int}s. * * @see #setValue(Object) * @see #intValue */ public int[] intValueList() throws InvalidParameterTypeException { if (value instanceof int[]) { return (int[]) value; } final String name = getName(descriptor); if (value == null) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } throw new InvalidParameterTypeException(getClassTypeError(), name); } /** * Returns a reference to a file or a part of a file containing one or more parameter * values. When referencing a part of a file, that file must contain multiple identified * parts, such as an XML encoded document. Furthermore, the referenced file or part of a * file can reference another part of the same or different files, as allowed in XML documents. * * @return The reference to a file containing parameter values. * @throws InvalidParameterTypeException if the value is not a reference to a file or an URI. * * @see #getValue * @see #setValue(Object) */ public URI valueFile() throws InvalidParameterTypeException { if (value instanceof URI) { return (URI) value; } if (value instanceof File) { return ((File) value).toURI(); } Exception cause = null; try { if (value instanceof URL) { return ((URL) value).toURI(); } if (value instanceof String) { return new URI((String) value); } } catch (URISyntaxException exception) { cause = exception; } /* * Value can't be converted. */ final String name = getName(descriptor); if (value == null) { throw new IllegalStateException(Errors.format(ErrorKeys.MISSING_PARAMETER_$1, name)); } final InvalidParameterTypeException exception = new InvalidParameterTypeException(getClassTypeError(), name); if (cause != null) { exception.initCause(cause); } throw exception; } /** * Returns the parameter value as an object. The object type is typically a {@link Double}, * {@link Integer}, {@link Boolean}, {@link String}, {@link URI}, {@code double[]} or * {@code int[]}. * * @return The parameter value as an object. * * @see #setValue(Object) */ public T getValue() { return value; } /** * Sets the parameter value as a floating point and its associated unit. * * @param value The parameter value. * @param unit The unit for the specified value. * @throws InvalidParameterValueException if the floating point type is inappropriate for this * parameter, or if the value is illegal for some other reason (for example a value out * of range). * * @see #setValue(double) * @see #doubleValue(Unit) */ public void setValue(final double value, final Unit<?> unit) throws InvalidParameterValueException { ensureNonNull("unit", unit); @SuppressWarnings("unchecked") // Checked by constructor. final ParameterDescriptor<T> descriptor = (ParameterDescriptor) this.descriptor; final Unit<?> targetUnit = descriptor.getUnit(); if (targetUnit == null) { throw unitlessParameter(descriptor); } final int expectedID = getUnitMessageID(targetUnit); if (getUnitMessageID(unit) != expectedID) { throw new InvalidParameterValueException(Errors.format(expectedID, unit), descriptor.getName().getCode(), value); } final Double converted = unit.getConverterTo(targetUnit).convert(value); ensureValidValue(descriptor, converted); // Really store the original value, not the converted one, // because we store the unit as well. this.value = descriptor.getValueClass().cast(value); this.unit = unit; } /** * Sets the parameter value as a floating point. * The unit, if any, stay unchanged. * * @param value The parameter value. * @throws InvalidParameterValueException if the floating point type is inappropriate for this * parameter, or if the value is illegal for some other reason (for example a value out * of range). * * @see #setValue(double,Unit) * @see #doubleValue() */ public void setValue(final double value) throws InvalidParameterValueException { final Double check = value; @SuppressWarnings("unchecked") // Checked by constructor. final ParameterDescriptor<T> descriptor = (ParameterDescriptor) this.descriptor; this.value = ensureValidValue(descriptor, check); } /** * Sets the parameter value as an integer. * * @param value The parameter value. * @throws InvalidParameterValueException if the integer type is inappropriate for this parameter, * or if the value is illegal for some other reason (for example a value out of range). * * @see #intValue */ public void setValue(final int value) throws InvalidParameterValueException { @SuppressWarnings("unchecked") // Checked by constructor. final ParameterDescriptor<T> descriptor = (ParameterDescriptor) this.descriptor; final Class<T> type = descriptor.getValueClass(); if (Double.class.equals(type) || Double.TYPE.equals(type)) { setValue((double) value); return; } final Integer check = value; this.value = ensureValidValue(descriptor, check); } /** * Sets the parameter value as a boolean. * * @param value The parameter value. * @throws InvalidParameterValueException if the boolean type is inappropriate for this parameter. * * @see #booleanValue */ public void setValue(final boolean value) throws InvalidParameterValueException { @SuppressWarnings("unchecked") // Checked by constructor. final ParameterDescriptor<T> descriptor = (ParameterDescriptor) this.descriptor; final Boolean check = Boolean.valueOf(value); this.value = ensureValidValue(descriptor, check); } /** * Set the parameter value as an object. The object type is typically a {@link Double}, * {@link Integer}, {@link Boolean}, {@link String}, {@link URI}, {@code double[]} * or {@code int[]}. * * @param value The parameter value. * @throws InvalidParameterValueException if the type of {@code value} is inappropriate * for this parameter, or if the value is illegal for some other reason (for example * the value is numeric and out of range). * * @see #getValue */ public void setValue(final Object value) throws InvalidParameterValueException { @SuppressWarnings("unchecked") // Checked by constructor. final ParameterDescriptor<T> descriptor = (ParameterDescriptor) this.descriptor; this.value = ensureValidValue(descriptor, value); } /** * Set the parameter value as an array of floating point and their associated unit. * * @param values The parameter values. * @param unit The unit for the specified value. * @throws InvalidParameterValueException if the floating point type is inappropriate for this * parameter, or if the value is illegal for some other reason (for example a value out * of range). */ public void setValue(double[] values, final Unit<?> unit) throws InvalidParameterValueException { ensureNonNull("unit", unit); @SuppressWarnings("unchecked") // Checked by constructor. final ParameterDescriptor<T> descriptor = (ParameterDescriptor) this.descriptor; final Unit<?> targetUnit = descriptor.getUnit(); if (targetUnit == null) { throw unitlessParameter(descriptor); } final int expectedID = getUnitMessageID(targetUnit); if (getUnitMessageID(unit) != expectedID) { throw new IllegalArgumentException(Errors.format(expectedID, unit)); } final double[] converted = values.clone(); final UnitConverter converter = unit.getConverterTo(targetUnit); for (int i=0; i<converted.length; i++) { converted[i] = converter.convert(converted[i]); } this.value = ensureValidValue(descriptor, converted); this.unit = unit; } /** * Compares the specified object with this parameter for equality. * * @param object The object to compare to {@code this}. * @return {@code true} if both objects are equal. */ @Override public boolean equals(final Object object) { if (object == this) { // Slight optimization return true; } if (super.equals(object)) { final Parameter that = (Parameter) object; return Utilities.equals(this.value, that.value) && Utilities.equals(this.unit, that.unit); } return false; } /** * Returns a hash value for this parameter. * * @return The hash code value. This value doesn't need to be the same * in past or future versions of this class. */ @Override public int hashCode() { int code = super.hashCode()*37; if (value != null) code += value.hashCode(); if (unit != null) code += 37*unit.hashCode(); return code ^ (int)serialVersionUID; } /** * Returns a clone of this parameter. */ @Override public Parameter clone() { return (Parameter) super.clone(); } }