sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1576977 [3/5] - in /sis/trunk: ./ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-metadata/src/main/java/org/apache/sis/io/wkt/ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/src/main/java/org/ap...
Date Wed, 12 Mar 2014 22:52:28 GMT
Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterDescriptor.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -19,35 +19,33 @@ package org.apache.sis.parameter;
 import java.util.Arrays;
 import java.util.Set;
 import java.util.Map;
-import java.util.Collection;
-import java.util.LinkedHashSet;
 import javax.measure.unit.Unit;
-
 import org.opengis.util.CodeList;
 import org.opengis.parameter.ParameterValue;
 import org.opengis.parameter.ParameterDescriptor;
-
-import org.apache.sis.util.Debug;
 import org.apache.sis.util.Classes;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.util.collection.Containers;
+import org.apache.sis.io.wkt.Formatter;
+import org.apache.sis.io.wkt.ElementKind;
+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.referencing.WKTUtilities;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.AbstractIdentifiedObject;
 
 import static org.apache.sis.util.ArgumentChecks.ensureNonNull;
 import static org.apache.sis.util.ArgumentChecks.ensureCanCast;
-import static org.apache.sis.util.collection.Containers.hashMapCapacity;
-import static org.apache.sis.internal.util.CollectionsExt.unmodifiableOrCopy;
 
 // Related to JDK7
 import org.apache.sis.internal.jdk7.Objects;
 
 
 /**
- * The definition of a parameter used by an operation method.
+ * 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.
  *
@@ -59,9 +57,10 @@ import org.apache.sis.internal.jdk7.Obje
  *       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 {@linkplain #getDefaultValue() default value} and its {@linkplain #getUnit() unit of measurement}.</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>
  *
  * @param <T> The type of elements to be returned by {@link DefaultParameterValue#getValue()}.
@@ -79,66 +78,56 @@ public class DefaultParameterDescriptor<
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = -295668622297737705L;
-
-    /**
-     * Key for the <code>{@value}</code> property to be given to the constructor.
-     * This is used for setting the value to be returned by {@link #getMinimumValue()}.
-     */
-    public static final String MINIMUM_VALUE_KEY = "minimumValue";
-
-    /**
-     * Key for the <code>{@value}</code> property to be given to the constructor.
-     * This is used for setting the value to be returned by {@link #getMaximumValue()}.
-     */
-    public static final String MAXIMUM_VALUE_KEY = "maximumValue";
-
-    /**
-     * Key for the <code>{@value}</code> property to be given to the constructor.
-     * This is used for setting the value to be returned by {@link #getValidValues()}.
-     */
-    public static final String VALID_VALUES_KEY = "validValues";
+    private static final long serialVersionUID = 7433401733923393656L;
 
     /**
      * {@code true} if this parameter is mandatory, or {@code false} if it is optional.
+     *
+     * @see #getMinimumOccurs()
      */
     private final boolean required;
 
     /**
      * 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 doesn't apply. This set is immutable.
+     *
+     * @see #getValidValues()
      */
     private final Set<T> validValues;
 
     /**
-     * The default value for the parameter, or {@code null}.
-     */
-    private final T defaultValue;
-
-    /**
-     * The minimum parameter value, or {@code null}.
-     */
-    private final Comparable<T> minimumValue;
-
-    /**
-     * The maximum parameter value, or {@code null}.
+     * 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 Comparable<T> maximumValue;
+    private final Range<?> valueDomain;
 
     /**
-     * The unit for default, minimum and maximum values, or {@code null}.
+     * The default value for the parameter, or {@code null}.
+     *
+     * @see #getDefaultValue()
      */
