blob: 783b8ebb54b717442edde893fbe0e9c203824a16 [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.util.Arrays;
import java.util.Set;
import java.util.Map;
import java.util.Objects;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.measure.Unit;
import org.opengis.util.CodeList;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.apache.sis.util.Classes;
import org.apache.sis.util.Numbers;
import org.apache.sis.util.Utilities;
import org.apache.sis.util.ComparisonMode;
import org.apache.sis.util.resources.Errors;
import org.apache.sis.measure.Range;
import org.apache.sis.measure.MeasurementRange;
import org.apache.sis.internal.util.Numerics;
import org.apache.sis.internal.util.CollectionsExt;
import org.apache.sis.internal.jaxb.Context;
import org.apache.sis.internal.jaxb.gco.PropertyType;
import org.apache.sis.internal.jaxb.referencing.CC_OperationParameter;
import org.apache.sis.referencing.IdentifiedObjects;
import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
import static org.apache.sis.util.ArgumentChecks.ensureCanCast;
/**
* The definition of a single parameter used by an operation method.
* For {@linkplain org.apache.sis.referencing.crs.AbstractCRS Coordinate Reference Systems}
* most parameter values are numeric, but other types of parameter values are possible.
*
* <p>A parameter descriptor contains the following properties:</p>
* <ul>
* <li>The parameter {@linkplain #getName() name}.</li>
* <li>The {@linkplain #getValueClass() class of values}. This is usually {@link Double}, {@code double[]},
* {@link Integer}, {@code int[]}, {@link Boolean}, {@link String} or {@link java.net.URI},
* but other types are allowed as well.</li>
* <li>Whether this parameter is optional or mandatory. This is specified by the {@linkplain #getMinimumOccurs()
* minimum occurences} number, which can be 0 or 1 respectively.</li>
* <li>The domain of values, as a {@linkplain #getMinimumValue() minimum value}, {@linkplain #getMaximumValue()
* maximum value} or an enumeration of {@linkplain #getValidValues() valid values}.</li>
* <li>The {@linkplain #getDefaultValue() default value}.</li>
* <li>The {@linkplain #getUnit() unit of measurement}.</li>
* </ul>
*
* @author Martin Desruisseaux (IRD, Geomatys)
* @author Johann Sorel (Geomatys)
* @version 0.8
*
* @param <T> the type of elements to be returned by {@link DefaultParameterValue#getValue()}.
*
* @see DefaultParameterValue
* @see DefaultParameterDescriptorGroup
*
* @since 0.4
* @module
*/
@XmlType(name = "OperationParameterType")
@XmlRootElement(name = "OperationParameter")
public class DefaultParameterDescriptor<T> extends AbstractParameterDescriptor implements ParameterDescriptor<T> {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -1978932430298071693L;
/**
* The class that describe the type of parameter values.
*
* @see #getValueClass()
*/
private final Class<T> valueClass;
/**
* A set of valid values (usually from a {@linkplain CodeList code list})
* or {@code null} if it does not apply. This set is immutable.
*
* @see #getValidValues()
*/
private final Set<T> validValues;
/**
* The minimum and maximum parameter value with their unit of measurement, or {@code null} if none.
* If this field is non-null, then <code>valueDomain.{@linkplain Range#getElementType() getElementType()}</code>
* shall be one of the following:
*
* <ul>
* <li>If {@link #valueClass} is not an array, then the range element type shall be the same class.</li>
* <li>If {@code valueClass} is an array, then the range element type shall be the wrapper of
* <code>valueClass.{@linkplain Class#getComponentType() getComponentType()}</code>.</li>
* </ul>
*
* @see #getValueDomain()
*/
private final Range<?> valueDomain;
/**
* The default value for the parameter, or {@code null}.
*
* @see #getDefaultValue()
*/
private final T defaultValue;
/**
* Constructs a descriptor from the given properties. The properties map is given unchanged to the
* {@linkplain AbstractParameterDescriptor#AbstractParameterDescriptor(Map, int, int) super-class constructor}.
* The following table is a reminder of main (not all) properties:
*
* <table class="sis">
* <caption>Recognized properties (non exhaustive list)</caption>
* <tr>
* <th>Property name</th>
* <th>Value type</th>
* <th>Returned by</th>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#NAME_KEY}</td>
* <td>{@link org.opengis.referencing.ReferenceIdentifier} or {@link String}</td>
* <td>{@link #getName()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#ALIAS_KEY}</td>
* <td>{@link org.opengis.util.GenericName} or {@link CharSequence} (optionally as array)</td>
* <td>{@link #getAlias()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#IDENTIFIERS_KEY}</td>
* <td>{@link org.opengis.referencing.ReferenceIdentifier} (optionally as array)</td>
* <td>{@link #getIdentifiers()}</td>
* </tr>
* <tr>
* <td>"description"</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getDescription()}</td>
* </tr>
* <tr>
* <td>{@value org.opengis.referencing.IdentifiedObject#REMARKS_KEY}</td>
* <td>{@link org.opengis.util.InternationalString} or {@link String}</td>
* <td>{@link #getRemarks()}</td>
* </tr>
* </table>
*
* The {@code valueDomain} argument combines the {@linkplain #getMinimumValue() minimum value},
* {@linkplain #getMaximumValue() maximum value}, {@linkplain #getUnit() unit of measurement}
* (if any) and information about whether the bounds are inclusive or exclusive.
* If this argument is non-null, then it shall comply to the following conditions:
*
* <ul>
* <li>The range shall be non-{@linkplain Range#isEmpty() empty}.</li>
* <li><code>valueDomain.{@linkplain Range#getElementType() getElementType()}</code> shall be equals
* to one of the following:
* <ul>
* <li>to {@code valueClass} if the later is not an array,</li>
* <li>or to <code>{@linkplain Numbers#primitiveToWrapper(Class)
* primitiveToWrapper}(valueClass.{@linkplain Class#getComponentType() getComponentType()})</code>
* if {@code valueClass} is an array.</li>
* </ul>
* </li>
* </ul>
*
* If both {@code valueDomain} and {@code validValues} are non-null, then all valid values shall be contained
* in the value domain.
*
* @param properties the properties to be given to the identified object.
* @param minimumOccurs the {@linkplain #getMinimumOccurs() minimum number of times} that values
* for this parameter group are required, or 0 if no restriction.
* @param maximumOccurs the {@linkplain #getMaximumOccurs() maximum number of times} that values
* for this parameter group are required, or {@link Integer#MAX_VALUE} if no restriction.
* @param valueClass the class that describes the type of the parameter value.
* @param valueDomain the minimum value, maximum value and unit of measurement, or {@code null} if none.
* @param validValues the list of valid values, or {@code null} if there is no restriction.
* This property is mostly for restricting values to a {@linkplain CodeList code list}
* or enumeration subset. It is not necessary to provide this property when all values
* from the code list or enumeration are valid.
* @param defaultValue the default value for the parameter, or {@code null} if none.
*/
@SuppressWarnings("unchecked")
public DefaultParameterDescriptor(final Map<String,?> properties,
final int minimumOccurs,
final int maximumOccurs,
final Class<T> valueClass,
final Range<?> valueDomain,
final T[] validValues,
final T defaultValue)
{
super(properties, minimumOccurs, maximumOccurs);
ensureNonNull("valueClass", valueClass);
ensureCanCast("defaultValue", valueClass, defaultValue);
if (valueDomain != null) {
Class<?> componentType = valueClass.getComponentType();
if (componentType != null) {
componentType = Numbers.primitiveToWrapper(componentType);
} else {
componentType = valueClass;
}
final Class<?> elementType = valueDomain.getElementType();
if (elementType != componentType) {
throw new IllegalArgumentException(Errors.getResources(properties).getString(
Errors.Keys.IllegalArgumentClass_2, "valueDomain",
"Range<" + Classes.getShortName(elementType) + '>'));
}
if (valueDomain.isEmpty()) {
throw new IllegalArgumentException(Errors.getResources(properties)
.getString(Errors.Keys.IllegalRange_2, valueDomain.getMinValue(), valueDomain.getMaxValue()));
}
}
this.valueClass = valueClass;
this.valueDomain = valueDomain;
this.defaultValue = Numerics.cached(defaultValue);
/*
* If the caller specified a set of valid values, then copy the values in
* a new set and verify their type and inclusion in the [min … max] range.
*/
if (validValues != null) {
final Set<T> valids = CollectionsExt.createSetForType(valueClass, validValues.length);
for (T value : validValues) {
if (value != null) {
value = Numerics.cached(value);
final Verifier error = Verifier.ensureValidValue(valueClass, null, valueDomain, value);
if (error != null) {
throw new IllegalArgumentException(error.message(properties, super.getName().getCode(), value));
}
valids.add(value);
}
}
this.validValues = CollectionsExt.unmodifiableOrCopy(valids);
} else {
this.validValues = null;
}
/*
* Finally, verify the default value if any.
*/
if (defaultValue != null) {
final Verifier error = Verifier.ensureValidValue(valueClass, this.validValues, valueDomain, defaultValue);
if (error != null) {
throw new IllegalArgumentException(error.message(properties, super.getName().getCode(), defaultValue));
}
}
}
/**
* Creates a new descriptor with the same values than the specified one.
* This copy constructor provides a way to convert an arbitrary implementation into a SIS one or a
* user-defined one (as a subclass), usually in order to leverage some implementation-specific API.
*
* <p>This constructor performs a shallow copy, i.e. the properties are not cloned.</p>
*
* @param descriptor the descriptor to shallow copy.
*
* @see #castOrCopy(ParameterDescriptor)
*/
@SuppressWarnings("unchecked")
protected DefaultParameterDescriptor(final ParameterDescriptor<T> descriptor) {
super(descriptor);
valueClass = descriptor.getValueClass();
validValues = descriptor.getValidValues();
defaultValue = descriptor.getDefaultValue();
valueDomain = Parameters.getValueDomain(descriptor);
}
/**
* Returns a SIS parameter implementation with the same values than the given arbitrary implementation.
* If the given object is {@code null}, then this method returns {@code null}.
* Otherwise if the given object is already a SIS implementation, then the given object is returned unchanged.
* Otherwise a new SIS implementation is created and initialized to the values of the given object.
*
* @param <T> the type of values.
* @param object the object to get as a SIS implementation, or {@code null} if none.
* @return a SIS implementation containing the values of the given object (may be the
* given object itself), or {@code null} if the argument was null.
*/
public static <T> DefaultParameterDescriptor<T> castOrCopy(final ParameterDescriptor<T> object) {
return (object == null) || (object instanceof DefaultParameterDescriptor<?>)
? (DefaultParameterDescriptor<T>) object : new DefaultParameterDescriptor<>(object);
}
/**
* Returns the GeoAPI interface implemented by this class.
* The SIS implementation returns {@code ParameterDescriptor.class}.
*
* <div class="note"><b>Note for implementers:</b>
* Subclasses usually do not need to override this method since GeoAPI does not define {@code ParameterDescriptor}
* sub-interface. Overriding possibility is left mostly for implementers who wish to extend GeoAPI with their own
* set of interfaces.</div>
*
* @return {@code ParameterDescriptor.class} or a user-defined sub-interface.
*/
@Override
@SuppressWarnings("unchecked")
public Class<? extends ParameterDescriptor<T>> getInterface() {
return (Class) ParameterDescriptor.class;
}
/**
* Returns the class that describe the type of the parameter.
*
* @return the parameter value class.
*/
@Override
public final Class<T> getValueClass() {
return valueClass;
}
/**
* If this parameter allows only a finite set of values, returns that set.
* The set of valid values is usually a {@linkplain CodeList code list} or enumeration.
* This method returns {@code null} if this parameter does not limit values to a finite set.
*
* @return a finite set of valid values (usually from a {@linkplain CodeList code list}),
* or {@code null} if it does not apply or if there is no restriction.
*/
@Override
@SuppressWarnings("ReturnOfCollectionOrArrayField")
public Set<T> getValidValues() {
return validValues; // Null or unmodifiable
}
/**
* Returns the domain of values with their unit of measurement (if any), or {@code null} if none.
* The {@code Range} object combines the {@linkplain #getValueClass() value class},
* {@linkplain #getMinimumValue() minimum value}, {@linkplain #getMaximumValue() maximum value}
* and whether these values are inclusive or inclusive. If the range is an instance of
* {@link MeasurementRange}, then it contains also the {@linkplain #getUnit() unit of measurement}.
*
* <div class="note"><b>API note:</b> If this method returns a non-null value, then its type is either exactly
* {@code Range<T>}, or {@code Range<E>} where {@code <E>} is the {@linkplain Class#getComponentType() component
* type} of {@code <T>} (using wrapper classes for primitive types).</div>
*
* @return the domain of values, or {@code null}.
*
* @see Parameters#getValueDomain(ParameterDescriptor)
*/
/* Implementation note: this method is final because the constructor performs various checks on range validity,
* and we can not express those rules in the method signature. The 'Verifier.ensureValidValue(…)' method needs
* some guarantees about range validity, so we can not let users override this method with a range that may
* break them.
*/
public final Range<?> getValueDomain() {
return valueDomain;
}
/**
* Returns the minimum parameter value. If there is no minimum value, or if minimum
* value is inappropriate for the {@linkplain #getValueClass() value class}, then
* this method returns {@code null}.
*
* <p>This is a convenience method for
* <code>{@linkplain #getValueDomain()}.{@linkplain Range#getMinValue() getMinValue()}</code>.
* Note that this method said nothing about whether the value is {@linkplain Range#isMinIncluded() inclusive}.</p>
*
* @return the minimum parameter value (often an instance of {@link Double}), or {@code null} if unbounded.
*/
@Override
@SuppressWarnings("unchecked")
public Comparable<T> getMinimumValue() {
return (valueDomain != null && valueDomain.getElementType() == valueClass)
? (Comparable<T>) valueDomain.getMinValue() : null;
}
/**
* Returns the maximum parameter value. If there is no maximum value, or if maximum
* value is inappropriate for the {@linkplain #getValueClass() value type}, then
* this method returns {@code null}.
*
* <p>This is a convenience method for
* <code>{@linkplain #getValueDomain()}.{@linkplain Range#getMaxValue() getMaxValue()}</code>.
* Note that this method said nothing about whether the value is {@linkplain Range#isMaxIncluded() inclusive}.</p>
*
* @return the minimum parameter value (often an instance of {@link Double}), or {@code null} if unbounded.
*/
@Override
@SuppressWarnings("unchecked")
public Comparable<T> getMaximumValue() {
return (valueDomain != null && valueDomain.getElementType() == valueClass)
? (Comparable<T>) valueDomain.getMaxValue() : null;
}
/**
* Returns the default value for the parameter. The return type can be any type
* including a {@link Number} or a {@link String}. If there is no default value,
* then this method returns {@code null}.
*
* @return the default value, or {@code null} in none.
*/
@Override
public T getDefaultValue() {
return defaultValue;
}
/**
* Returns the unit of measurement for the
* {@linkplain #getMinimumValue() minimum},
* {@linkplain #getMaximumValue() maximum} and
* {@linkplain #getDefaultValue() default} values.
* This attribute apply only if the values is of numeric type (usually an instance of {@link Double}).
*
* <p>This is a convenience method for
* <code>{@linkplain #getValueDomain()}.{@linkplain MeasurementRange#unit() unit()}</code>.</p>
*
* @return the unit for numeric value, or {@code null} if it doesn't apply to the value type.
*/
@Override
public Unit<?> getUnit() {
return (valueDomain instanceof MeasurementRange<?>) ? ((MeasurementRange<?>) valueDomain).unit() : null;
}
/**
* Creates a new parameter value instance initialized with the {@linkplain #getDefaultValue() default value}.
* The {@linkplain DefaultParameterDescriptor parameter descriptor} for the created parameter value will be
* {@code this} object.
*
* @return a parameter initialized to the default value.
*/
@Override
public ParameterValue<T> createValue() {
return new DefaultParameterValue<>(this);
}
/**
* Compares the specified object with this parameter for equality.
* The strictness level is controlled by the second argument.
* This method compares the following properties in every cases:
*
* <ul>
* <li>{@link #getName()}, compared {@linkplain #isHeuristicMatchForName(String) heuristically}
* in {@code IGNORE_METADATA} or less strict mode.</li>
* <li>{@link #getValueClass()}</li>
* <li>{@link #getDefaultValue()}</li>
* <li>{@link #getUnit()}</li>
* </ul>
*
* All other properties (minimum and maximum occurrences, minimum, maximum and valid values)
* are compared only for modes stricter than {@link ComparisonMode#IGNORE_METADATA}.
*
* @return {@inheritDoc}
*/
@Override
public boolean equals(final Object object, final ComparisonMode mode) {
if (object == this) {
return true;
}
if (super.equals(object, mode)) {
switch (mode) {
default: {
/*
* Tests for name, since parameters with different name have completely different meaning.
* For example there is no difference between "semi_major" and "semi_minor" parameters
* except the name. We do not perform this comparison if the user asked for metadata
* comparison, because in such case the names have already been compared by the super-class.
*/
final ParameterDescriptor<?> that = (ParameterDescriptor<?>) object;
return getValueClass() == that.getValueClass() &&
Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
Utilities.deepEquals(getUnit(), that.getUnit(), mode) &&
(isHeuristicMatchForName(that.getName().getCode()) ||
IdentifiedObjects.isHeuristicMatchForName(that, getName().getCode()));
}
case BY_CONTRACT: {
final ParameterDescriptor<?> that = (ParameterDescriptor<?>) object;
return getMinimumOccurs() == that.getMinimumOccurs() &&
getMaximumOccurs() == that.getMaximumOccurs() &&
getValueClass() == that.getValueClass() &&
Objects. equals(getValidValues(), that.getValidValues()) &&
Objects. equals(getMinimumValue(), that.getMinimumValue()) &&
Objects. equals(getMaximumValue(), that.getMaximumValue()) &&
Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
Utilities.deepEquals(getUnit(), that.getUnit(), mode);
}
case STRICT: {
final DefaultParameterDescriptor<?> that = (DefaultParameterDescriptor<?>) object;
return this.valueClass == that.valueClass &&
Objects. equals(this.validValues, that.validValues) &&
Objects. equals(this.valueDomain, that.valueDomain) &&
Objects.deepEquals(this.defaultValue, that.defaultValue);
}
}
}
return false;
}
/**
* Invoked by {@link #hashCode()} for computing the hash code when first needed.
*
* @return {@inheritDoc}
*/
@Override
protected long computeHashCode() {
return Arrays.deepHashCode(new Object[] {valueClass, valueDomain, defaultValue}) + super.computeHashCode();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// 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. ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////////////
/**
* Constructs a new object in which every attributes are set to a null value.
* <strong>This is not a valid object.</strong> This constructor is strictly
* reserved to JAXB, which will assign values to the fields using reflexion.
*
* <p>This constructor fetches the value class and the unit of measurement from the enclosing
* {@link DefaultParameterValue}, if presents, because those information are not presents in GML.
* They are GeoAPI additions.</p>
*/
@SuppressWarnings("unchecked")
private DefaultParameterDescriptor() {
final PropertyType<?,?> wrapper = Context.getWrapper(Context.current());
if (wrapper instanceof CC_OperationParameter) {
final CC_OperationParameter param = (CC_OperationParameter) wrapper;
/*
* This unsafe cast would be forbidden if this constructor was public or used in any context where the
* user can choose the value of <T>. But this constructor should be invoked only during unmarshalling,
* after the creation of the ParameterValue (this is the reverse creation order than what we normally
* do through the public API). The 'valueClass' should be compatible with DefaultParameterValue.value,
* and the parameterized type visible to the user should be only <?>.
*/
valueClass = (Class) param.valueClass;
valueDomain = param.valueDomain;
} else {
valueClass = null;
valueDomain = null;
}
validValues = null;
defaultValue = null;
}
}