sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Fix a failure to build projected CRS from grid mapping information in netCDF file. It was due in part to netCDF reader stopping the parsing of map projection parameters too early, and in part to ParameterValue expecting numbers of type Double while the netCDF reader provided Floats. The DefaultParameterValue class has been extended for performing some type conversions when needed.
Date Mon, 27 May 2019 17:02:15 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 2e2057d  Fix a failure to build projected CRS from grid mapping information in netCDF
file. It was due in part to netCDF reader stopping the parsing of map projection parameters
too early, and in part to ParameterValue expecting numbers of type Double while the netCDF
reader provided Floats. The DefaultParameterValue class has been extended for performing some
type conversions when needed.
2e2057d is described below

commit 2e2057d3531c1f259b83d23663c85212f25f1f5d
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon May 27 18:57:29 2019 +0200

    Fix a failure to build projected CRS from grid mapping information in netCDF file.
    It was due in part to netCDF reader stopping the parsing of map projection parameters
too early,
    and in part to ParameterValue expecting numbers of type Double while the netCDF reader
provided Floats.
    The DefaultParameterValue class has been extended for performing some type conversions
when needed.
---
 .../sis/parameter/DefaultParameterValue.java       | 210 +++++++++++++--------
 .../java/org/apache/sis/parameter/Verifier.java    |  88 +++++----
 .../src/main/java/org/apache/sis/util/Classes.java |  43 ++---
 .../src/main/java/org/apache/sis/util/Numbers.java |  18 +-
 .../org/apache/sis/internal/netcdf/Convention.java |  25 ++-
 .../apache/sis/internal/netcdf/GridMapping.java    |   2 +-
 6 files changed, 232 insertions(+), 154 deletions(-)

diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
index 348311d..1fe269f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/DefaultParameterValue.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.parameter;
 
+import java.lang.reflect.Array;
 import java.util.Objects;
 import java.nio.file.Path;
 import java.io.Serializable;
@@ -47,6 +48,7 @@ import org.apache.sis.internal.metadata.MetadataUtilities;
 import org.apache.sis.internal.metadata.WKTKeywords;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.system.Loggers;
+import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ComparisonMode;
 import org.apache.sis.util.LenientComparable;
