org.geotoolkit.parameter.Parameter Maven / Gradle / Ivy
/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2004-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.parameter;
import java.io.File;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import javax.measure.unit.SI;
import javax.measure.unit.Unit;
import javax.measure.unit.NonSI;
import javax.measure.converter.UnitConverter;
import javax.measure.converter.ConversionException;
import org.opengis.util.CodeList;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.InvalidParameterTypeException;
import org.opengis.parameter.InvalidParameterValueException;
import org.geotoolkit.measure.Units;
import org.geotoolkit.util.Utilities;
import org.geotoolkit.resources.Errors;
import org.geotoolkit.internal.io.IOUtilities;
import static org.geotoolkit.util.ArgumentChecks.ensureNonNull;
/**
* A parameter value used by an operation method. Most CRS parameter values are numeric and can
* be obtained by the {@link #intValue()} or {@link #doubleValue()} methods. But other types of
* parameter values are possible and can be handled by the more generic {@link #getValue()} and
* {@link #setValue(Object)} methods. The type and constraints on parameter values are given
* by the {@linkplain #getDescriptor() descriptor}.
*
* Instances of {@code ParameterValue} are created by the {@link ParameterDescriptor#createValue()}
* method. The parameter type can be fetch with the following idiom:
*
* {@preformat java
* Class extends T> valueClass = parameter.getDescriptor().getValueClass();
* }
*
* {@section Implementation note for subclasses}
* Except for the constructors, the {@link #equals(Object)} and the {@link #hashCode()} methods,
* all read and write operations ultimately delegates to the following methods:
*
*
* - All getter methods will invoke {@link #getValue()} and - if needed - {@link #getUnit()},
* then performs their processing on the values returned by those methods.
* - All setter methods will first convert (if needed) and verify the argument validity,
* then pass the values to the {@link #setSafeValue(Object, Unit)} method.
*
*
* Consequently, the above-cited methods provide single points that subclasses can override
* for modifying the behavior of all getter and setter methods.
*
* @param The value type.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Jody Garnett (Refractions)
* @version 3.20
*
* @see DefaultParameterDescriptor
* @see ParameterGroup
*
* @since 2.0
* @module
*/
public class Parameter extends AbstractParameterValue {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -5837826787089486776L;
/**
* The value, or {@code null} if undefined. Except for the constructors, the
* {@link #equals(Object)} and the {@link #hashCode()} methods, this field is
* read only by {@link #getValue()} and written by {@link #setSafeValue(Object, Unit)}.
*/
private T value;
/**
* The unit of measure for the value, or {@code null} if it doesn't apply. Except for the
* constructors, the {@link #equals(Object)} and the {@link #hashCode()} methods, this field
* is read only by {@link #getUnit()} and written by {@link #setSafeValue(Object, Unit)}.
*/
private Unit> unit;
/**
* 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 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 descriptor, final T value)
throws InvalidParameterValueException
{
super(descriptor);
unit = descriptor.getUnit();
setValue(value);
}
/**
* Constructs a parameter from the specified name and value.
*
* {@section Proposed alternative}
* This convenience constructor creates a {@link DefaultParameterDescriptor} object. But
* if such descriptor 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 create(final String name, final int value) {
final ParameterDescriptor descriptor =
new DefaultParameterDescriptor(name, Integer.class, null, null);
final Parameter parameter = new Parameter(descriptor);
parameter.value = value;
return parameter;
}
/**
* Constructs a parameter from the specified name, value and unit.
*
* {@section Proposed alternative}
* This convenience constructor creates a {@link DefaultParameterDescriptor} object. But
* if such descriptor 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 create(final String name, final double value, Unit> unit) {
/*
* Normalizes the specified unit into one of "standard" units used in projections.
* This is for the descriptor only; the parameter will use exactly the given unit.
*/
if (unit != null) {
if (Units.isLinear(unit)) {
unit = SI.METRE;
} else if (Units.isTemporal(unit)) {
unit = NonSI.DAY;
} else if (Units.isAngular(unit)) {
unit = NonSI.DEGREE_ANGLE;
}
}
final ParameterDescriptor descriptor = DefaultParameterDescriptor.create(
name, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, unit);
final Parameter parameter = new Parameter(descriptor);
parameter.value = value;
parameter.unit = unit;
return parameter;
}
/**
* Constructs a parameter from the specified code list.
*
* {@section Proposed alternative}
* This convenience constructor creates a {@link DefaultParameterDescriptor} object. But
* if such descriptor is available, then the preferred way to get a {@code ParameterValue}
* is to invoke {@link ParameterDescriptor#createValue()}.
*
* @param 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 > Parameter create(final String name, final Class type, final T value) {
final ParameterDescriptor descriptor = new DefaultParameterDescriptor(name, null, type, null, true);
final Parameter parameter = new Parameter(descriptor);
parameter.value = value;
return parameter;
}
/**
* Formats an error message for illegal method call for the current value type.
*/
private String getClassTypeError() {
return Errors.format(Errors.Keys.ILLEGAL_OPERATION_FOR_VALUE_CLASS_$1,
((ParameterDescriptor>) descriptor).getValueClass());
}
/**
* 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".
*
* {@section Implementation note for subclasses}
* All getter methods which need unit information will invoke this {@code getUnit()} method.
* Subclasses can override this method if they need to compute the unit dynamically.
*
* @return The unit of measure, or {@code null} if none.
*
* @see #doubleValue()
* @see #doubleValueList()
* @see #getValue()
*/
@Override
public Unit> getUnit() {
return unit;
}
/**
* Returns the numeric value of the coordinate operation parameter in the specified unit
* of measure. This convenience method applies unit conversions 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 IllegalArgumentException if the specified unit is invalid for this parameter.
* @throws InvalidParameterTypeException if the value is not a numeric type.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #getUnit()
* @see #setValue(double,Unit)
* @see #doubleValueList(Unit)
*/
@Override
public double doubleValue(final Unit> unit) throws IllegalArgumentException, IllegalStateException {
final Unit> actual = getUnit();
if (actual == null) {
throw unitlessParameter(descriptor);
}
ensureNonNull("unit", unit);
final int expectedID = getUnitMessageID(actual);
if (getUnitMessageID(unit) != expectedID) {
throw new IllegalArgumentException(Errors.format(expectedID, unit));
}
try {
return actual.getConverterToAny(unit).convert(doubleValue());
} catch (ConversionException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.INCOMPATIBLE_UNIT_$1, unit), e);
}
}
/**
* Returns the numeric value represented by this operation parameter.
* The units of measurement are specified by {@link #getUnit()}.
*
* {@note The behavior of this method is slightly different than the equivalent method in
* the FloatParameter
class, since this method throws an exception instead than
* returning NaN
if no value has been explicitely set. This method behaves that
* way for consistency will other methods defined in this class, since all of them except
* getValue()
throw an exception in such case.}
*
* @return The numeric value represented by this parameter after conversion to type {@code double}.
* This method returns {@link Double#NaN} only if such "value" has been explicitely set.
* @throws InvalidParameterTypeException if the value is not a numeric type.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #getUnit()
* @see #setValue(double)
* @see #doubleValueList()
*/
@Override
public double doubleValue() throws IllegalStateException {
final T value = getValue();
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.geotoolkit.io.wkt.Formatter.
// If a default value existed, it should has been copied by the constructor or setter methods.
throw new IllegalStateException(Errors.format(Errors.Keys.NO_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.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #setValue(int)
* @see #intValueList()
*/
@Override
public int intValue() throws IllegalStateException {
final T value = getValue();
if (value instanceof Number) {
final int integer = ((Number) value).intValue();
if (integer == ((Number) value).doubleValue()) {
return integer;
}
}
final String name = getName(descriptor);
if (value == null) {
throw new IllegalStateException(Errors.format(Errors.Keys.NO_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.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #setValue(boolean)
*/
@Override
public boolean booleanValue() throws IllegalStateException {
final T value = getValue();
if (value instanceof Boolean) {
return ((Boolean) value).booleanValue();
}
final String name = getName(descriptor);
if (value == null) {
throw new IllegalStateException(Errors.format(Errors.Keys.NO_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.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #getValue()
* @see #setValue(Object)
*/
@Override
public String stringValue() throws IllegalStateException {
final T value = getValue();
if (value instanceof CharSequence) {
return value.toString();
}
final String name = getName(descriptor);
if (value == null) {
throw new IllegalStateException(Errors.format(Errors.Keys.NO_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 IllegalArgumentException if the specified unit is invalid for this parameter.
* @throws InvalidParameterTypeException if the value is not an array of {@code double}s.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #getUnit()
* @see #setValue(double[],Unit)
* @see #doubleValue(Unit)
*/
@Override
public double[] doubleValueList(final Unit> unit) throws IllegalArgumentException, IllegalStateException {
final Unit> actual = getUnit();
if (actual == null) {
throw unitlessParameter(descriptor);
}
ensureNonNull("unit", unit);
final int expectedID = getUnitMessageID(actual);
if (getUnitMessageID(unit) != expectedID) {
throw new IllegalArgumentException(Errors.format(expectedID, unit));
}
final UnitConverter converter;
try {
converter = actual.getConverterToAny(unit);
} catch (ConversionException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.INCOMPATIBLE_UNIT_$1, unit), e);
}
final double[] values = doubleValueList().clone();
for (int i=0; i unit) throws InvalidParameterValueException {
ensureNonNull("unit", unit);
@SuppressWarnings("unchecked") // Checked by constructor.
final ParameterDescriptor 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;
try {
converted = unit.getConverterToAny(targetUnit).convert(value);
} catch (ConversionException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.INCOMPATIBLE_UNIT_$1, unit), e);
}
ensureValidValue(descriptor, converted);
/*
* Really store the original value, not the converted one, because we store the given
* unit as well. Conversions will be applied on the fly by the getter method if needed.
*/
setSafeValue(descriptor.getValueClass().cast(value), unit);
}
/**
* Sets the parameter value as a floating point. The unit, if any, stay unchanged.
* The default implementation verifies the argument validity, then invokes
* {@link #setSafeValue(Object, Unit)}.
*
* @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()
*/
@Override
@SuppressWarnings("unchecked") // Safe because type was checked by the constructor.
public void setValue(final double value) throws InvalidParameterValueException {
setSafeValue(ensureValidValue((ParameterDescriptor) descriptor, Double.valueOf(value)), unit);
// Really 'unit', not 'getUnit()' since units are not expected to be involved in this method.
// We just want the current unit setting to be unchanged.
}
/**
* Sets the parameter value as an integer. The default implementation performs a
* choice based on the {@linkplain ParameterDescriptor#getValueClass() value class}:
*
*
* - If the value type is {@code Float.class} or {@code Double.class},
* then this method delegates to {@link #setValue(double)}.
* - Otherwise this method verifies the argument validity, then
* invokes {@link #setSafeValue(Object, Unit)}.
*
*
* @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()
*/
@Override
public void setValue(final int value) throws InvalidParameterValueException {
@SuppressWarnings("unchecked") // Checked by constructor.
final ParameterDescriptor descriptor = (ParameterDescriptor) this.descriptor;
final Class type = descriptor.getValueClass();
if (type == Double.class || type == Float.class) {
setValue((double) value);
} else {
setSafeValue(ensureValidValue(descriptor, Integer.valueOf(value)), unit);
// Really 'unit', not 'getUnit()' since we don't expect units in this context.
// We just want the current unit setting to be unchanged as a paranoiac safety.
}
}
/**
* Sets the parameter value as a boolean.
* The default implementation verifies the argument validity, then invokes
* {@link #setSafeValue(Object, Unit)}.
*
* @param value The parameter value.
* @throws InvalidParameterValueException if the boolean type is inappropriate for this parameter.
*
* @see #booleanValue()
*/
@Override
@SuppressWarnings("unchecked") // Safe because type was checked by the constructor.
public void setValue(final boolean value) throws InvalidParameterValueException {
setSafeValue(ensureValidValue(((ParameterDescriptor) descriptor), Boolean.valueOf(value)), unit);
// Really 'unit', not 'getUnit()' since we don't expect units in this context.
// We just want the current unit setting to be unchanged as a paranoiac safety.
}
/**
* Sets 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[]}.
*
* The default implementation verifies the argument validity, then invokes
* {@link #setSafeValue(Object, Unit)}.
*
* @param value The parameter value, or {@code null} to restore the default 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()
*/
@Override
@SuppressWarnings("unchecked") // Safe because type was checked by the constructor.
public void setValue(final Object value) throws InvalidParameterValueException {
setSafeValue(ensureValidValue((ParameterDescriptor) descriptor, value), unit);
// Really 'unit', not 'getUnit()' since we don't expect units in this context.
// We just want the current unit setting to be unchanged as a paranoiac safety.
}
/**
* Sets the parameter value as an array of floating point and their associated unit.
* The default implementation verifies the argument validity, then invokes
* {@link #setSafeValue(Object, 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).
*/
@Override
public void setValue(double[] values, final Unit> unit) throws InvalidParameterValueException {
ensureNonNull("unit", unit);
@SuppressWarnings("unchecked") // Checked by constructor.
final ParameterDescriptor 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;
try {
converter = unit.getConverterToAny(targetUnit);
} catch (ConversionException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.INCOMPATIBLE_UNIT_$1, unit), e);
}
for (int i=0; i unit) {
this.value = value;
this.unit = unit;
fireValueChanged();
}
/**
* 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 = 31 * super.hashCode();
if (value != null) code += value.hashCode();
if (unit != null) code += 31*unit.hashCode();
return code ^ (int)serialVersionUID;
}
/**
* Returns a clone of this parameter.
*/
@Override
public Parameter clone() {
return (Parameter) super.clone();
}
}