-    private final Unit<?> unit;
+    private final T defaultValue;
 
     /**
-     * Constructs a descriptor from a set of properties. The properties given in argument follow the same rules
-     * than for the {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
-     * Additionally, the following properties are understood by this constructor:
+     * Constructs a descriptor from the given properties. The properties map is given unchanged to the
+     * {@linkplain AbstractIdentifiedObject#AbstractIdentifiedObject(Map) super-class constructor}.
+     * The following table is a reminder of main (not all) properties:
      *
      * <table class="sis">
      *   <tr>
@@ -147,24 +136,6 @@ public class DefaultParameterDescriptor<
      *     <th>Returned by</th>
      *   </tr>
      *   <tr>
-     *     <td>{@value #MINIMUM_VALUE_KEY}</td>
-     *     <td>{@code Comparable<T>}</td>
-     *     <td>{@link #getMinimumValue()}</td>
-     *   </tr>
-     *   <tr>
-     *     <td>{@value #MAXIMUM_VALUE_KEY}</td>
-     *     <td>{@code Comparable<T>}</td>
-     *     <td>{@link #getMaximumValue()}</td>
-     *   </tr>
-     *   <tr>
-     *     <td>{@value #VALID_VALUES_KEY}</td>
-     *     <td>{@code Collection<T>} or {@code T[]}</td>
-     *     <td>{@link #getValidValues()}</td>
-     *   </tr>
-     *   <tr>
-     *     <th colspan="3" class="hsep">Defined in parent class (reminder)</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>
@@ -186,96 +157,95 @@ public class DefaultParameterDescriptor<
      *   </tr>
      * </table>
      *
-     * Generally speaking, information provided in the {@code properties} map are considered ignorable metadata
-     * (except the parameter name) while information provided as explicit arguments may have an impact on coordinate
-     * transformation results. See {@link #equals(Object, ComparisonMode)} for more information.
+     * 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 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.
-     * @param unit         The unit of measurement for the default, minimum and maximum values, or {@code null} if none.
      * @param required     {@code true} if this parameter is mandatory, or {@code false} if it is optional.
      */
     @SuppressWarnings("unchecked")
     public DefaultParameterDescriptor(final Map<String,?> properties,
                                       final Class<T>      valueClass,
+                                      final Range<?>      valueDomain,
+                                      final T[]           validValues,
                                       final T             defaultValue,
-                                      final Unit<?>       unit,
                                       final boolean       required)
     {
         super(properties);
-        final Comparable<T> minimumValue = Containers.property(properties, MINIMUM_VALUE_KEY, Comparable.class);
-        final Comparable<T> maximumValue = Containers.property(properties, MAXIMUM_VALUE_KEY, Comparable.class);
-        ensureNonNull("valueClass",      valueClass);
-        ensureCanCast("defaultValue",    valueClass, defaultValue);
-        ensureCanCast(MINIMUM_VALUE_KEY, valueClass, minimumValue);
-        ensureCanCast(MAXIMUM_VALUE_KEY, valueClass, maximumValue);
-        this.required     = required;
-        this.valueClass   = valueClass;
-        this.defaultValue = Numerics.cached(defaultValue);
-        this.minimumValue = Numerics.cached(minimumValue);
-        this.maximumValue = Numerics.cached(maximumValue);
-        this.unit         = unit;
-        /*
-         * If the caller specified a unit of measurement, then
-         * verify that the values are of some numerical type.
-         */
-        if (unit != null) {
-            Class<?> componentType = valueClass;
-            for (Class<?> c; (c = componentType.getComponentType()) != null;) {
-                componentType = c;
+        ensureNonNull("valueClass",   valueClass);
+        ensureCanCast("defaultValue", valueClass, defaultValue);
+        if (valueDomain != null) {
+            Class<?> componentType = valueClass.getComponentType();
+            if (componentType != null) {
+                componentType = Numbers.primitiveToWrapper(componentType);
+            } else {
+                componentType = valueClass;
             }
-            componentType = Numbers.primitiveToWrapper(componentType);
-            if (!Number.class.isAssignableFrom(componentType)) {
+            final Class<?> elementType = valueDomain.getElementType();
+            if (elementType != componentType) {
                 throw new IllegalArgumentException(Errors.getResources(properties).getString(
-                        Errors.Keys.IllegalUnitFor_2, super.getName().getCode(), unit));
+                        Errors.Keys.IllegalArgumentClass_2, "valueDomain",
+                        "Range<" + Classes.getShortName(elementType) + '>'));
             }
-        }
-        /*
-         * If the caller specified minimum and maximum values, then
-         * verify that the minimum is not greater than the maximum.
-         */
-        if (minimumValue != null && maximumValue != null) {
-            if (minimumValue.compareTo(valueClass.cast(maximumValue)) > 0) {
+            if (valueDomain.isEmpty()) {
                 throw new IllegalArgumentException(Errors.getResources(properties)
-                        .getString(Errors.Keys.IllegalRange_2, minimumValue, maximumValue));
+                        .getString(Errors.Keys.IllegalRange_2, valueDomain.getMinValue(), valueDomain.getMaxValue()));
             }
         }
+        this.required     = required;
+        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.
          */
-        final Object values = properties.get(VALID_VALUES_KEY);
-        if (values != null) {
-            final Object[] array;
-            if (values instanceof Object[]) {
-                array = (Object[]) values;
-            } else if (values instanceof Collection<?>) {
-                array = ((Collection<?>) values).toArray();
-            } else {
-                throw new IllegalArgumentException(Errors.getResources(properties)
-                        .getString(Errors.Keys.IllegalPropertyClass_2, VALID_VALUES_KEY, values.getClass()));
-            }
-            final Set<T> valids = new LinkedHashSet<T>(hashMapCapacity(array.length));
-            for (Object value : array) {
+        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, minimumValue, maximumValue, 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);
                 }
-                valids.add((T) value);
             }
-            validValues = unmodifiableOrCopy(valids);
+            this.validValues = CollectionsExt.unmodifiableOrCopy(valids);
         } else {
-            validValues = null;
+            this.validValues = null;
         }
         /*
          * Finally, verify the default value if any.
          */
         if (defaultValue != null) {
-            final Verifier error = Verifier.ensureValidValue(valueClass, validValues, minimumValue, maximumValue, defaultValue);
+            final Verifier error = Verifier.ensureValidValue(valueClass, this.validValues, valueDomain, defaultValue);
             if (error != null) {
                 throw new IllegalArgumentException(error.message(properties, super.getName().getCode(), defaultValue));
             }
@@ -290,16 +260,33 @@ public class DefaultParameterDescriptor<
      * <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)
      */