@@ -108,14 +110,14 @@ import static org.apache.sis.util.Utilities.deepEquals;
  * <ul>
  *   <li>All getter methods will invoke {@link #getValue()} and {@link #getUnit()}
(if needed),
  *       then perform their processing on the values returned by those methods.</li>
- *   <li>All setter methods delegates to the {@link #setValue(Object, Unit)} method.</li>
+ *   <li>All setter methods delegate to the {@link #setValue(Object, Unit)} method.</li>
  * </ul>
  *
  * Consequently, the above-cited methods provide single points that subclasses can override
  * for modifying the behavior of all getter and setter methods.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @param  <T>  the type of the value stored in this parameter.
  *
@@ -482,20 +484,12 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
     @Override
     public URI valueFile() throws IllegalStateException {
         final T value = getValue();
-        if (value instanceof URI) {
-            return (URI) value;
-        }
-        if (value instanceof File) {
-            return ((File) value).toURI();
-        }
-        if (value instanceof Path) {
-            return ((Path) value).toUri();
-        }
+        if (value instanceof URI)  return   (URI) value;
+        if (value instanceof File) return ((File) value).toURI();
+        if (value instanceof Path) return ((Path) value).toUri();
         Exception cause = null;
-        try {
-            if (value instanceof URL) {
-                return ((URL) value).toURI();
-            }
+        if (value instanceof URL) try {
+            return ((URL) value).toURI();
         } catch (URISyntaxException exception) {
             cause = exception;
         }
@@ -547,14 +541,36 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
     }
 
     /**
+     * Converts the given number to the expected type, with a special case for conversion
from float to double type.
+     * Widening conversions are aimed to be exact in base 10 instead than base 2. If {@code
expectedClass} is not a
+     * {@link Number} subtype, then this method does nothing. If the cast would result in
information lost, than
+     * this method returns the given value unchanged for allowing a more accurate error message
to happen later.
+     *
+     * @param  value          the value to cast (can be {@code null}).
+     * @param  expectedClass  the desired class as a wrapper class (not a primitive type).
+     * @return value converted to the desired class, or {@code value} if no cast is needed
or can be done.
+     */
+    @SuppressWarnings("unchecked")
+    private static Number cast(final Number value, final Class<?> expectedClass) {
+        if (expectedClass == Double.class && value instanceof Float) {
+            return DecimalFunctions.floatToDouble(value.floatValue());
+        } else if (Number.class.isAssignableFrom(expectedClass)) {
+            final Number n = Numbers.cast(value, (Class<? extends Number>) expectedClass);
+            if (Numerics.equals(n.doubleValue(), value.doubleValue())) {
+                return n;
+            }
+        }
+        return value;
+    }
+
+    /**
      * Sets the parameter value as an object. The object type is typically (but not limited
to) {@link Double},
      * {@code double[]}, {@link Integer}, {@code int[]}, {@link Boolean}, {@link String}
or {@link URI}.
      * If the given value is {@code null}, then this parameter is set to the
      * {@linkplain DefaultParameterDescriptor#getDefaultValue() default value}.
-     *
-     * <p>The default implementation delegates to {@link #setValue(Object, Unit)}.
-     * This implementation does not clone the given value. In particular, references to {@code
int[]}
-     * and {@code double[]} arrays are stored <cite>as-is</cite>.</p>
+     * If the given value is not an instance of the expected type, then this method may perform
automatically
+     * a type conversion (for example from {@link Float} to {@link Double} or from {@link
Path} to {@link URI})
+     * if such conversion can be done without information lost.
      *
      * @param  newValue  the parameter value, or {@code null} to restore the default.
      * @throws InvalidParameterValueException if the type of {@code value} is inappropriate
for this parameter,
@@ -568,19 +584,54 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
          * Try to convert the value only for a limited amount of types. In particular we
want to allow conversions
          * between java.io.File and java.nio.file.Path for easier transition between JDK6
and JDK7. We do not want
          * to allow too many conversions for reducing the risk of unexpected behavior.  If
we fail to convert, try
-         * to set the value anyway since the user may have redefined the setValue(Object,
Unit) method.
+         * to set the value anyway since the user may have redefined the `setValue(Object,
Unit)` method.
          */
-        if (isOrNeedFile(newValue)) try {
-            newValue = ObjectConverters.convert(newValue, descriptor.getValueClass());
-        } catch (UnconvertibleObjectException e) {
-            // Level.FINE (not WARNING) because this log duplicates the exception
-            // that 'setValue(Object, Unit)' may throw (with a better message).
-            Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
-                    DefaultParameterValue.class, "setValue", e);
+        if (newValue != null) {
+            final Class<?> expectedClass = descriptor.getValueClass();
+            if (!expectedClass.isInstance(newValue)) {
+                if (newValue instanceof Number) {
+                    newValue = cast((Number) newValue, expectedClass);
+                } else if (isOrNeedFile(newValue)) try {
+                    newValue = ObjectConverters.convert(newValue, expectedClass);
+                } catch (UnconvertibleObjectException e) {
+                    /*
+                     * Level.FINE (not WARNING) because this log duplicates the exception
+                     * that `setValue(Object, Unit)` may throw (with a better message).
+                     */
+                    Logging.recoverableException(Logging.getLogger(Loggers.COORDINATE_OPERATION),
+                            DefaultParameterValue.class, "setValue", e);
+                } else {
+                    /*
+                     * If the given value is an array, verify if array elements need to be
converted
+                     * for example from `float` to `double`. This is a "all or nothing" operation:
+                     * if at least one element can not be converted, then the whole array
is unchanged.
+                     */
+                    Class<?> componentType = expectedClass.getComponentType();
+convert:            if (componentType != null) {
+                        final Object array = newValue.getClass().isArray() ? newValue : new
Object[] {newValue};
+                        final int length = Array.getLength(array);
+                        if (length > 0) {
+                            final Object copy = Array.newInstance(componentType, length);
+                            componentType = Numbers.primitiveToWrapper(componentType);
+                            for (int i=0; i<length; i++) {
+                                Object element = Array.get(array, i);
+                                if (element != null) {
+                                    if (!(element instanceof Number)) break convert;
+                                    element = cast((Number) element, componentType);
+                                    if (!(componentType.isInstance(element))) break convert;
+                                    Array.set(copy, i, element);
+                                }
+                            }
+                            newValue = copy;
+                        }
+                    }
+                }
+            }
         }
         /*
-         * Use 'unit' instead than 'getUnit()' despite class Javadoc claims because units
are not expected
-         * to be involved in this method. We just want the current unit setting to be unchanged.
+         * Code below uses `unit` instead than `getUnit()` despite class Javadoc claim because
units are not expected
+         * to be involved in this method. We access this field only as a matter of principle,
for making sure that no
+         * property other than the value is altered by this method call.
          */
         setValue(newValue, unit);
     }
@@ -588,8 +639,6 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
     /**
      * Sets the parameter value as a boolean.
      *
-     * <p>The default implementation delegates to {@link #setValue(Object, Unit)}.</p>
-     *
      * @param  newValue  the parameter value.
      * @throws InvalidParameterValueException if the boolean type is inappropriate for this
parameter.
      *
@@ -597,16 +646,18 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
      */
     @Override
     public void setValue(final boolean newValue) throws InvalidParameterValueException {
-        // Use 'unit' instead than 'getUnit()' despite class Javadoc claims because units
are not expected
-        // to be involved in this method. We just want the current unit setting to be unchanged.
         setValue(newValue, unit);
+        /*
+         * Above code used `unit` instead than `getUnit()` despite class Javadoc claim because
units are not expected
+         * to be involved in this method. We access this field only as a matter of principle,
for making sure that no
+         * property other than the value is altered by this method call.
+         */
     }
 
     /**
-     * Sets the parameter value as an integer.
-     *
-     * <p>The default implementation wraps the given integer in an object of the type
specified by the
-     * {@linkplain #getDescriptor() descriptor}, then delegates to {@link #setValue(Object,
Unit)}.</p>
+     * Sets the parameter value as an integer. This method automatically wraps the given
integer
+     * in an object of the type specified by the {@linkplain #getDescriptor() descriptor}
if that
+     * conversion can be done without information lost.
      *
      * @param  newValue  the parameter value.
      * @throws InvalidParameterValueException if the integer type is inappropriate for this
parameter,
@@ -617,21 +668,20 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
     @Override
     public void setValue(final int newValue) throws InvalidParameterValueException {
         Number n = newValue;
-        final Class<T> valueClass = descriptor.getValueClass();
-        if (Number.class.isAssignableFrom(valueClass)) {
-            @SuppressWarnings("unchecked")
-            final Number c = Numbers.cast(newValue, (Class<? extends Number>) valueClass);
-            if (c.intValue() == newValue) {
-                n = c;
-            }
-        }
+        Number c = cast(n, descriptor.getValueClass());
+        if (c.intValue() == newValue) n = c;
         setValue(n, unit);
-        // Use 'unit' instead than 'getUnit()' despite class Javadoc claims because units
are not expected
-        // to be involved in this method. We just want the current unit setting to be unchanged.
+        /*
+         * Above code used `unit` instead than `getUnit()` despite class Javadoc claim because
units are not expected
+         * to be involved in this method. We access this field only as a matter of principle,
for making sure that no
+         * property other than the value is altered by this method call.
+         */
     }
 
     /**
      * Wraps the given value in a type compatible with the expected value class, if possible.
+     * If the value can not be wrapped, then this method fallbacks on the {@link Double}
class
+     * consistently with this method being invoked only by {@code setValue(double, …)}
methods.
      *
      * @throws IllegalArgumentException if the given value can not be converted to the given
type.
      */
@@ -645,10 +695,8 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
     }
 
     /**
-     * Sets the parameter value as a floating point. The unit, if any, stay unchanged.
-     *
-     * <p>The default implementation wraps the given number in an object of the type
specified by the
-     * {@linkplain #getDescriptor() descriptor}, then delegates to {@link #setValue(Object,
Unit)}.</p>
+     * Sets the parameter value as a floating point. The unit, if any, stay unchanged. This
method automatically
+     * wraps the given number in an object of the type specified by the {@linkplain #getDescriptor()
descriptor}.
      *
      * @param  newValue  the parameter value.
      * @throws InvalidParameterValueException if the floating point type is inappropriate
for this parameter,
@@ -659,20 +707,24 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
      */
     @Override
     public void setValue(final double newValue) throws InvalidParameterValueException {
+        final Number n;
         try {
-            // Use 'unit' instead than 'getUnit()' despite class Javadoc claims because units
are not expected
-            // to be involved in this method. We just want the current unit setting to be
unchanged.
-            setValue(wrap(newValue, descriptor.getValueClass()), unit);
+            n = wrap(newValue, descriptor.getValueClass());
         } catch (IllegalArgumentException e) {
-            throw new InvalidParameterValueException(e.getLocalizedMessage(), Verifier.getDisplayName(descriptor),
newValue);
+            throw (InvalidParameterValueException) new InvalidParameterValueException(
+                    e.getLocalizedMessage(), Verifier.getDisplayName(descriptor), newValue).initCause(e);
         }
+        setValue(n, unit);
+        /*
+         * Above code used `unit` instead than `getUnit()` despite class Javadoc claim because
units are not expected
+         * to be involved in this method. We access this field only as a matter of principle,
for making sure that no
+         * property other than the value is altered by this method call.
+         */
     }
 
     /**
-     * Sets the parameter value as a floating point and its associated unit.
-     *
-     * <p>The default implementation wraps the given number in an object of the type
specified by the
-     * {@linkplain #getDescriptor() descriptor}, then delegates to {@link #setValue(Object,
Unit)}.</p>
+     * Sets the parameter value as a floating point and its associated unit. This method
automatically wraps
+     * the given number in an object of the type specified by the {@linkplain #getDescriptor()
descriptor}.
      *
      * @param  newValue  the parameter value.
      * @param  unit      the unit for the specified value.
@@ -684,21 +736,19 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
      */
     @Override
     public void setValue(final double newValue, final Unit<?> unit) throws InvalidParameterValueException
{
+        final Number n;
         try {
-            setValue(wrap(newValue, descriptor.getValueClass()), unit);
-        } catch (InvalidParameterValueException e) {
-            throw e;        // Need to be thrown explicitly because it is a subclass of IllegalArgumentException.
+            n = wrap(newValue, descriptor.getValueClass());
         } catch (IllegalArgumentException e) {
             throw (InvalidParameterValueException) new InvalidParameterValueException(
                     e.getLocalizedMessage(), Verifier.getDisplayName(descriptor), newValue).initCause(e);
         }
+        setValue(n, unit);
     }
 
     /**
      * Sets the parameter value as an array of floating point and their associated unit.
      *
-     * <p>The default implementation delegates to {@link #setValue(Object, Unit)}.</p>
-     *
      * @param  newValues  the parameter values.
      * @param  unit       the unit for the specified value.
      * @throws InvalidParameterValueException if the floating point array type is inappropriate
for this parameter,
@@ -713,9 +763,14 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
      * Sets the parameter value and its associated unit.
      * If the given value is {@code null}, then this parameter is set to the
      * {@linkplain DefaultParameterDescriptor#getDefaultValue() default value}.
+     * Otherwise the given value shall be an instance of the class expected by the {@linkplain
#getDescriptor() descriptor}.
      *
-     * <p>Current implementation does not clone the given value. In particular, references
to
-     * {@code int[]} and {@code double[]} arrays are stored <cite>as-is</cite>.</p>
+     * <ul>
+     *   <li>This method does not perform any type conversion. Type conversion, if
desired, should be
+     *       applied by the public {@code setValue(…)} methods before to invoke this protected
method.</li>
+     *   <li>This method does not clone the given value. In particular, references
to {@code int[]} and
+     *       {@code double[]} arrays are stored <cite>as-is</cite>.</li>
+     * </ul>
      *
      * <div class="section">Implementation note for subclasses</div>
      * This method is invoked by all setter methods in this class, thus providing a single
point that
@@ -774,8 +829,7 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
     @Override
     public boolean equals(final Object object, final ComparisonMode mode) {
         if (object == this) {
-            // Slight optimization
-            return true;
+            return true;            // Slight optimization
         }
         if (object != null) {
             if (mode == ComparisonMode.STRICT) {
@@ -911,7 +965,7 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
      *     PARAMETER[“scale_factor”, 0.99987742],
      *     PARAMETER[“false_easting”, 600.0],           // In kilometres
      *     PARAMETER[“false_northing”, 2200.0],         // In kilometres
-     *     UNIT[“kilometre”, 1000]]                            // Unit for all lengths
+     *     UNIT[“kilometre”, 1000]]                     // Unit for all lengths
      * }
      *
      * <p><b>WKT 2:</b></p>
@@ -979,13 +1033,17 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
             try {
                 value = doubleValue(unit);
             } catch (IllegalStateException exception) {
-                // May happen if a parameter is mandatory (e.g. "semi-major")
-                // but no value has been set for this parameter.
+                /*
+                 * May happen if a parameter is mandatory (e.g. "semi-major")
+                 * but no value has been set for this parameter.
+                 */
                 if (descriptor != null) {
                     formatter.setInvalidWKT(descriptor, exception);
                 } else {
-                    // Null descriptor should be illegal but may happen after unmarshalling
of invalid GML.
-                    // We make this WKT formatting robust since it is used by 'toString()'
implementation.
+                    /*
+                     * Null descriptor should be illegal but may happen after unmarshalling
of invalid GML.
+                     * We make this WKT formatting robust since it is used by 'toString()'
implementation.
+                     */
                     formatter.setInvalidWKT(DefaultParameterValue.class, exception);
                 }
                 value = Double.NaN;
@@ -1013,8 +1071,10 @@ public class DefaultParameterValue<T> extends FormattableObject
implements Param
                     if (descriptor != null) {
                         formatter.setInvalidWKT(descriptor, null);
                     } else {
-                        // Null descriptor should be illegal but may happen after unmarshalling
of invalid GML.
-                        // We make this WKT formatting robust since it is used by 'toString()'
implementation.
+                        /*
+                         * Null descriptor should be illegal but may happen after unmarshalling
of invalid GML.
+                         * We make this WKT formatting robust since it is used by 'toString()'
implementation.
+                         */
                         formatter.setInvalidWKT(DefaultParameterValue.class, null);
                     }
                 }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java
