| /* |
| * 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; |
| } |
| } |