-    public DefaultParameterDescriptor(final ParameterDescriptor<T> descriptor) {
+    @SuppressWarnings("unchecked")
+    protected DefaultParameterDescriptor(final ParameterDescriptor<T> descriptor) {
         super(descriptor);
         required     = descriptor.getMinimumOccurs() != 0;
         valueClass   = descriptor.getValueClass();
         validValues  = descriptor.getValidValues();
         defaultValue = descriptor.getDefaultValue();
-        minimumValue = descriptor.getMinimumValue();
-        maximumValue = descriptor.getMaximumValue();
-        unit         = descriptor.getUnit();
+        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<T>(object);
     }
 
     /**
@@ -320,7 +307,7 @@ public class DefaultParameterDescriptor<
     }
 
     /**
-     * The minimum number of times that values for this parameter group or parameter are required.
+     * The minimum number of times that values for this parameter are required.
      * A value of 0 means an optional parameter and a value of 1 means a mandatory parameter.
      *
      * @see #getMaximumOccurs()
@@ -331,7 +318,7 @@ public class DefaultParameterDescriptor<
     }
 
     /**
-     * The maximum number of times that values for this parameter group or parameter can be included.
+     * The maximum number of times that values for this parameter can be included.
      * For a {@code ParameterDescriptor}, the value is always 1.
      *
      * @return The maximum occurrence.
@@ -361,17 +348,17 @@ public class DefaultParameterDescriptor<
      * @return The parameter value class.
      */
     @Override
-    public Class<T> getValueClass() {
+    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 enumerations.
+     * 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 {@code null} if it does not apply or if there is no restriction.
      */
     @Override
     public Set<T> getValidValues() {
@@ -379,15 +366,27 @@ public class DefaultParameterDescriptor<
     }
 
     /**
-     * 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.
+     * 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.
      */
-    @Override
-    public T getDefaultValue() {
-        return defaultValue;
+    public final Range<?> getValueDomain() {
+        return valueDomain;
     }
 
     /**
@@ -395,11 +394,17 @@ public class DefaultParameterDescriptor<
      * value is inappropriate for the {@linkplain #getValueClass() value class}, then
      * this method returns {@code null}.
      *
-     * @return The minimum parameter value (often an instance of {@link Double}), or {@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 minimumValue;
+        return (valueDomain != null && valueDomain.getElementType() == valueClass)
+               ? (Comparable<T>) valueDomain.getMinValue() : null;
     }
 
     /**
@@ -407,25 +412,46 @@ public class DefaultParameterDescriptor<
      * value is inappropriate for the {@linkplain #getValueClass() value type}, then
      * this method returns {@code null}.
      *
-     * @return The minimum parameter value (often an instance of {@link Double}), or {@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 maximumValue;
+        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 #getDefaultValue() default},
-     * {@linkplain #getMinimumValue() minimum} and
-     * {@linkplain #getMaximumValue() maximum} values.
+     * {@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 unit;
+        return (valueDomain instanceof MeasurementRange<?>) ? ((MeasurementRange<?>) valueDomain).unit() : null;
     }
 
     /**
@@ -473,20 +499,18 @@ public class DefaultParameterDescriptor<
                            getMaximumOccurs() == that.getMaximumOccurs() &&
                            getValueClass()    == that.getValueClass()    &&
                            Objects.    equals(getValidValues(),  that.getValidValues())  &&
-                           Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
                            Objects.    equals(getMinimumValue(), that.getMinimumValue()) &&
                            Objects.    equals(getMaximumValue(), that.getMaximumValue()) &&
+                           Objects.deepEquals(getDefaultValue(), that.getDefaultValue()) &&
                            Objects.    equals(getUnit(),         that.getUnit());
                 }
                 case STRICT: {
                     final DefaultParameterDescriptor<?> that = (DefaultParameterDescriptor<?>) object;
-                    return                    this.required   == that.required      &&
-                                              this.valueClass == that.valueClass    &&
-                           Objects.    equals(this.validValues,  that.validValues)  &&
-                           Objects.deepEquals(this.defaultValue, that.defaultValue) &&
-                           Objects.    equals(this.minimumValue, that.minimumValue) &&
-                           Objects.    equals(this.maximumValue, that.maximumValue) &&
-                           Objects.    equals(this.unit,         that.unit);
+                    return                    this.required   == that.required     &&
+                                              this.valueClass == that.valueClass   &&
+                           Objects.    equals(this.validValues,  that.validValues) &&
+                           Objects.    equals(this.valueDomain,  that.valueDomain) &&
+                           Objects.deepEquals(this.defaultValue, that.defaultValue);
                 }
             }
         }
@@ -494,39 +518,37 @@ public class DefaultParameterDescriptor<
     }
 
     /**
-     * {@inheritDoc}
+     * Invoked by {@link #hashCode()} for computing the hash code when first needed.
      *
      * @return {@inheritDoc}
      */
     @Override
     protected long computeHashCode() {
-        return Arrays.deepHashCode(new Object[] {required, valueClass, defaultValue, minimumValue, maximumValue, unit})
+        return Arrays.deepHashCode(new Object[] {required, valueClass, valueDomain, defaultValue})
                 + super.computeHashCode();
     }
 
     /**
-     * Returns a string representation of this descriptor. The string returned by this
-     * method is for information purpose only and may change in future SIS version.
+     * Formats this parameter as a pseudo-<cite>Well Known Text</cite> element. The WKT specification
+     * does not define any representation of parameter descriptors. Apache SIS fallback on the
+     * {@linkplain DefaultParameterValue#formatTo(Formatter) same representation than parameter value},
+     * with the descriptor {@linkplain #getDefaultValue() default value} in place of the parameter value.
+     * The text formatted by this method is {@linkplain Formatter#setInvalidWKT flagged as invalid WKT}.
+     *
+     * @param  formatter The formatter where to format the inner content of this WKT element.
+     * @return {@code "Parameter"}.
      */
-    @Debug
     @Override
-    public String toString() {
-        final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(this))
-                .append("[\"").append(getName().getCode()).append("\", ")
-                .append(getMinimumOccurs() == 0 ? "optional" : "mandatory");
-        buffer.append(", class=").append(Classes.getShortName(valueClass));
-        if (minimumValue != null || maximumValue != null) {
-            buffer.append(", valid=[").append(minimumValue != null ? minimumValue : "-∞")
-                  .append(" … ") .append(maximumValue != null ? maximumValue :  "∞").append(']');
-        } else if (validValues != null) {
-            buffer.append(", valid=").append(validValues);
-        }
-        if (defaultValue != null) {
-            buffer.append(", default=").append(defaultValue);
-        }
+    protected String formatTo(final Formatter formatter) {
+        WKTUtilities.appendName(this, formatter, ElementKind.PARAMETER);
+        formatter.setInvalidWKT(this, null);
+        formatter.appendAny(defaultValue);
+        final Unit<?> unit = getUnit();
         if (unit != null) {
-            buffer.append(", unit=").append(unit);
+            if (!formatter.getConvention().isSimplified() || !unit.equals(formatter.toContextualUnit(unit))) {
+                formatter.append(unit);
+            }
         }
-        return buffer.append(']').toString();
+        return "Parameter";
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -45,37 +45,43 @@ import org.apache.sis.internal.jdk7.Obje
 
 
 /**
- * A parameter value used by an operation method. Most CRS parameter values are numeric and can
- * be obtained by the {@link #intValue()} or {@link #doubleValue()} methods. But other types of
- * parameter values are possible and can be handled by the more generic {@link #getValue()} and
- * {@link #setValue(Object)} methods. The type and constraints on parameter values are given by
- * the {@linkplain #getDescriptor() descriptor}.
+ * A single parameter value used by an operation method. Most CRS parameter values are numeric and can be obtained
+ * by the {@link #intValue()} or {@link #doubleValue()} methods. But other types of parameter values are possible
+ * and can be handled by the more generic {@link #getValue()} and {@link #setValue(Object)} methods.
  *
- * <p>The following table summarizes the ISO 19111 attributes with the corresponding getter and
- * setter methods:</p>
+ * <p>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">
- *   <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>{@code stringValue}</td>       <td>{@link String}</td>   <td>{@link #stringValue()}</td>         <td>{@code  setValue(Object)}</td></tr>
- *   <tr><td>{@code 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>{@code valueList}</td>         <td>{@code double[]}</td> <td>{@link #doubleValueList()}</td>     <td>{@code  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>{@code integerValue}</td>      <td>{@code int}</td>      <td>{@link #intValue()}</td>            <td>{@link #setValue(int)}</td></tr>
- *   <tr><td>{@code integerValueList}</td>  <td>{@code int[]}</td>    <td>{@link #intValueList()}</td>        <td>{@code  setValue(Object)}</td></tr>
- *   <tr><td>{@code booleanValue}</td>      <td>{@code boolean}</td>  <td>{@link #booleanValue()}</td>        <td>{@link #setValue(boolean)}</td></tr>
- *   <tr><td>{@code valueFile}</td>         <td>{@link URI}</td>      <td>{@link #valueFile()}</td>           <td>{@code  setValue(Object)}</td></tr>
- *   <tr><td>{@code valueFileCitation}</td> <td>{@link Citation}</td> <td>{@code  getValue()}</td>            <td>{@code  setValue(Object)}</td></tr>
+ *   <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>
  *
- * Instances of {@code ParameterValue} are created by the {@link ParameterDescriptor#createValue()} method.
- * The parameter type can be fetch with the following idiom:
+ * 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();
  * }
  *
+ * {@section Instantiation}
+ * 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.
+ *
  * {@section Implementation note for subclasses}
  * All read and write operations (except constructors, {@link #equals(Object)} and {@link #hashCode()})
  * ultimately delegates to the following methods:
@@ -97,7 +103,7 @@ import org.apache.sis.internal.jdk7.Obje
  * @module
  *
  * @see DefaultParameterDescriptor
- * @see DefaultParameterGroup
+ * @see DefaultParameterValueGroup
  */
 public class DefaultParameterValue<T> extends FormattableObject implements ParameterValue<T>, Serializable, Cloneable {
     /**
@@ -609,7 +615,7 @@ public class DefaultParameterValue<T> ex
      */
     @Override
     public void setValue(final double[] values, final Unit<?> unit) throws InvalidParameterValueException {
-        setValue(value, unit);
+        setValue((Object) values, unit);
     }
 
     /**
@@ -630,9 +636,38 @@ public class DefaultParameterValue<T> ex
      * @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).
      */
+    @SuppressWarnings("unchecked")
     protected void setValue(final Object value, final Unit<?> unit) throws InvalidParameterValueException {
-        this.value = Verifier.ensureValidValue(descriptor, value, unit);
-        this.unit  = unit; // Assign only on success.
+        final T convertedValue = Verifier.ensureValidValue(descriptor, value, unit);
+        if (value != null) {
+            validate(convertedValue);
+            this.value = (T) value; // 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.
+     *
+     * {@section Unit of measurement}
+     * 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.
+     *
+     * {@section Standard validations}
+     * 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  value 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 value) throws InvalidParameterValueException {
     }
 
     /**

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Parameters.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -16,14 +16,18 @@
  */
 package org.apache.sis.parameter;
 
+import javax.measure.unit.Unit;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.ParameterValue;
+import org.apache.sis.measure.Range;
+import org.apache.sis.measure.NumberRange;
+import org.apache.sis.measure.MeasurementRange;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Static;
 
 
 /**
- * Static methods working on parameters.
+ * Static methods working on parameters and their descriptors.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4 (derived from geotk-2.1)
@@ -91,4 +95,44 @@ public final class Parameters extends St
         }
         return (ParameterValue<T>) value;
     }
+
+    /**
+     * Returns the domain of valid values defined by the given descriptor, or {@code null} if none.
+     * This method builds the range from the {@linkplain DefaultParameterDescriptor#getMinimumValue() minimum value},
+     * {@linkplain DefaultParameterDescriptor#getMaximumValue() maximum value} and, if the values are numeric, from
+     * the {@linkplain DefaultParameterDescriptor#getUnit() unit}.
+     *
+     * @param  descriptor The parameter descriptor, or {@code null}.
+     * @return The domain of valid values, or {@code null} if none.
+     *
+     * @see DefaultParameterDescriptor#getValueDomain()
+     */
+    @SuppressWarnings({"unchecked", "rawtypes"})
+    public static Range<?> getValueDomain(final ParameterDescriptor<?> descriptor) {
+        if (descriptor != null) {
+            if (descriptor instanceof DefaultParameterDescriptor<?>) {
+                return ((DefaultParameterDescriptor<?>) descriptor).getValueDomain();
+            }
+            final Class<?> valueClass = descriptor.getValueClass();
+            final Comparable<?> minimumValue = descriptor.getMinimumValue();
+            final Comparable<?> maximumValue = descriptor.getMaximumValue();
+            if ((minimumValue == null || valueClass.isInstance(minimumValue)) &&
+                (maximumValue == null || valueClass.isInstance(maximumValue)))
+            {
+                if (Number.class.isAssignableFrom(valueClass)) {
+                    final Unit<?> unit = descriptor.getUnit();
+                    if (unit != null) {
+                        return new MeasurementRange((Class) valueClass,
+                                (Number) minimumValue, true, (Number) maximumValue, true, unit);
+                    } else if (minimumValue != null || maximumValue != null) {
+                        return new NumberRange((Class) valueClass,
+                                (Number) minimumValue, true, (Number) maximumValue, true);
+                    }
+                } else if (minimumValue != null || maximumValue != null) {
+                    return new Range(valueClass, minimumValue, true, maximumValue, true);
+                }
+            }
+        }
+        return null;
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -18,12 +18,14 @@ package org.apache.sis.parameter;
 
 import java.util.Map;
 import java.util.Set;
+import java.lang.reflect.Array;
 import javax.measure.unit.Unit;
 import javax.measure.converter.UnitConverter;
 import javax.measure.converter.ConversionException;
 import org.opengis.parameter.ParameterDescriptor;
 import org.opengis.parameter.GeneralParameterDescriptor;
 import org.opengis.parameter.InvalidParameterValueException;
+import org.apache.sis.measure.Range;
 import org.apache.sis.measure.Units;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.resources.Errors;
@@ -32,7 +34,7 @@ import org.apache.sis.util.resources.Err
 /**
  * Verifies the validity of a given value.
  * An instance of {@code Verifier} is created only if an error is detected.
- * In such case, the error message is given by {@link #message(String, Object)}.
+ * In such case, the error message is given by {@link #message(Map, String, Object)}.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
  * @since   0.4 (derived from geotk-2.0)
@@ -55,7 +57,8 @@ final class Verifier {
      * The current implementation relies on the following invariants:
      *
      * <ul>
-     *   <li>The first element in this array is always the parameter name.</li>
+     *   <li>The first element in this array will be the parameter name. Before the name is known,
+     *       this element is either {@code null} or the index to append to the name.</li>
      *   <li>The last element shall be set to the erroneous value if {@link #needsValue} is {@code true}.</li>
      * </ul>
      */
@@ -72,7 +75,7 @@ final class Verifier {
 
     /**
      * Ensures that the given value is valid according the specified parameter descriptor.
-     * This convenience method ensures that {@code value} is assignable to the
+     * This method ensures that {@code value} is assignable to the
      * {@linkplain ParameterDescriptor#getValueClass() expected class}, is between the
      * {@linkplain ParameterDescriptor#getMinimumValue() minimum} and
      * {@linkplain ParameterDescriptor#getMaximumValue() maximum} values and is one of the
@@ -85,20 +88,21 @@ final class Verifier {
      * @param  descriptor The parameter descriptor to check against.
      * @param  value      The value to check, or {@code null}.
      * @param  unit       The unit of the value to check, or {@code null}.
-     * @return The value casted to the descriptor parameterized type, or the
-     *         {@linkplain ParameterDescriptor#getDefaultValue() default value}
-     *         if the given value was null.
+     * @return The given value converted to the descriptor unit if any,
+     *         then casted to the descriptor parameterized type.
      * @throws InvalidParameterValueException if the parameter value is invalid.
      */
     @SuppressWarnings("unchecked")
     static <T> T ensureValidValue(final ParameterDescriptor<T> descriptor, final Object value, final Unit<?> unit)
             throws InvalidParameterValueException
     {
-        final Class<T> type = descriptor.getValueClass();
+        final Class<T> valueClass = descriptor.getValueClass();
         /*
          * Before to verify if the given value is inside the bounds, we need to convert the value
-         * to the units used by the parameter descriptor.
+         * to the units used by the parameter descriptor. The first part of this block verifies
+         * the validity of the unit argument, so we execute it even if 'value' is null.
          */
+        UnitConverter converter = null;
         Object convertedValue = value;
         if (unit != null) {
             final Unit<?> def = descriptor.getUnit();
@@ -111,39 +115,85 @@ final class Verifier {
                 if (getUnitMessageID(unit) != expectedID) {
                     throw new IllegalArgumentException(Errors.format(expectedID, unit));
                 }
-                if (value != null && Number.class.isAssignableFrom(type)) {
-                    final UnitConverter converter;
+                /*
+                 * Verify the type of the user's value before to perform the unit conversion,
+                 * because the conversion will create a new object not necessarily of the same type.
+                 */
+                if (value != null) {
+                    if (!valueClass.isInstance(value)) {
+                        final String name = getName(descriptor);
+                        throw new InvalidParameterValueException(
+                                Errors.format(Errors.Keys.IllegalParameterValueClass_3,
+                                name, valueClass, value.getClass()), name, value);
+                    }
+                    /*
+                     * From this point we will perform the actual unit conversion. The value may be either
+                     * a Number instance, or an array of numbers (typically an array of type double[]). In
+                     * the array case, we will store the converted values in a new array of the same type.
+                     */
                     try {
                         converter = unit.getConverterToAny(def);
                     } catch (ConversionException e) {
                         throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnits_2, unit, def), e);
                     }
-                    Number n = (Number) value; // Given value.
-                    n = converter.convert(n.doubleValue()); // Value in units that we can compare.
-                    try {
-                        convertedValue = Numbers.cast(n, (Class<? extends Number>) type);
-                    } catch (IllegalArgumentException e) {
-                        throw new InvalidParameterValueException(e.getLocalizedMessage(), getName(descriptor), value);
+                    Class<?> componentType = valueClass.getComponentType();
+                    if (componentType == null) {
+                        /*
+                         * Usual case where the value is not an array. Convert the value directly.
+                         * Note that the value can only be a number because the unit is associated
+                         * to MeasurementRange, which accepts only numbers.
+                         */
+                        Number n = converter.convert(((Number) value).doubleValue());
+                        try {
+                            convertedValue = Numbers.cast(n, (Class<? extends Number>) valueClass);
+                        } catch (IllegalArgumentException e) {
+                            throw new InvalidParameterValueException(e.getLocalizedMessage(), getName(descriptor), value);
+                        }
+                    } else {
+                        /*
+                         * The value is an array. Creates a new array and store the converted values
+                         * using Array reflection.
+                         */
+                        final int length = Array.getLength(value);
+                        convertedValue = Array.newInstance(componentType, length);
+                        componentType = Numbers.primitiveToWrapper(componentType);
+                        for (int i=0; i<length; i++) {
+                            Number n = (Number) Array.get(value, i);
+                            n = converter.convert(n.doubleValue()); // Value in units that we can compare.
+                            try {
+                                n = Numbers.cast(n, (Class<? extends Number>) componentType);
+                            } catch (IllegalArgumentException e) {
+                                throw new InvalidParameterValueException(e.getLocalizedMessage(),
+                                        getName(descriptor) + '[' + i + ']', value);
+                            }
+                            Array.set(convertedValue, i, n);
+                        }
                     }
                 }
             }
         }
-        if (convertedValue == null) {
-            return descriptor.getDefaultValue();
-        }
-        final Comparable<T> minimum = descriptor.getMinimumValue();
-        final Comparable<T> maximum = descriptor.getMaximumValue();
-        final Verifier error = ensureValidValue(type, descriptor.getValidValues(), minimum, maximum, convertedValue);
-        if (error != null) {
-            final String name = getName(descriptor);
-            throw new InvalidParameterValueException(error.message(null, name, value), name, value);
-        }
         /*
-         * Passed every tests - the value is valid.
-         * Really returns the original value, not the converted one, because we store the given
-         * unit as well. Conversions will be applied on the fly by the getter method if needed.
+         * At this point the user's value has been fully converted to the unit of measurement specified
+         * by the ParameterDescriptor. Now compares the converted value to the restricting given by the
+         * descriptor (set of valid values and range of value domain).
          */
-        return (T) value;
+        if (convertedValue != null) {
+            final Verifier error;
+            final Set<T> validValues = descriptor.getValidValues();
+            if (descriptor instanceof DefaultParameterDescriptor<?>) {
+                error = ensureValidValue(valueClass, validValues,
+                        ((DefaultParameterDescriptor<?>) descriptor).getValueDomain(), convertedValue);
+            } else {
+                error = ensureValidValue(valueClass, validValues,
+                        descriptor.getMinimumValue(), descriptor.getMaximumValue(), convertedValue);
+            }
+            if (error != null) {
+                error.convertRange(converter);
+                final String name = getName(descriptor);
+                throw new InvalidParameterValueException(error.message(null, name, value), name, value);
+            }
+        }
+        return (T) convertedValue;
     }
 
     /**
@@ -154,31 +204,102 @@ final class Verifier {
      *        This is not necessarily the user-provided value.
      */
     @SuppressWarnings("unchecked")
-    static <T> Verifier ensureValidValue(final Class<T> type, final Set<T> validValues,
+    static <T> Verifier ensureValidValue(final Class<T> valueClass, final Set<T> validValues,
+            final Range<?> valueDomain, final Object convertedValue)
+    {
+        final Verifier verifier = ensureValidValue(valueClass, validValues, null, null, convertedValue);
+        if (verifier == null && valueDomain != null) {
+            if (!valueClass.isArray()) {
+                /*
+                 * Following assertion should never fail with DefaultParameterDescriptor instances.
+                 * It could fail if the user overrides DefaultParameterDescriptor.getValueDomain()
+                 * in a way that break the method contract.
+                 */
+                assert valueDomain.getElementType() == valueClass : valueDomain;
+                if (!((Range) valueDomain).contains((Comparable<?>) convertedValue)) {
+                    return new Verifier(Errors.Keys.ValueOutOfRange_4, true, null,
+                            valueDomain.getMinValue(), valueDomain.getMaxValue(), convertedValue);
+                }
+            } else {
+                /*
+                 * Following assertion should never fail under the same condition than above.
+                 */
+                assert valueDomain.getElementType() == Numbers.primitiveToWrapper(valueClass.getComponentType()) : valueDomain;
+                final int length = Array.getLength(convertedValue);
+                for (int i=0; i<length; i++) {
+                    final Object e = Array.get(convertedValue, i);
+                    if (!((Range) valueDomain).contains((Comparable<?>) e)) {
+                        return new Verifier(Errors.Keys.ValueOutOfRange_4, true, i,
+                                valueDomain.getMinValue(), valueDomain.getMaxValue(), e);
+                    }
+                }
+            }
+        }
+        return verifier;
+    }
+
+    /**
+     * Same as {@link #ensureValidValue(Class, Set, Range, Object)}, used as a fallback when
+     * the descriptor is not an instance of {@link DefaultParameterDescriptor}.
+     *
+     * <div class="note"><b>Implementation note:</b>
+     * At the difference of {@code ensureValidValue(…, Range, …)}, this method does not need to verify array elements
+     * because the type returned by {@link ParameterDescriptor#getMinimumValue()} and {@code getMaximumValue()}
+     * methods (namely {@code Comparable<T>}) does not allow usage with arrays.</div>
+     *
+     * @param convertedValue The value <em>converted to the units specified by the descriptor</em>.
+     *        This is not necessarily the user-provided value.
+     */
+    @SuppressWarnings("unchecked")
+    private static <T> Verifier ensureValidValue(final Class<T> valueClass, final Set<T> validValues,
             final Comparable<T> minimum, final Comparable<T> maximum, final Object convertedValue)
     {
-        if (!type.isInstance(convertedValue)) {
-            return new Verifier(Errors.Keys.IllegalParameterValueClass_3, false, null, type, convertedValue.getClass());
+        if (!valueClass.isInstance(convertedValue)) {
+            return new Verifier(Errors.Keys.IllegalParameterValueClass_3, false, null, valueClass, convertedValue.getClass());
+        }
+        if (validValues != null && !validValues.contains(convertedValue)) {
+            return new Verifier(Errors.Keys.IllegalParameterValue_2, true, null, convertedValue);
         }
         if ((minimum != null && minimum.compareTo((T) convertedValue) > 0) ||
             (maximum != null && maximum.compareTo((T) convertedValue) < 0))
         {
             return new Verifier(Errors.Keys.ValueOutOfRange_4, true, null, minimum, maximum, convertedValue);
         }
-        if (validValues != null && !validValues.contains(convertedValue)) {
-            return new Verifier(Errors.Keys.IllegalParameterValue_2, true, null, convertedValue);
-        }
         return null;
     }
 
     /**
+     * Converts the information about an "value out of range" error. The range in the error message will be formatted
+     * in the unit given by the user, which is not necessarily the same than the unit of the parameter descriptor.
+     *
+     * @param converter The conversion from user unit to descriptor unit, or {@code null} if none. This method
+     *        uses the inverse of that conversion for converting the given minimum and maximum values.
+     */
+    private void convertRange(UnitConverter converter) {
+        if (converter != null && errorKey == Errors.Keys.ValueOutOfRange_4) {
+            converter = converter.inverse();
+            Object minimumValue = arguments[1];
+            Object maximumValue = arguments[2];
+            minimumValue = (minimumValue != null) ? converter.convert(((Number) minimumValue).doubleValue()) : "-∞";
+            maximumValue = (maximumValue != null) ? converter.convert(((Number) maximumValue).doubleValue()) :  "∞";
+            arguments[1] = minimumValue;
+            arguments[2] = maximumValue;
+        }
+    }
+
+    /**
      * Returns an error message for the error detected by
-     * {@link #ensureValidValue(Class, Set, Comparable, Comparable, Object)}.
+     * {@link #ensureValidValue(Class, Set, Range, Object)}.
      *
      * @param name  The parameter name.
      * @param value The user-supplied value (not necessarily equals to the converted value).
      */
-    String message(final Map<?,?> properties, final String name, final Object value) {
+    String message(final Map<?,?> properties, String name, Object value) {
+        final Object index = arguments[0];
+        if (index != null) {
+            name = name + '[' + index + ']';
+            value = Array.get(value, (Integer) index);
+        }
         arguments[0] = name;
         if (needsValue) {
             arguments[arguments.length - 1] = value;

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/AbstractIdentifiedObject.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -296,6 +296,10 @@ public class AbstractIdentifiedObject ex
         // -------------------------------------
         Object value = properties.get(NAME_KEY);
         if (value == null || value instanceof String) {
+            if (value == null && properties.get(ReferenceIdentifier.CODE_KEY) == null) {
+                throw new IllegalArgumentException(Errors.getResources(properties)
+                        .getString(Errors.Keys.MissingValueForProperty_1, NAME_KEY));
+            }
             name = new NamedIdentifier(PropertiesConverter.convert(properties));
         } else if (value instanceof ReferenceIdentifier) {
             name = (ReferenceIdentifier) value;
@@ -362,7 +366,7 @@ public class AbstractIdentifiedObject ex
 
     /**
      * Returns a SIS identified object implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable actions in the following choices:
+     * This method performs the first applicable action in the following choices:
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>
@@ -371,8 +375,10 @@ public class AbstractIdentifiedObject ex
      *       {@link org.opengis.referencing.cs.CoordinateSystem},
      *       {@link org.opengis.referencing.cs.CoordinateSystemAxis},
      *       {@link org.opengis.referencing.datum.Datum},
-     *       {@link org.opengis.referencing.datum.Ellipsoid} or
+     *       {@link org.opengis.referencing.datum.Ellipsoid},
      *       {@link org.opengis.referencing.datum.PrimeMeridian},
+     *       {@link org.opengis.parameter.ParameterDescriptor} or
+     *       {@link org.opengis.parameter.ParameterDescriptorGroup},
      *       then this method delegates to the {@code castOrCopy(…)} method of the corresponding SIS subclass.
      *       Note that if the given object implements more than one of the above-cited interfaces,
      *       then the {@code castOrCopy(…)} method to be used is unspecified.</li>

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/NamedIdentifier.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -32,6 +32,7 @@ import org.opengis.metadata.citation.Cit
 import org.opengis.metadata.Identifier;
 import org.opengis.referencing.ReferenceIdentifier;
 import org.opengis.parameter.InvalidParameterValueException;
+import org.apache.sis.internal.referencing.NameToIdentifier;
 import org.apache.sis.internal.system.DefaultFactories;
 import org.apache.sis.metadata.iso.citation.Citations;
 import org.apache.sis.metadata.iso.ImmutableIdentifier;
@@ -58,13 +59,23 @@ import org.apache.sis.internal.jdk7.Obje
  * {@linkplain AbstractIdentifiedObject#getAlias() aliases} and have those names used in contexts
  * where {@code ReferenceIdentifier} instances are required, like GML marshalling time.
  *
- * {@section Name inference}
- * The generic name will be inferred from {@code ReferenceIdentifier} attributes.
- * More specifically, a {@link ScopedName} will be created using the shortest authority's
- * {@linkplain Citation#getAlternateTitles() alternate titles} (or the {@linkplain Citation#getTitle() main title}
- * if there is no alternate titles) as the {@linkplain ScopedName#scope() scope}, and the {@linkplain #getCode() code}
- * as the name {@linkplain ScopedName#tip() tip}. Note that according ISO 19115, citation alternate titles often
- * contains abbreviation (for example "DCW" as an alternative title for "<cite>Digital Chart of the World</cite>").
+ * {@section Name ↔ Identifier mapping}
+ * The {@code GenericName} attributes will be inferred from {@code ReferenceIdentifier} attributes as below:
+ *
+ * <ul>
+ *   <li><b>{@linkplain #tip() Tip}:</b> derived from the identifier {@linkplain #getCode() code}.</li>
+ *   <li><b>{@linkplain #head() Head}:</b> derived from the identifier {@linkplain #getCodeSpace() code space}.</li>
+ *   <li><b>{@linkplain #scope() Scope}:</b> derived from the shortest {@linkplain #getAuthority() authority}'s
+ *     {@linkplain Citation#getAlternateTitles() alternate titles}, or the {@linkplain Citation#getTitle() main title}
+ *     if there is no alternate titles. This policy exploits the ISO 19115 comment saying that citation alternate titles
+ *     often contain abbreviation (for example "DCW" as an alternative title for "<cite>Digital Chart of the World</cite>").</li>
+ * </ul>
+ *
+ * <div class="note"><b>Example:</b>
+ * If the identifier attributes are {@code authority} = {@link Citations#OGP}, {@code codeSpace} = {@code "EPSG"}
+ * and {@code code} = {@code "4326"}, then the name attributes will be {@code scope} = {@code "OGP"},
+ * {@code head} = {@code "EPSG"}, {@code tip} = {@code "4326"} and {@link #toString()} = {@code "EPSG:4326"}.
+ * Note that the scope does not appear in the string representation of names.</div>
  *
  *
  * {@section Immutability and thread safety}
@@ -84,7 +95,7 @@ public class NamedIdentifier extends Imm
     /**
      * Serial number for inter-operability with different versions.
      */
-    private static final long serialVersionUID = 8474731565582874497L;
+    private static final long serialVersionUID = -3982456534858346939L;
 
     /**
      * A pool of {@link NameSpace} values for given {@link InternationalString}.
@@ -93,8 +104,8 @@ public class NamedIdentifier extends Imm
             new WeakValueHashMap<CharSequence,NameSpace>(CharSequence.class);
 
     /**
-     * The name of this identifier as a generic name. If {@code null}, will be constructed
-     * only when first needed.
+     * The name of this identifier as a generic name.
+     * If {@code null}, will be constructed only when first needed.
      */
     private transient GenericName name;
 
@@ -102,7 +113,7 @@ public class NamedIdentifier extends Imm
      * {@code true} if {@link #name} has been given explicitly by the user.
      * Consider this field as final - it is not only for constructors convenience.
      */
-    private boolean isNameSupplied;
+    private transient boolean isNameSupplied;
 
     /**
      * Creates a new identifier from the specified one. This is a copy constructor
@@ -110,7 +121,7 @@ public class NamedIdentifier extends Imm
      * available) from the given identifier.
      *
      * <p>If the given identifier implements the {@link GenericName} interface, then calls to
-     * {@link #tip()}, {@link #head()}, {@link #scope()} and similar methods will delegates
+     * {@link #tip()}, {@link #head()}, {@link #scope()} and similar methods will delegate
      * to that name.</p>
      *
      * @param identifier The identifier to copy.
@@ -124,6 +135,19 @@ public class NamedIdentifier extends Imm
     }
 
     /**
+     * Creates a new identifier from the specified name. This constructor infers the identifier attributes
+     * (code, codespace and authority) from the given name. Calls to name-related methods like {@link #tip()},
+     * {@link #head()} and {@link #scope()} will delegate to the given name.
+     *
+     * @param name The name to wrap.
+     */
+    public NamedIdentifier(final GenericName name) {
+        super(name instanceof ReferenceIdentifier ? (ReferenceIdentifier) name : new NameToIdentifier(name));
+        this.name = name;
+        isNameSupplied = true;
+    }
+
+    /**
      * Constructs an identifier from the given properties. The content of the properties map is used as
      * described in the {@linkplain ImmutableIdentifier#ImmutableIdentifier(Map) super-class constructor}.
      *
@@ -216,25 +240,23 @@ public class NamedIdentifier extends Imm
      */
     private GenericName createName(final Citation authority, final CharSequence code) {
         final NameFactory factory = DefaultFactories.NAMES;
-        if (authority == null) {
-            return factory.createLocalName(null, code);
+        final String title = Citations.getIdentifier(authority); // Whitespaces trimed by Citations.
+        NameSpace scope = null;
+        if (title != null) {
+            synchronized (SCOPES) {
+                scope = SCOPES.get(title);
+                if (scope == null) {
+                    scope = factory.createNameSpace(factory.createLocalName(null, title), null);
+                    SCOPES.put(title, scope);
+                }
+            }
         }
-        final String title;
         final String codeSpace = super.getCodeSpace();
         if (codeSpace != null) {
-            title = codeSpace; // Whitespaces trimed by constructor.
+            return factory.createGenericName(scope, codeSpace, code);
         } else {
-            title = Citations.getIdentifier(authority); // Whitespaces trimed by Citations.
-        }
-        NameSpace scope;
-        synchronized (SCOPES) {
-            scope = SCOPES.get(title);
-            if (scope == null) {
-                scope = factory.createNameSpace(factory.createLocalName(null, title), null);
-                SCOPES.put(title, scope);
-            }
+            return factory.createLocalName(scope, code);
         }
-        return factory.createLocalName(scope, code).toFullyQualifiedName();
     }
 
     /**
@@ -266,10 +288,10 @@ public class NamedIdentifier extends Imm
 
     /**
      * Returns the scope (name space) in which this name is local.
-     * By default, this is the same value than {@link #getCodeSpace()} provided as a name space.
+     * By default, this is the same value than the {@link #getAuthority() authority} provided as a name space.
      *
      * @see #head()
-     * @see #getCodeSpace()
+     * @see #getAuthority()
      *
      * @return The scope of this name.
      */
@@ -340,7 +362,7 @@ public class NamedIdentifier extends Imm
     /**
      * Returns a string representation of this generic name. This string representation
      * is local-independent. It contains all elements listed by {@link #getParsedNames()}
-     * separated by an arbitrary character (usually {@code :} or {@code /}).
+     * separated by a namespace-dependent character (usually {@code :} or {@code /}).
      *
      * @return A local-independent string representation of this generic name.
      *
@@ -403,9 +425,7 @@ public class NamedIdentifier extends Imm
      */
     private void writeObject(final ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
-        if (isNameSupplied) {
-            out.writeObject(name);
-        }
+        out.writeObject(isNameSupplied ? name : null);
     }
 
     /**
@@ -414,8 +434,7 @@ public class NamedIdentifier extends Imm
      */
     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
         in.defaultReadObject();
-        if (isNameSupplied) {
-            name = (GenericName) in.readObject();
-        }
+        name = (GenericName) in.readObject();
+        isNameSupplied = (name != null);
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/SubTypes.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/SubTypes.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/SubTypes.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/SubTypes.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -23,12 +23,16 @@ import org.opengis.referencing.crs.Coord
 import org.opengis.referencing.datum.Datum;
 import org.opengis.referencing.datum.Ellipsoid;
 import org.opengis.referencing.datum.PrimeMeridian;
+import org.opengis.parameter.ParameterDescriptor;
+import org.opengis.parameter.ParameterDescriptorGroup;
 import org.apache.sis.referencing.crs.AbstractCRS;
 import org.apache.sis.referencing.cs.AbstractCS;
 import org.apache.sis.referencing.cs.DefaultCoordinateSystemAxis;
 import org.apache.sis.referencing.datum.AbstractDatum;
 import org.apache.sis.referencing.datum.DefaultEllipsoid;
 import org.apache.sis.referencing.datum.DefaultPrimeMeridian;
+import org.apache.sis.parameter.DefaultParameterDescriptor;
+import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
 
 
 /**
@@ -77,6 +81,12 @@ final class SubTypes {
         if (object instanceof PrimeMeridian) {
             return DefaultPrimeMeridian.castOrCopy((PrimeMeridian) object);
         }
+        if (object instanceof ParameterDescriptor<?>) {
+            return DefaultParameterDescriptor.castOrCopy((ParameterDescriptor<?>) object);
+        }
+        if (object instanceof ParameterDescriptorGroup) {
+            return DefaultParameterDescriptorGroup.castOrCopy((ParameterDescriptorGroup) object);
+        }
         /*
          * Intentionally check for AbstractIdentifiedObject after the interfaces because user may have defined his own
          * subclass implementing the interface. If we were checking for AbstractIdentifiedObject before the interfaces,

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/crs/AbstractCRS.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -190,7 +190,7 @@ public class AbstractCRS extends Abstrac
 
     /**
      * Returns a SIS coordinate reference system implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable actions in the following choices:
+     * This method performs the first applicable action in the following choices:
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/cs/AbstractCS.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -233,7 +233,7 @@ public class AbstractCS extends Abstract
 
     /**
      * Returns a SIS coordinate system implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable actions in the following choices:
+     * This method performs the first applicable action in the following choices:
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/AbstractDatum.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -219,7 +219,7 @@ public class AbstractDatum extends Abstr
 
     /**
      * Returns a SIS datum implementation with the values of the given arbitrary implementation.
-     * This method performs the first applicable actions in the following choices:
+     * This method performs the first applicable action in the following choices:
      *
      * <ul>
      *   <li>If the given object is {@code null}, then this method returns {@code null}.</li>

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java?rev=1576977&r1=1576976&r2=1576977&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/DefaultPrimeMeridian.java [UTF-8] Wed Mar 12 22:52:24 2014
@@ -342,7 +342,10 @@ public class DefaultPrimeMeridian extend
         super.formatTo(formatter);
         final Convention convention = formatter.getConvention();
         final boolean isWKT1 = convention.majorVersion() == 1;
-        final Unit<Angle> targetUnit = formatter.toContextualUnit(NonSI.DEGREE_ANGLE);
+        Unit<Angle> targetUnit = formatter.toContextualUnit(NonSI.DEGREE_ANGLE);
+        if (targetUnit == null) {
+            targetUnit = NonSI.DEGREE_ANGLE;
+        }
         formatter.append(isWKT1 ? getGreenwichLongitude(targetUnit) : greenwichLongitude);
         if (isWKT1) {
             return "PrimeM";



Mime
View raw message