index 94b797a..cde64bc 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/parameter/Verifier.java
@@ -47,7 +47,7 @@ import org.apache.sis.util.resources.Vocabulary;
  * In such case, the error message is given by {@link #message(Map, String, Object)}.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.4
  * @module
  */
@@ -99,6 +99,9 @@ final class Verifier {
      * {@linkplain ParameterDescriptor#getValidValues() set of valid values}.
      * If the value fails any of those tests, then an exception is thrown.
      *
+     * <p>This method does not attempt to convert the given value (for example from
{@link Float} to {@link Double}) because
+     * such conversions should be done by the caller if desired. See {@link DefaultParameterValue#setValue(Object,
Unit)}.</p>
+     *
      * @param  <T>         the type of parameter value. The given {@code value} should
typically be an instance of this class.
      *                     This is not required by this method signature but is checked by
this method implementation.
      * @param  descriptor  the parameter descriptor to check against.
@@ -111,11 +114,14 @@ final class Verifier {
     static <T> T ensureValidValue(final ParameterDescriptor<T> descriptor, final
Object value, final Unit<?> unit)
             throws InvalidParameterValueException
     {
-        final Class<T> valueClass = descriptor.getValueClass();
+        final Class<T> expectedClass = 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. The first part of this block verifies
-         * the validity of the unit argument, so we execute it even if 'value' is null.
+         * Convert the given value to the units used by the parameter descriptor.
+         * Notes:
+         *
+         *   1) We need to perform this conversion before to verify if the given value is
inside the bounds.
+         *   2) This conversion may change the type (e.g. from java.lang.Float to Double)
as a side effect.
+         *   3) We execute this code even if value is null because it contains a check of
unit validity.
          */
         UnitConverter converter = null;
         Object convertedValue = value;
@@ -134,58 +140,58 @@ final class Verifier {
                     throw new IllegalArgumentException(Errors.format(expectedID, unit));
                 }
                 /*
-                 * 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.
+                 * Verify the value type before to perform unit conversion. This will indirectly
verifies that the value
+                 * is an instance of `java.lang.Number` or an array of numbers because non-null
units are associated to
+                 * `MeasurementRange` in SIS implementation, which accepts only numeric values.
                  */
                 if (value != null) {
-                    if (!valueClass.isInstance(value)) {
+                    if (!expectedClass.isInstance(value)) {
                         final String name = getDisplayName(descriptor);
                         throw new InvalidParameterValueException(
                                 Resources.format(Resources.Keys.IllegalParameterValueClass_3,
-                                name, valueClass, value.getClass()), name, value);
+                                name, expectedClass, 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.
+                     * the array case, we will store the converted values in a new array
of expected type.
                      */
                     try {
                         converter = unit.getConverterToAny(def);
                     } catch (IncommensurableException e) {
                         throw new IllegalArgumentException(Errors.format(Errors.Keys.IncompatibleUnits_2,
unit, def), e);
                     }
-                    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, valueClass.asSubclass(Number.class));
-                        } catch (IllegalArgumentException e) {
-                            throw new InvalidParameterValueException(e.getLocalizedMessage(),
-                                    getDisplayName(descriptor), value);
-                        }
-                    } else {
-                        /*
-                         * The value is an array. Creates a new array and store the converted
values
-                         * using Array reflection.
-                         */
+                    final Class<?> componentType = expectedClass.getComponentType();
+                    if (componentType != null) {
                         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.
+                        if (length != 0) {
+                            final Class<? extends Number> numberType = Numbers.primitiveToWrapper(componentType).asSubclass(Number.class);
+                            convertedValue = Array.newInstance(componentType, length);
+                            int i = 0;
                             try {
-                                n = Numbers.cast(n, componentType.asSubclass(Number.class));
+                                do {
+                                    Number n = (Number) Array.get(value, i);
+                                    n = converter.convert(n);                       // Value
in units that we can compare.
+                                    n = Numbers.cast(n, numberType);
+                                    Array.set(convertedValue, i, n);
+                                } while (++i < length);
                             } catch (IllegalArgumentException e) {
-                                throw new InvalidParameterValueException(e.getLocalizedMessage(),
-                                        Strings.toIndexed(getDisplayName(descriptor), i),
value);
+                                throw (InvalidParameterValueException) new InvalidParameterValueException(e.getLocalizedMessage(),
+                                        Strings.toIndexed(getDisplayName(descriptor), i),
value).initCause(e);
                             }
-                            Array.set(convertedValue, i, n);
+                        }
+                    } else {
+                        /*
+                         * Usual case where the expected value is a singleton. A ClassCastException
below could be
+                         * a bug in our code logic since non-null units is allowed only with
numeric values in SIS
+                         * implementation. However the given descriptor could be a "foreigner"
implementation.
+                         */
+                        try {
+                            Number n = converter.convert((Number) value);
+                            convertedValue = Numbers.cast(n, expectedClass.asSubclass(Number.class));
+                        } catch (ClassCastException | IllegalArgumentException e) {
+                            throw (InvalidParameterValueException) new InvalidParameterValueException(
+                                    e.getLocalizedMessage(), getDisplayName(descriptor),
value).initCause(e);
                         }
                     }
                 }
@@ -200,10 +206,10 @@ final class Verifier {
             final Verifier error;
             final Set<T> validValues = descriptor.getValidValues();
             if (descriptor instanceof DefaultParameterDescriptor<?>) {
-                error = ensureValidValue(valueClass, validValues,
+                error = ensureValidValue(expectedClass, validValues,
                         ((DefaultParameterDescriptor<?>) descriptor).getValueDomain(),
convertedValue);
             } else {
-                error = ensureValidValue(valueClass, validValues,
+                error = ensureValidValue(expectedClass, validValues,
                         descriptor.getMinimumValue(), descriptor.getMaximumValue(), convertedValue);
             }
             /*
@@ -225,7 +231,7 @@ final class Verifier {
                 }
             }
         }
-        return valueClass.cast(convertedValue);
+        return expectedClass.cast(convertedValue);
     }
 
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java b/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java
index cf3e177..06a62bb 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Classes.java
@@ -84,43 +84,40 @@ public final class Classes extends Static {
 
     /**
      * Changes the array dimension by the given amount. The given class can be a primitive
type,
-     * a Java object, or an array of the above. If the given {@code dimension} is positive,
then
-     * the array dimension will be increased by that amount. For example a change of dimension
1
-     * will change a {@code int} class into {@code int[]}, and a {@code String[]} class into
-     * {@code String[][]}. A change of dimension 2 is like applying a change of dimension
1 two
-     * times.
+     * a Java object, or an array of the above. If the given {@code change} is positive,
then the
+     * array dimension will be increased by that amount. For example a change of +1 dimension
will
+     * change an {@code int} class into {@code int[]}, and a {@code String[]} class into
{@code String[][]}.
+     * A change of +2 dimensions is like applying two times a change of +1 dimension.
      *
-     * <p>The change of dimension can also be negative. For example a change of dimension
-1 will
+     * <p>The change of dimension can also be negative. For example a change of -1
dimension will
      * change a {@code String[]} class into a {@code String}. More specifically:</p>
      *
      * <ul>
      *   <li>If the given {@code element} is null, then this method returns {@code
null}.</li>
-     *   <li>Otherwise if the given {@code dimension} change is 0, then the given {@code
element}
-     *       is returned unchanged.</li>
-     *   <li>Otherwise if the given {@code dimension} change is negative, then
-     *       {@link Class#getComponentType()} is invoked {@code abs(dimension)} times.
-     *       The result is a {@code null} value if {@code abs(dimension)} is greater
-     *       than the array dimension.</li>
-     *   <li>Otherwise if {@code element} is {@link Void#TYPE}, then this method returns
-     *       {@code Void.TYPE} since arrays of {@code void} don't exist.</li>
-     *   <li>Otherwise this method returns a class that represents an array of the
given
-     *       class augmented by the given amount of dimensions.</li>
+     *   <li>Otherwise if the given {@code change} is 0, then the given {@code element}
is returned unchanged.</li>
+     *   <li>Otherwise if the given {@code change} is negative, then {@link Class#getComponentType()}
is invoked
+     *       {@code abs(change)} times. The result is a {@code null} value if {@code abs(change)}
is greater than
+     *       the array dimension.</li>
+     *   <li>Otherwise if {@code element} is {@link Void#TYPE}, then this method returns
{@code Void.TYPE}
+     *       since arrays of {@code void} do not exist.</li>
+     *   <li>Otherwise this method returns a class that represents an array of the
given class augmented by
+     *       the given amount of dimensions.</li>
      * </ul>
      *
-     * @param  element    the type of elements in the array.
-     * @param  dimension  the change of dimension, as a negative or positive number.
+     * @param  element  the type of elements in the array.
+     * @param  change   the change of dimension, as a negative or positive number.
      * @return the type of an array of the given element type augmented by the given
      *         number of dimensions (which may be negative), or {@code null}.
      */
-    public static Class<?> changeArrayDimension(Class<?> element, int dimension)
{
-        if (dimension != 0 && element != null) {
-            if (dimension < 0) {
+    public static Class<?> changeArrayDimension(Class<?> element, int change)
{
+        if (change != 0 && element != null) {
+            if (change < 0) {
                 do element = element.getComponentType();
-                while (element!=null && ++dimension != 0);
+                while (element!=null && ++change != 0);
             } else if (element != Void.TYPE) {
                 final StringBuilder buffer = new StringBuilder();
                 do buffer.insert(0, '[');
-                while (--dimension != 0);
+                while (--change != 0);
                 if (element.isPrimitive()) {
                     buffer.append(Numbers.getInternal(element));
                 } else if (element.isArray()) {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
index cb933b3..3030fae 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
@@ -150,7 +150,7 @@ public final class Numbers extends Static {
      * Returns {@code true} if the given {@code type} is a floating point type. The floating
point types
      * are {@link Float}, {@code float}, {@link Double}, {@code double} and {@link BigDecimal}.
      *
-     * @param  type  the type to test (may be {@code null}).
+     * @param  type  the primitive type or wrapper class to test (can be {@code null}).
      * @return {@code true} if {@code type} is one of the known types capable to represent
floating point numbers.
      *
      * @see #isInteger(Class)
@@ -165,7 +165,7 @@ public final class Numbers extends Static {
      * {@link Byte}, {@code byte}, {@link Short}, {@code short}, {@link Integer}, {@code
int},
      * {@link Long}, {@code long} and {@link BigInteger}.
      *
-     * @param  type  the type to test (may be {@code null}).
+     * @param  type  the primitive type or wrapper class to test (can be {@code null}).
      * @return {@code true} if {@code type} is an integer type.
      *
      * @see #isFloat(Class)
@@ -179,7 +179,7 @@ public final class Numbers extends Static {
      * Returns the number of bits used by primitive of the specified type.
      * The given type must be a primitive type or its wrapper class.
      *
-     * @param  type  the primitive type (may be {@code null}).
+     * @param  type  the primitive type (can be {@code null}).
      * @return the number of bits, or 0 if {@code type} is null.
      * @throws IllegalArgumentException if the given type is unknown.
      */
@@ -201,7 +201,7 @@ public final class Numbers extends Static {
      * Changes a primitive class to its wrapper (for example {@code int} to {@link Integer}).
      * If the specified class is not a primitive type, then it is returned unchanged.
      *
-     * @param  type  the primitive type (may be {@code null}).
+     * @param  type  the primitive type (can be {@code null}).
      * @return the type as a wrapper.
      *
      * @see #wrapperToPrimitive(Class)
@@ -215,7 +215,7 @@ public final class Numbers extends Static {
      * Changes a wrapper class to its primitive (for example {@link Integer} to {@code int}).
      * If the specified class is not a wrapper type, then it is returned unchanged.
      *
-     * @param  type  the wrapper type (may be {@code null}).
+     * @param  type  the wrapper type (can be {@code null}).
      * @return the type as a primitive.
      *
      * @see #primitiveToWrapper(Class)
@@ -545,7 +545,11 @@ public final class Numbers extends Static {
                 } else if (isInteger(number.getClass())) {
                     c = BigDecimal.valueOf(number.longValue());
                 } else {
-                    c = BigDecimal.valueOf(number.doubleValue());
+                    /*
+                     * Same implementation as BigDecimal.valueOf(double)
+                     * but better result if number is a Float instance.
+                     */
+                    c = new BigDecimal(number.toString());
                 }
                 return (N) c;
             }
@@ -753,7 +757,7 @@ public final class Numbers extends Static {
      * {@link #SHORT}, {@link #BYTE}, {@link #CHARACTER}, {@link #BOOLEAN}, or {@link #OTHER}
      * constants for the given type. This is a commodity for usage in {@code switch} statements.
      *
-     * @param  type  a type (usually either a primitive type or its wrapper).
+     * @param  type  a type (usually either a primitive type or its wrapper), or {@code null}.
      * @return the constant for the given type, or {@link #OTHER} if unknown.
      */
     public static byte getEnumConstant(final Class<?> type) {
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
index 96d32cf..0c59f8e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Convention.java
@@ -441,12 +441,8 @@ public class Convention {
         definition.put(CF.GRID_MAPPING_NAME, method);
         for (final String name : node.getAttributeNames()) {
             final String ln = name.toLowerCase(Locale.US);
-            final Object value;
-            if (ln.endsWith("_name")) {
-                value = node.getAttributeAsString(name);
-                if (value == null) continue;
-                break;
-            } else switch (ln) {
+            Object value;
+            switch (ln) {
                 case CF.GRID_MAPPING_NAME: continue;        // Already stored.
                 case TOWGS84: {
                     /*
@@ -470,14 +466,29 @@ public class Convention {
                     continue;
                 }
                 default: {
+                    if (ln.endsWith("_name")) {
+                        value = node.getAttributeAsString(name);
+                        if (value == null) continue;
+                    }
                     /*
                      * Assume that all map projection parameters in netCDF files are numbers
or array of numbers.
+                     * If the array contains float value, returns a `float[]` array for letting
the caller know
+                     * that it may need to use base 10 for conversion to `double[]` array.
                      */
                     final Object[] values = node.getAttributeValues(name, true);
                     switch (values.length) {
                         case 0:  continue;                       // Attribute not found or
not numeric.
                         case 1:  value = values[0]; break;       // This is the usual case.
-                        default: value = Vector.create(values, false).doubleValues(); break;
+                        default: {
+                            boolean isFloat = false;
+                            for (final Object e : values) {
+                                isFloat = (e instanceof Float);
+                                if (!isFloat) break;
+                            }
+                            final Vector v = Vector.create(values, false);
+                            value = isFloat ? v.floatValues() : v.doubleValues();
+                            break;
+                        }
                     }
                     break;
                 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
index f088b83..44e7282 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/GridMapping.java
@@ -208,7 +208,7 @@ final class GridMapping {
             for (final Map.Entry<String,Object> entry : definition.entrySet()) {
                 final String name  = entry.getKey();
                 final Object value = entry.getValue();
-                if (value instanceof Number || value instanceof double[]) try {
+                if (value instanceof Number || value instanceof double[] || value instanceof
float[]) try {
                     parameters.parameter(name).setValue(value);
                 } catch (IllegalArgumentException ex) {
                     warning(node, ex, Resources.Keys.CanNotSetProjectionParameter_5, node.decoder.getFilename(),


Mime
View raw message