blob: 94f7287e827d461680e1c3001394c7087c757ca4 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sis.parameter;
import java.lang.reflect.Array;
import java.util.Objects;
import java.nio.file.Path;
import java.io.Serializable;
import java.io.File;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElements;
import javax.xml.bind.annotation.XmlRootElement;
import javax.measure.Unit;
import javax.measure.UnitConverter;
import javax.measure.IncommensurableException;
import org.opengis.metadata.citation.Citation;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.InvalidParameterTypeException;
import org.opengis.parameter.InvalidParameterValueException;
import org.apache.sis.io.wkt.FormattableObject;
import org.apache.sis.io.wkt.Formatter;
import org.apache.sis.io.wkt.Convention;
import org.apache.sis.io.wkt.ElementKind;
import org.apache.sis.internal.jaxb.gml.Measure;
import org.apache.sis.internal.jaxb.gml.MeasureList;
import org.apache.sis.internal.referencing.Resources;
import org.apache.sis.internal.referencing.WKTUtilities;
import org.apache.sis.internal.metadata.MetadataUtilities;
import org.apache.sis.internal.referencing.WKTKeywords;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.internal.system.Loggers;
import org.apache.sis.math.DecimalFunctions;
import org.apache.sis.util.Numbers;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.LenientComparable;
import org.apache.sis.util.ObjectConverters;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.util.logging.Logging;
import org.apache.sis.util.UnconvertibleObjectException;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
import static org.apache.sis.util.Utilities.deepEquals;
/**
* A single parameter value used by an operation method. {@code ParameterValue} instances are elements in
* a {@linkplain DefaultParameterValueGroup parameter value group}, in a way similar to {@code Map.Entry}
* instances in a {@code java.util.Map}.
*
* <p>In the context of coordinate operations, most 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.
* All {@code xxxValue()} methods in this class are convenience methods converting the value from {@code Object}
* to some commonly used types. Those types are specified in ISO 19111 as an union of attributes, listed below with
* the corresponding getter and setter methods:</p>
*
* <table class="sis">
* <caption>Mapping from ISO attributes to getters and setters</caption>
* <tr><th>ISO attribute</th> <th>Java type</th> <th>Getter method</th> <th>Setter method</th></tr>
* <tr><td></td> <td>{@link Object}</td> <td>{@link #getValue()}</td> <td>{@link #setValue(Object)}</td></tr>
* <tr><td>stringValue</td> <td>{@link String}</td> <td>{@link #stringValue()}</td> <td>{@link #setValue(Object)}</td></tr>
* <tr><td>value</td> <td>{@code double}</td> <td>{@link #doubleValue()}</td> <td>{@link #setValue(double)}</td></tr>
* <tr><td></td> <td>{@code double}</td> <td>{@link #doubleValue(Unit)}</td> <td>{@link #setValue(double, Unit)}</td></tr>
* <tr><td>valueList</td> <td>{@code double[]}</td> <td>{@link #doubleValueList()}</td> <td>{@link #setValue(Object)}</td></tr>
* <tr><td></td> <td>{@code double[]}</td> <td>{@link #doubleValueList(Unit)}</td> <td>{@link #setValue(double[], Unit)}</td></tr>
* <tr><td>integerValue</td> <td>{@code int}</td> <td>{@link #intValue()}</td> <td>{@link #setValue(int)}</td></tr>
* <tr><td>integerValueList</td> <td>{@code int[]}</td> <td>{@link #intValueList()}</td> <td>{@link #setValue(Object)}</td></tr>
* <tr><td>booleanValue</td> <td>{@code boolean}</td> <td>{@link #booleanValue()}</td> <td>{@link #setValue(boolean)}</td></tr>
* <tr><td>valueFile</td> <td>{@link URI}</td> <td>{@link #valueFile()}</td> <td>{@link #setValue(Object)}</td></tr>
* <tr><td>valueFileCitation</td> <td>{@link Citation}</td> <td>{@link #getValue()}</td> <td>{@link #setValue(Object)}</td></tr>
* </table>
*
* The type and constraints on parameter values are given by the {@linkplain #getDescriptor() descriptor},
* which is specified at construction time. The parameter type can be fetch with the following idiom:
*
* {@preformat java
* Class<T> valueClass = parameter.getDescriptor().getValueClass();
* }
*
* <div class="section">Instantiation</div>
* A {@linkplain DefaultParameterDescriptor parameter descriptor} must be defined before parameter value can be created.
* Descriptors are usually pre-defined by map projection or process providers. Given a descriptor, a parameter value can
* be created by a call to the {@link #DefaultParameterValue(ParameterDescriptor)} constructor or by a call to the
* {@link ParameterDescriptor#createValue()} method. The later is recommended since it allows descriptors to return
* specialized implementations.
*
* <div class="section">Implementation note for subclasses</div>
* All read and write operations (except constructors, {@link #equals(Object)} and {@link #hashCode()})
* ultimately delegates to the following methods:
*
* <ul>
* <li>All getter methods will invoke {@link #getValue()} and {@link #getUnit()} (if needed),
* then perform their processing on the values returned by those methods.</li>
* <li>All setter methods delegate to the {@link #setValue(Object, Unit)} method.</li>
* </ul>
*
* Consequently, the above-cited methods provide single points that subclasses can override
* for modifying the behavior of all getter and setter methods.
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @version 1.0
*
* @param <T> the type of the value stored in this parameter.
*
* @see DefaultParameterDescriptor
* @see DefaultParameterValueGroup
*
* @since 0.4
* @module
*/
@XmlType(name = "ParameterValueType", propOrder = {
"xmlValue",
"descriptor"
})
@XmlRootElement(name = "ParameterValue")
public class DefaultParameterValue<T> extends FormattableObject implements ParameterValue<T>,
LenientComparable, Serializable, Cloneable
{
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -5837826787089486776L;
/**
* The definition of this parameter.
*
* <p><b>Consider this field as final!</b>
* This field is modified only at unmarshalling time by {@link #setDescriptor(ParameterDescriptor)}</p>
*
* @see #getDescriptor()
*/
private ParameterDescriptor<T> descriptor;
/**
* The value, or {@code null} if undefined.
* Except for the constructors, the {@link #equals(Object)} and the {@link #hashCode()} methods,
* this field should be read only by {@link #getValue()} and written only by {@link #setValue(Object, Unit)}.
*
* @since 0.7
*/
protected T value;
/**
* The unit of measure for the value, or {@code null} if it does not apply.
* Except for the constructors, the {@link #equals(Object)} and the {@link #hashCode()} methods,
* this field should be read only by {@link #getUnit()} and written only by {@link #setValue(Object, Unit)}.
*
* @since 0.7
*/
protected Unit<?> unit;
/**
* Creates 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 DefaultParameterValue(final ParameterDescriptor<T> descriptor) {
ensureNonNull("descriptor", descriptor);
this.descriptor = descriptor;
this.value = descriptor.getDefaultValue();
this.unit = descriptor.getUnit();
}
/**
* Creates a new instance initialized with the values from the specified parameter object.
* This is a <em>shallow</em> copy constructor, since the value contained in the given
* object is not cloned.
*
* @param parameter the parameter to copy values from.
*
* @see #clone()
* @see #unmodifiable(ParameterValue)
*/
public DefaultParameterValue(final ParameterValue<T> parameter) {
ensureNonNull("parameter", parameter);
descriptor = parameter.getDescriptor();
value = parameter.getValue();
unit = parameter.getUnit();
}
/**
* Returns the definition of this parameter.
*
* @return the definition of this parameter.
*/
@Override
@XmlElement(name = "operationParameter", required = true)
public ParameterDescriptor<T> getDescriptor() {
return descriptor;
}
/**
* Returns the unit of measure of the 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" does not mean "dimensionless".
*
* <div class="section">Implementation note for subclasses</div>
* 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 parameter value as an object.
* If no value has been set, then this method returns the
* {@linkplain DefaultParameterDescriptor#getDefaultValue() default value} (which may be null).
*
* <div class="section">Implementation note for subclasses</div>
* All getter methods will invoke this {@code getValue()} method.
* Subclasses can override this method if they need to compute the value dynamically.
*
* @return the parameter value as an object, or {@code null} if no value has been set
* and there is no default value.
*
* @see #setValue(Object)
* @see Parameters#getValue(ParameterDescriptor)
*/
@Override
public T getValue() {
return value;
}
/**
* Returns the boolean value of this parameter.
* A boolean value does not have an associated unit of measure.
*
* <p>The default implementation invokes {@link #getValue()} and casts the result if possible,
* or throws an exception otherwise.</p>
*
* @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)
* @see Parameters#booleanValue(ParameterDescriptor)
*/
@Override
public boolean booleanValue() throws IllegalStateException {
final T value = getValue();
if (value instanceof Boolean) {
return (Boolean) value;
}
throw missingOrIncompatibleValue(value);
}
/**
* Returns the integer value of this parameter, usually used for a count.
* An integer value does not have an associated unit of measure.
*
* <p>The default implementation invokes {@link #getValue()} and casts the result if possible,
* or throws an exception otherwise.</p>
*
* @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()
* @see Parameters#intValue(ParameterDescriptor)
*/
@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;
}
}
throw missingOrIncompatibleValue(value);
}
/**
* Returns an ordered sequence of two or more integer values of this parameter, usually used for counts.
*
* <p>The default implementation invokes {@link #getValue()} and casts the result if possible,
* or throws an exception otherwise. If the value can be casted, then the array is cloned before
* to be returned.</p>
*
* @return a copy of the sequence of values represented by this parameter.
* @throws InvalidParameterTypeException if the value is not an array of {@code int}s.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #setValue(Object)
* @see #intValue()
* @see Parameters#intValueList(ParameterDescriptor)
*/
@Override
public int[] intValueList() throws IllegalStateException {
final T value = getValue();
if (value instanceof int[]) {
return ((int[]) value).clone();
}
throw missingOrIncompatibleValue(value);
}
/**
* Returns the numeric value of this parameter.
* The units of measurement are specified by {@link #getUnit()}.
*
* <p>The default implementation invokes {@link #getValue()} and casts the result if possible,
* or throws an exception otherwise.</p>
*
* @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 explicitly 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()
* @see Parameters#doubleValue(ParameterDescriptor)
*/
@Override
public double doubleValue() throws IllegalStateException {
final T value = getValue();
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
throw missingOrIncompatibleValue(value);
}
/**
* Returns an ordered sequence of two or more numeric values of this parameter,
* where each value has the same associated unit of measure.
*
* <p>The default implementation invokes {@link #getValue()} and casts the result if possible,
* or throws an exception otherwise. If the value can be casted, then the array is cloned before
* to be returned.</p>
*
* @return a copy of the sequence of values represented by 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(Object)
* @see #doubleValue()
*/
@Override
public double[] doubleValueList() throws IllegalStateException {
final T value = getValue();
if (value instanceof double[]) {
return ((double[]) value).clone();
}
throw missingOrIncompatibleValue(value);
}
/**
* Returns the converter to be used by {@link #doubleValue(Unit)} and {@link #doubleValueList(Unit)}.
*/
private UnitConverter getConverterTo(final Unit<?> unit) {
final Unit<?> source = getUnit();
if (source == null) {
throw new IllegalStateException(Resources.format(Resources.Keys.UnitlessParameter_1, Verifier.getDisplayName(descriptor)));
}
ensureNonNull("unit", unit);
final short expectedID = Verifier.getUnitMessageID(source);
if (Verifier.getUnitMessageID(unit) != expectedID) {
throw new IllegalArgumentException(Errors.format(expectedID, unit));
}
try {
return source.getConverterToAny(unit);
} catch (IncommensurableException e) {
throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnits_2, source, unit), e);
}
}
/**
* Returns the numeric value of this parameter in the given unit of measure.
* This convenience method applies unit conversions on the fly as needed.
*
* <p>The default implementation invokes {@link #doubleValue()} and {@link #getUnit()},
* then converts the values to the given unit of measurement.</p>
*
* @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)
* @see Parameters#doubleValue(ParameterDescriptor)
*/
@Override
public double doubleValue(final Unit<?> unit) throws IllegalArgumentException, IllegalStateException {
final double value = doubleValue(); // Invoke first in case it throws an exception.
return getConverterTo(unit).convert(value);
}
/**
* Returns an ordered sequence of numeric values in the specified unit of measure.
* This convenience method applies unit conversions on the fly as needed.
*
* <p>The default implementation invokes {@link #doubleValueList()} and {@link #getUnit()},
* then converts the values to the given unit of measurement.</p>
*
* @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)
* @see Parameters#doubleValueList(ParameterDescriptor)
*/
@Override
public double[] doubleValueList(final Unit<?> unit) throws IllegalArgumentException, IllegalStateException {
final UnitConverter converter = getConverterTo(unit);
final double[] values = doubleValueList();
for (int i=0; i<values.length; i++) {
values[i] = converter.convert(values[i]);
}
return values;
}
/**
* Returns the string value of this 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)
* @see Parameters#stringValue(ParameterDescriptor)
*/
@Override
public String stringValue() throws IllegalStateException {
final T value = getValue();
if (value instanceof CharSequence) {
return value.toString();
}
throw missingOrIncompatibleValue(value);
}
/**
* Returns a reference to a file or a part of a file containing one or more parameter values.
* The default implementation can convert the following value types:
* {@link URI}, {@link URL}, {@link Path}, {@link File}.
*
* @return the reference to a file containing parameter values.
* @throws InvalidParameterTypeException if the value is not a reference to a file or a URI.
* @throws IllegalStateException if the value is not defined and there is no default value.
*
* @see #getValue()
* @see #setValue(Object)
*/
@Override
public URI valueFile() throws IllegalStateException {
final T value = getValue();
if (value instanceof URI) return (URI) value;
if (value instanceof File) return ((File) value).toURI();
if (value instanceof Path) return ((Path) value).toUri();
Exception cause = null;
if (value instanceof URL) try {
return ((URL) value).toURI();
} catch (URISyntaxException exception) {
cause = exception;
}
final String name = Verifier.getDisplayName(descriptor);
if (value != null) {
throw new InvalidParameterTypeException(getClassTypeError(), name);
}
throw new IllegalStateException(Resources.format(Resources.Keys.MissingValueForParameter_1, name), cause);
}
/**
* Returns {@code true} if the given value is an instance of one of the types documented in {@link #valueFile()}.
*/
private static boolean isFile(final Object value) {
return (value instanceof URI || value instanceof URL || value instanceof File || value instanceof Path);
}
/**
* Same as {@link #isFile(Object)}, but accepts also a {@link String} if the type specified
* in the parameter descriptor is one of the types documented in {@link #valueFile()}.
*/
private boolean isOrNeedFile(final Object newValue) {
if (newValue instanceof String) {
final Class<?> type = descriptor.getValueClass();
return (type == URI.class) || (type == URL.class)
|| Path.class.isAssignableFrom(type)
|| File.class.isAssignableFrom(type);
}
return isFile(newValue);
}
/**
* Returns the exception to throw when an incompatible method is invoked for the value type.
*/
private IllegalStateException missingOrIncompatibleValue(final Object newValue) {
final String name = Verifier.getDisplayName(descriptor);
if (newValue != null) {
return new InvalidParameterTypeException(getClassTypeError(), name);
}
return new IllegalStateException(Resources.format(Resources.Keys.MissingValueForParameter_1, name));
}
/**
* Formats an error message for illegal method call for the current value type.
*/
private String getClassTypeError() {
return Resources.format(Resources.Keys.IllegalOperationForValueClass_1,
(descriptor != null) ? ((ParameterDescriptor<?>) descriptor).getValueClass() : "?");
}
/**
* Converts the given number to the expected type, with a special case for conversion from float to double type.
* Widening conversions are aimed to be exact in base 10 instead than base 2. If {@code expectedClass} is not a
* {@link Number} subtype, then this method does nothing. If the cast would result in information lost, than
* this method returns the given value unchanged for allowing a more accurate error message to happen later.
*
* @param value the value to cast (can be {@code null}).
* @param expectedClass the desired class as a wrapper class (not a primitive type).
* @return value converted to the desired class, or {@code value} if no cast is needed or can be done.
*/
@SuppressWarnings("unchecked")
private static Number cast(final Number value, final Class<?> expectedClass) {
if (expectedClass == Double.class && value instanceof Float) {
return DecimalFunctions.floatToDouble(value.floatValue());
} else if (Number.class.isAssignableFrom(expectedClass)) {
final Number n = Numbers.cast(value, (Class<? extends Number>) expectedClass);
if (Numerics.equals(n.doubleValue(), value.doubleValue())) {
return n;
}
}
return value;
}
/**
* Sets the parameter value as an object. The object type is typically (but not limited to) {@link Double},
* {@code double[]}, {@link Integer}, {@code int[]}, {@link Boolean}, {@link String} or {@link URI}.
* If the given value is {@code null}, then this parameter is set to the
* {@linkplain DefaultParameterDescriptor#getDefaultValue() default value}.
* If the given value is not an instance of the expected type, then this method may perform automatically
* a type conversion (for example from {@link Float} to {@link Double} or from {@link Path} to {@link URI})
* if such conversion can be done without information lost.
*
* @param newValue the parameter value, or {@code null} to restore the default.
* @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
public void setValue(Object newValue) throws InvalidParameterValueException {
/*
* Try to convert the value only for a limited amount of types. In particular we want to allow conversions
* between java.io.File and java.nio.file.Path for easier transition between JDK6 and JDK7. We do not want
* to allow too many conversions for reducing the risk of unexpected behavior. If we fail to convert, try
* to set the value anyway since the user may have redefined the `setValue(Object, Unit)` method.
*/
if (newValue != null) {
final Class<?> expectedClass = descriptor.getValueClass();
if (!expectedClass.isInstance(newValue)) {
if (newValue instanceof Number) {
newValue = cast((Number) newValue, expectedClass);
} else if (isOrNeedFile(newValue)) try {
newValue = ObjectConverters.convert(newValue, expectedClass);
} catch (UnconvertibleObjectException e) {
/*
* Level.FINE (not WARNING) because this log duplicates the exception
* that `setValue(Object, Unit)` may throw (with a better message).
*/
Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
DefaultParameterValue.class, "setValue", e);
} else {
/*
* If the given value is an array, verify if array elements need to be converted
* for example from `float` to `double`. This is a "all or nothing" operation:
* if at least one element can not be converted, then the whole array is unchanged.
*/
Class<?> componentType = expectedClass.getComponentType();
convert: if (componentType != null) {
final Object array = newValue.getClass().isArray() ? newValue : new Object[] {newValue};
final int length = Array.getLength(array);
if (length > 0) {
final Object copy = Array.newInstance(componentType, length);
componentType = Numbers.primitiveToWrapper(componentType);
for (int i=0; i<length; i++) {
Object element = Array.get(array, i);
if (element != null) {
if (!(element instanceof Number)) break convert;
element = cast((Number) element, componentType);
if (!(componentType.isInstance(element))) break convert;
Array.set(copy, i, element);
}
}
newValue = copy;
}
}
}
}
}
/*
* Code below uses `unit` instead than `getUnit()` despite class Javadoc claim because units are not expected
* to be involved in this method. We access this field only as a matter of principle, for making sure that no
* property other than the value is altered by this method call.
*/
setValue(newValue, unit);
}
/**
* Sets the parameter value as a boolean.
*
* @param newValue the parameter value.
* @throws InvalidParameterValueException if the boolean type is inappropriate for this parameter.
*
* @see #booleanValue()
*/
@Override
public void setValue(final boolean newValue) throws InvalidParameterValueException {
setValue(newValue, unit);
/*
* Above code used `unit` instead than `getUnit()` despite class Javadoc claim because units are not expected
* to be involved in this method. We access this field only as a matter of principle, for making sure that no
* property other than the value is altered by this method call.
*/
}
/**
* Sets the parameter value as an integer. This method automatically wraps the given integer
* in an object of the type specified by the {@linkplain #getDescriptor() descriptor} if that
* conversion can be done without information lost.
*
* @param newValue 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 newValue) throws InvalidParameterValueException {
Number n = newValue;
Number c = cast(n, descriptor.getValueClass());
if (c.intValue() == newValue) n = c;
setValue(n, unit);
/*
* Above code used `unit` instead than `getUnit()` despite class Javadoc claim because units are not expected
* to be involved in this method. We access this field only as a matter of principle, for making sure that no
* property other than the value is altered by this method call.
*/
}
/**
* Wraps the given value in a type compatible with the expected value class, if possible.
* If the value can not be wrapped, then this method fallbacks on the {@link Double} class
* consistently with this method being invoked only by {@code setValue(double, …)} methods.
*
* @throws IllegalArgumentException if the given value can not be converted to the given type.
*/
@SuppressWarnings("unchecked")
private static Number wrap(final double value, final Class<?> valueClass) throws IllegalArgumentException {
if (Number.class.isAssignableFrom(valueClass)) {
return Numbers.wrap(value, (Class<? extends Number>) valueClass);
} else {
return Numerics.valueOf(value);
}
}
/**
* Sets the parameter value as a floating point. The unit, if any, stay unchanged. This method automatically
* wraps the given number in an object of the type specified by the {@linkplain #getDescriptor() descriptor}.
*
* @param newValue 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
public void setValue(final double newValue) throws InvalidParameterValueException {
final Number n;
try {
n = wrap(newValue, descriptor.getValueClass());
} catch (IllegalArgumentException e) {
throw (InvalidParameterValueException) new InvalidParameterValueException(
e.getLocalizedMessage(), Verifier.getDisplayName(descriptor), newValue).initCause(e);
}
setValue(n, unit);
/*
* Above code used `unit` instead than `getUnit()` despite class Javadoc claim because units are not expected
* to be involved in this method. We access this field only as a matter of principle, for making sure that no
* property other than the value is altered by this method call.
*/
}
/**
* Sets the parameter value as a floating point and its associated unit. This method automatically wraps
* the given number in an object of the type specified by the {@linkplain #getDescriptor() descriptor}.
*
* @param newValue 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)
*/
@Override
public void setValue(final double newValue, final Unit<?> unit) throws InvalidParameterValueException {
final Number n;
try {
n = wrap(newValue, descriptor.getValueClass());
} catch (IllegalArgumentException e) {
throw (InvalidParameterValueException) new InvalidParameterValueException(
e.getLocalizedMessage(), Verifier.getDisplayName(descriptor), newValue).initCause(e);
}
setValue(n, unit);
}
/**
* Sets the parameter value as an array of floating point and their associated unit.
*
* @param newValues the parameter values.
* @param unit the unit for the specified value.
* @throws InvalidParameterValueException if the floating point array 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(final double[] newValues, final Unit<?> unit) throws InvalidParameterValueException {
setValue((Object) newValues, unit);
}
/**
* Sets the parameter value and its associated unit.
* If the given value is {@code null}, then this parameter is set to the
* {@linkplain DefaultParameterDescriptor#getDefaultValue() default value}.
* Otherwise the given value shall be an instance of the class expected by the {@linkplain #getDescriptor() descriptor}.
*
* <ul>
* <li>This method does not perform any type conversion. Type conversion, if desired, should be
* applied by the public {@code setValue(…)} methods before to invoke this protected method.</li>
* <li>This method does not clone the given value. In particular, references to {@code int[]} and
* {@code double[]} arrays are stored <cite>as-is</cite>.</li>
* </ul>
*
* <div class="section">Implementation note for subclasses</div>
* This method is invoked by all setter methods in this class, thus providing a single point that
* subclasses can override if they want to perform more processing on the value before its storage,
* or to be notified about value changes.
*
* @param newValue the parameter value, or {@code null} to restore the default.
* @param unit the unit associated to the new parameter value, or {@code null}.
* @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 #validate(Object)
*/
@SuppressWarnings("unchecked")
protected void setValue(final Object newValue, final Unit<?> unit) throws InvalidParameterValueException {
final T convertedValue = Verifier.ensureValidValue(descriptor, newValue, unit);
if (newValue != null) {
validate(convertedValue);
this.value = (T) newValue; // Type has been verified by Verifier.ensureValidValue(…).
} else {
this.value = descriptor.getDefaultValue();
}
this.unit = unit; // Assign only on success.
}
/**
* Invoked by {@link #setValue(Object, Unit)} after the basic verifications have been done and before
* the value is stored. Subclasses can override this method for performing additional verifications.
*
* <div class="section">Unit of measurement</div>
* If the user specified a unit of measurement, then the value given to this method has been converted
* to the unit specified by the {@linkplain #getDescriptor() descriptor}, for easier comparisons against
* standardized values. This converted value may be different than the value to be stored in this
* {@code ParameterValue}, since the later value will be stored in the unit specified by the user.
*
* <div class="section">Standard validations</div>
* The checks for {@linkplain DefaultParameterDescriptor#getValueClass() value class},
* for {@linkplain DefaultParameterDescriptor#getValueDomain() value domain} and for
* {@linkplain DefaultParameterDescriptor#getValidValues() valid values} are performed
* before this method is invoked. The default implementation of this method does nothing.
*
* @param newValue the value converted to the unit of measurement specified by the descriptor.
* @throws InvalidParameterValueException if the given value is invalid for implementation-specific reasons.
*/
protected void validate(final T newValue) throws InvalidParameterValueException {
}
/**
* Compares the specified object with this parameter for equality.
* The strictness level is controlled by the second argument.
*
* @param object the object to compare to {@code this}.
* @param mode the strictness level of the comparison.
* @return {@code true} if both objects are equal according the given comparison mode.
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true; // Slight optimization
}
if (object != null) {
if (mode == ComparisonMode.STRICT) {
if (getClass() == object.getClass()) {
final DefaultParameterValue<?> that = (DefaultParameterValue<?>) object;
return Objects.equals(descriptor, that.descriptor) &&
Objects.equals(value, that.value) &&
Objects.equals(unit, that.unit);
}
} else if (object instanceof ParameterValue<?>) {
final ParameterValue<?> that = (ParameterValue<?>) object;
return deepEquals(getDescriptor(), that.getDescriptor(), mode) &&
deepEquals(getValue(), that.getValue(), mode) &&
deepEquals(getUnit(), that.getUnit(), mode);
}
}
return false;
}
/**
* Compares the specified object with this parameter for equality.
* This method is implemented as below:
*
* {@preformat java
* return equals(other, ComparisonMode.STRICT);
* }
*
* Subclasses shall override {@link #equals(Object, ComparisonMode)} instead than this method.
*
* @param object the object to compare to {@code this}.
* @return {@code true} if both objects are equal.
*/
@Override
public final boolean equals(final Object object) {
return equals(object, ComparisonMode.STRICT);
}
/**
* Returns a hash value for this parameter.
* This value does not need to be the same in past or future versions of this class.
*
* @return the hash code value.
*/
@Override
public int hashCode() {
int code = 37 * descriptor.hashCode();
if (value != null) code += value.hashCode();
if (unit != null) code += 31*unit.hashCode();
return code;
}
/**
* Returns a clone of this parameter value.
*
* @see #DefaultParameterValue(ParameterValue)
* @see #unmodifiable(ParameterValue)
*/
@Override
@SuppressWarnings("unchecked")
public DefaultParameterValue<T> clone() {
try {
return (DefaultParameterValue<T>) super.clone();
} catch (CloneNotSupportedException exception) {
throw new AssertionError(exception); // Should not happen, since we are cloneable
}
}
/**
* Returns an unmodifiable implementation of the given parameter value.
* This method shall be used only with:
*
* <ul>
* <li>immutable {@linkplain #getDescriptor() descriptor},</li>
* <li>immutable or null {@linkplain #getUnit() unit}, and</li>
* <li>immutable or {@linkplain Cloneable cloneable} parameter {@linkplain #getValue() value}.</li>
* </ul>
*
* If the parameter value implements the {@link Cloneable} interface and has a public {@code clone()} method,
* then that value will be cloned every time the {@link #getValue()} method is invoked.
* The value is not cloned by this method however; it is caller's responsibility to not modify the value of
* the given {@code parameter} instance after this method call.
*
* <div class="section">Instances sharing</div>
* If this method is invoked more than once with equal {@linkplain #getDescriptor() descriptor},
* {@linkplain #getValue() value} and {@linkplain #getUnit() unit}, then this method will return
* the same {@code DefaultParameterValue} instance on a <cite>best effort</cite> basis.
*
* <div class="note"><b>Rational:</b>
* the same parameter value is often used in many different coordinate operations. For example all <cite>Universal
* Transverse Mercator</cite> (UTM) projections use the same scale factor (0.9996) and false easting (500000 metres).
* </div>
*
* @param <T> the type of the value stored in the given parameter.
* @param parameter the parameter to make unmodifiable, or {@code null}.
* @return a unmodifiable implementation of the given parameter, or {@code null} if the given parameter was null.
*
* @see DefaultParameterValueGroup#unmodifiable(ParameterValueGroup)
*
* @since 0.6
*/
public static <T> DefaultParameterValue<T> unmodifiable(final ParameterValue<T> parameter) {
return UnmodifiableParameterValue.create(parameter);
}
/**
* Formats this parameter as a <cite>Well Known Text</cite> {@code Parameter[…]} element.
* Example:
*
* {@preformat wkt
* Parameter["False easting", 0.0, LengthUnit["metre", 1]]
* }
*
* <div class="section">Unit of measurement</div>
* The units of measurement were never specified in WKT 1 format, and are optional in WKT 2 format.
* If the units are not specified, then they are inferred from the context.
* Typically, parameter values that are lengths are given in the unit for the projected CRS axes
* while parameter values that are angles are given in the unit for the base geographic CRS.
*
* <div class="note"><b>Example:</b>
* The snippet below show WKT representations of the map projection parameters of a projected CRS
* (most other elements are omitted). The map projection uses a <cite>"Latitude of natural origin"</cite>
* parameters which is set to 52 <strong>grads</strong>, as defined in the {@code UNIT[…]} element of the
* enclosing CRS. A similar rule applies to <cite>“False easting”</cite> and <cite>“False northing”</cite>
* parameters, which are in kilometres in this example.
*
* <p><b>WKT 1:</b></p>
* {@preformat wkt
* PROJCS[…,
* GEOGCS[…,
* UNIT[“grad”, 0.015707963267948967]], // Unit for all angles
* PROJECTION[“Lambert_Conformal_Conic_1SP”]
* PARAMETER[“latitude_of_origin”, 52.0], // In grads
* PARAMETER[“scale_factor”, 0.99987742],
* PARAMETER[“false_easting”, 600.0], // In kilometres
* PARAMETER[“false_northing”, 2200.0], // In kilometres
* UNIT[“kilometre”, 1000]] // Unit for all lengths
* }
*
* <p><b>WKT 2:</b></p>
* {@preformat wkt
* ProjectedCRS[…
* BaseGeodCRS[…
* AngleUnit[“grad”, 0.015707963267948967]],
* Conversion[“Lambert zone II”,
* Method[“Lambert Conic Conformal (1SP)”],
* Parameter[“Latitude of natural origin”, 52.0],
* Parameter[“Scale factor at natural origin”, 0.99987742],
* Parameter[“False easting”, 600.0],
* Parameter[“False northing”, 2200.0]],
* CS[“Cartesian”, 2],
* LengthUnit[“kilometre”, 1000]]
* }
* </div>
*
* @param formatter the formatter where to format the inner content of this WKT element.
* @return {@code "Parameter"} or {@code "ParameterFile"}.
*
* @see <a href="http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#119">WKT 2 specification §17.2.4</a>
*/
@Override
protected String formatTo(final Formatter formatter) {
final ParameterDescriptor<T> descriptor = getDescriptor(); // Gives to users a chance to override this property.
WKTUtilities.appendName(descriptor, formatter, ElementKind.PARAMETER);
final Convention convention = formatter.getConvention();
final boolean isWKT1 = convention.majorVersion() == 1;
Unit<?> unit = getUnit(); // Gives to users a chance to override this property.
if (unit == null) {
final T value = getValue(); // Gives to users a chance to override this property.
if (!isWKT1 && isFile(value)) {
formatter.append(value.toString(), null);
return WKTKeywords.ParameterFile;
} else {
formatter.appendAny(value);
}
} else {
/*
* In the WKT 1 specification, the unit of measurement is given by the context.
* If this parameter value does not use the same unit, we will need to convert it.
* Otherwise we can write the value as-is.
*
* Note that we take the descriptor unit as a starting point instead than this parameter unit
* in order to give precedence to the descriptor units in Convention.WKT1_COMMON_UNITS mode.
*/
Unit<?> contextualUnit;
if (descriptor == null || (contextualUnit = descriptor.getUnit()) == null) {
// Should be very rare (probably a buggy descriptor), but we try to be safe.
contextualUnit = unit;
}
contextualUnit = formatter.toContextualUnit(contextualUnit);
boolean ignoreUnits;
if (isWKT1) {
unit = contextualUnit;
ignoreUnits = true;
} else {
if (convention != Convention.INTERNAL) {
unit = WKTUtilities.toFormattable(unit);
}
ignoreUnits = unit.equals(contextualUnit);
}
double value;
try {
value = doubleValue(unit);
} catch (IllegalStateException exception) {
/*
* May happen if a parameter is mandatory (e.g. "semi-major")
* but no value has been set for this parameter.
*/
if (descriptor != null) {
formatter.setInvalidWKT(descriptor, exception);
} else {
/*
* Null descriptor should be illegal but may happen after unmarshalling of invalid GML.
* We make this WKT formatting robust since it is used by 'toString()' implementation.
*/
formatter.setInvalidWKT(DefaultParameterValue.class, exception);
}
value = Double.NaN;
}
formatter.append(value);
/*
* In the WKT 2 specification, the unit and the identifier are optional but recommended.
* We follow that recommendation in strict WKT2 mode, but omit them in non-strict modes.
* The only exception is when the parameter unit is not the same than the contextual unit,
* in which case we have no choice: we must format that unit, unless the numerical value
* is identical in both units (typically the 0 value).
*/
if (!ignoreUnits && !Double.isNaN(value)) {
// Test equivalent to unit.equals(contextualUnit) but more aggressive.
ignoreUnits = Numerics.equals(value, doubleValue(contextualUnit));
}
if (ignoreUnits && convention != Convention.INTERNAL) {
// One last check about if we are really allowed to ignore units.
ignoreUnits = convention.isSimplified() && hasContextualUnit(formatter);
}
if (!ignoreUnits) {
if (!isWKT1) {
formatter.append(unit);
} else if (!ignoreUnits) {
if (descriptor != null) {
formatter.setInvalidWKT(descriptor, null);
} else {
/*
* Null descriptor should be illegal but may happen after unmarshalling of invalid GML.
* We make this WKT formatting robust since it is used by 'toString()' implementation.
*/
formatter.setInvalidWKT(DefaultParameterValue.class, null);
}
}
}
}
// ID will be added by the Formatter itself.
return WKTKeywords.Parameter;
}
/**
* Returns {@code true} if the given formatter has contextual units, in which case the WKT format can omit
* the unit of measurement. The contextual units may be defined either in the direct parent or in the parent
* of the parent, depending if we are formatting WKT1 or WKT2 respectively. This is because WKT2 wraps the
* parameters in an additional {@code CONVERSION[…]} element which is not present in WKT1.
*
* <p>Taking the example documented in {@link #formatTo(Formatter)}:</p>
* <ul>
* <li>in WKT 1, {@code PROJCS[…]} is the immediate parent of {@code PARAMETER[…]}, but</li>
* <li>in WKT 2, {@code ProjectedCRS[…]} is the parent of {@code Conversion[…]},
* which is the parent of {@code Parameter[…]}.</li>
* </ul>
*/
private static boolean hasContextualUnit(final Formatter formatter) {
return formatter.hasContextualUnit(1) || // In WKT1
formatter.hasContextualUnit(2); // In WKT2
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// XML support with JAXB ////////
//////// ////////
//////// The following methods are invoked by JAXB using reflection (even if ////////
//////// they are private) or are helpers for other methods invoked by JAXB. ////////
//////// Those methods can be safely removed if Geographic Markup Language ////////
//////// (GML) support is not needed. ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Default constructor for JAXB only. The descriptor is initialized to {@code null},
* but will be assigned a value after XML unmarshalling.
*/
private DefaultParameterValue() {
}
/**
* Invoked by JAXB at unmarshalling time.
* May also be invoked by {@link DefaultParameterValueGroup} if the descriptor as been completed
* with additional information provided in the {@code <gml:group>} element of a descriptor group.
*
* @see #getDescriptor()
*/
final void setDescriptor(final ParameterDescriptor<T> descriptor) {
this.descriptor = descriptor;
assert (value == null) || descriptor.getValueClass().isInstance(value) : this;
}
/**
* Invoked by JAXB for obtaining the object to marshal.
* The property name depends on its type after conversion by this method.
*/
@XmlElements({
@XmlElement(name = "value", type = Measure.class),
@XmlElement(name = "integerValue", type = Integer.class),
@XmlElement(name = "booleanValue", type = Boolean.class),
@XmlElement(name = "stringValue", type = String .class),
@XmlElement(name = "valueFile", type = URI .class),
@XmlElement(name = "integerValueList", type = IntegerList.class),
@XmlElement(name = "valueList", type = MeasureList.class)
})
private Object getXmlValue() {
final Object value = getValue(); // Give to user a chance to override.
if (value != null) {
if (value instanceof Number) {
final Number n = (Number) value;
if (Numbers.isInteger(n.getClass())) {
final int xmlValue = n.intValue();
if (xmlValue >= 0 && xmlValue == n.doubleValue()) {
return xmlValue;
}
}
return new Measure(((Number) value).doubleValue(), getUnit());
}
if (value instanceof CharSequence) {
return value.toString();
}
if (isFile(value)) {
return ObjectConverters.convert(value, URI.class);
}
final Class<?> type = Numbers.primitiveToWrapper(value.getClass().getComponentType());
if (type != null && Number.class.isAssignableFrom(type)) {
if (Numbers.isInteger(type)) {
return new IntegerList(value);
}
return new MeasureList(value, type, getUnit());
}
}
return value;
}
/**
* Invoked by JAXB at unmarshalling time.
*/
@SuppressWarnings("unchecked")
private void setXmlValue(Object xmlValue) {
if (value == null && unit == null) {
if (xmlValue instanceof Measure) {
final Measure measure = (Measure) xmlValue;
xmlValue = measure.value;
unit = measure.unit;
} else if (xmlValue instanceof MeasureList) {
final MeasureList measure = (MeasureList) xmlValue;
xmlValue = measure.toArray();
unit = measure.unit;
} else if (xmlValue instanceof IntegerList) {
xmlValue = ((IntegerList) xmlValue).toArray();
}
if (descriptor != null) {
/*
* Should never happen with default SIS implementation, but may happen if the user created
* a sub-type of DefaultParameterValue with a default constructor providing the descriptor.
*/
value = ObjectConverters.convert(xmlValue, descriptor.getValueClass());
} else {
/*
* Temporarily accept the value without checking its type. This is required because the
* descriptor is normally defined after the value in a GML document. The type will need
* to be verified when the descriptor will be set.
*
* There is no way we can prove that this cast is correct before the descriptor is set,
* and maybe that descriptor will never be set if the GML document is illegal. However
* this code is executed only during XML unmarshalling, in which case our unmarshalling
* process will construct a descriptor compatible with the value rather than the converse.
*/
value = (T) xmlValue;
}
} else {
MetadataUtilities.propertyAlreadySet(DefaultParameterValue.class, "setXmlValue", "value");
}
}
}