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: Better error detection and more accurate error messages.
Date Wed, 07 Nov 2018 15:24:20 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 de60211  Better error detection and more accurate error messages.
de60211 is described below

commit de602119e0d84796d04f9ecea742a0a3f6c6628f
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Wed Nov 7 16:23:55 2018 +0100

    Better error detection and more accurate error messages.
---
 .../apache/sis/internal/netcdf/NamedElement.java   |  13 +-
 .../org/apache/sis/internal/netcdf/Resources.java  |  20 ++++
 .../sis/internal/netcdf/Resources.properties       |   4 +
 .../sis/internal/netcdf/Resources_fr.properties    |   4 +
 .../sis/internal/netcdf/impl/ChannelDecoder.java   |  25 ++--
 .../sis/internal/netcdf/impl/GridGeometryInfo.java |  18 ++-
 .../sis/internal/netcdf/impl/VariableInfo.java     | 131 ++++++++++++++-------
 .../sis/internal/netcdf/ucar/VariableWrapper.java  |   3 +-
 .../org/apache/sis/internal/storage/Resources.java |   2 +-
 .../sis/internal/storage/Resources.properties      |   2 +-
 .../sis/internal/storage/Resources_fr.properties   |   2 +-
 .../sis/internal/storage/io/DataTransfer.java      |   8 +-
 .../internal/storage/io/HyperRectangleReader.java  |  16 ++-
 .../org/apache/sis/internal/storage/io/Region.java |  40 +++++--
 .../sis/storage/DataStoreContentException.java     |   6 +
 .../org/apache/sis/storage/DataStoreException.java |   6 +
 16 files changed, 225 insertions(+), 75 deletions(-)

diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
index 21500d0..c11af8d 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/NamedElement.java
@@ -21,6 +21,7 @@ import java.util.AbstractMap;
 import java.util.Locale;
 import java.util.Map;
 import org.apache.sis.internal.util.CollectionsExt;
+import org.apache.sis.util.logging.WarningListeners;
 import org.opengis.parameter.InvalidParameterCardinalityException;
 
 
@@ -29,7 +30,7 @@ import org.opengis.parameter.InvalidParameterCardinalityException;
  * All those objects share in common a {@link #getName()} method.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -75,4 +76,14 @@ public abstract class NamedElement {
             }
         }, namesLocale);
     }
+
+    /**
+     * Returns the resources to use for warnings or error messages.
+     *
+     * @param  listeners  where the warnings are sent. Used for inferring the locale.
+     * @return the resources for the locales specified by the given argument.
+     */
+    protected static Resources resources(final WarningListeners<?> listeners) {
+        return Resources.forLocale(listeners != null ? listeners.getLocale() : null);
+    }
 }
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
index a001c7d..7ddb3c8 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.java
@@ -59,6 +59,11 @@ public final class Resources extends IndexedResourceBundle {
         }
 
         /**
+         * Can not compute data location for “{1}” variable in the “{0}” netCDF file.
+         */
+        public static final short CanNotComputeVariablePosition_2 = 6;
+
+        /**
          * Can not use UCAR library for netCDF format. Fallback on Apache SIS implementation.
          */
         public static final short CanNotUseUCAR = 4;
@@ -69,11 +74,26 @@ public final class Resources extends IndexedResourceBundle {
         public static final short DimensionNotFound_3 = 1;
 
         /**
+         * Duplicated reference to “{1}” in netCDF file “{0}”.
+         */
+        public static final short DuplicatedReference_2 = 7;
+
+        /**
+         * The declared size of variable “{1}” in netCDF file “{0}” is {2} bytes
greater than expected.
+         */
+        public static final short MismatchedVariableSize_3 = 8;
+
+        /**
          * Variable “{1}” in file “{0}” has a dimension “{3}” while we expected
“{2}”.
          */
         public static final short UnexpectedDimensionForVariable_4 = 2;
 
         /**
+         * NetCDF file “{0}” uses unsupported data type {2} for variable “{1}”.
+         */
+        public static final short UnsupportedDataType_3 = 5;
+
+        /**
          * Variable “{1}” is not found in the “{0}” file.
          */
         public static final short VariableNotFound_2 = 3;
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
index dcc8727..92f6c3f 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources.properties
@@ -19,7 +19,11 @@
 # Resources in this file are for "sis-netcdf" usage only and should not be used by any other
module.
 # For resources shared by all modules in the Apache SIS project, see "org.apache.sis.util.resources"
package.
 #
+CanNotComputeVariablePosition_2   = Can not compute data location for \u201c{1}\u201d variable
in the \u201c{0}\u201d netCDF file.
 CanNotUseUCAR                     = Can not use UCAR library for netCDF format. Fallback
on Apache SIS implementation.
 DimensionNotFound_3               = Dimension \u201c{2}\u201d declared by attribute \u201c{1}\u201d
is not found in the \u201c{0}\u201d file.
+DuplicatedReference_2             = Duplicated reference to \u201c{1}\u201d in netCDF file
\u201c{0}\u201d.
+MismatchedVariableSize_3          = The declared size of variable \u201c{1}\u201d in netCDF
file \u201c{0}\u201d is {2} bytes greater than expected.
 UnexpectedDimensionForVariable_4  = Variable \u201c{1}\u201d in file \u201c{0}\u201d has
a dimension \u201c{3}\u201d while we expected \u201c{2}\u201d.
+UnsupportedDataType_3             = NetCDF file \u201c{0}\u201d uses unsupported data type
{2} for variable \u201c{1}\u201d.
 VariableNotFound_2                = Variable \u201c{1}\u201d is not found in the \u201c{0}\u201d
file.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
index c83365e..78adf94 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/Resources_fr.properties
@@ -24,7 +24,11 @@
 #   U+202F NARROW NO-BREAK SPACE  before  ; ! and ?
 #   U+00A0 NO-BREAK SPACE         before  :
 #
+CanNotComputeVariablePosition_2   = Ne peut pas calculer la position des donn\u00e9es de
la variable \u00ab\u202f{1}\u202f\u00bb dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb.
 CanNotUseUCAR                     = Ne peut pas utiliser la biblioth\u00e8que de l\u2019UCAR
pour le format netCDF. L\u2019impl\u00e9mentation de Apache SIS sera utilis\u00e9e \u00e0
la place.
 DimensionNotFound_3               = La dimension \u00ab\u202f{2}\u202f\u00bb d\u00e9clar\u00e9e
par l\u2019attribut \u00ab\u202f{1}\u202f\u00bb n\u2019a pas \u00e9t\u00e9 trouv\u00e9e dans
le fichier \u00ab\u202f{0}\u202f\u00bb.
+DuplicatedReference_2             = R\u00e9f\u00e9rence vers \u00ab\u202f{1}\u202f\u00bb
dupliqu\u00e9e dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb.
+MismatchedVariableSize_3          = La longueur d\u00e9clar\u00e9e de la variable \u00ab\u202f{1}\u202f\u00bb
dans le fichier netCDF \u00ab\u202f{0}\u202f\u00bb d\u00e9passe de {2} octets la valeur attendue.
 UnexpectedDimensionForVariable_4  = La variable \u00ab\u202f{1}\u202f\u00bb dans le fichier
\u00ab\u202f{0}\u202f\u00bb a une dimension \u00ab\u202f{3}\u202f\u00bb alors qu\u2019on attendait
\u00ab\u202f{2}\u202f\u00bb.
+UnsupportedDataType_3             = Le fichier netCDF \u00ab\u202f{0}\u202f\u00bb utilise
un type de donn\u00e9es non-support\u00e9 {2} pour la variable \u00ab\u202f{1}\u202f\u00bb.
 VariableNotFound_2                = La variable \u00ab\u202f{1}\u202f\u00bb n\u2019a pas
\u00e9t\u00e9 trouv\u00e9e dans le fichier \u00ab\u202f{0}\u202f\u00bb.
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
index 1fa3e4e..b52f92e 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
@@ -274,7 +274,7 @@ public final class ChannelDecoder extends Decoder {
                         default:        throw malformedHeader();
                     }
                 } catch (InvalidParameterCardinalityException e) {
-                    throw new DataStoreContentException(e.getLocalizedMessage(), e);
+                    throw malformedHeader().initCause(e);
                 }
             }
         }
@@ -323,21 +323,27 @@ public final class ChannelDecoder extends Decoder {
      * Returns an exception for a malformed header. This is used only after we have determined
      * that the file should be a netCDF one, but we found some inconsistency or unknown tags.
      */
-    private DataStoreException malformedHeader() {
+    private DataStoreContentException malformedHeader() {
         return new DataStoreContentException(listeners.getLocale(), "netCDF", getFilename(),
null);
     }
 
     /**
      * Ensures that {@code nelems} is not a negative value.
      */
-    private void ensureNonNegative(final int nelems, final int tag) throws DataStoreException
{
+    private void ensureNonNegative(final int nelems, final int tag) throws DataStoreContentException
{
         if (nelems < 0) {
-            throw new DataStoreContentException(errors().getString(Errors.Keys.NegativeArrayLength_1,
-                    getFilename() + DefaultNameSpace.DEFAULT_SEPARATOR + tagName(tag)));
+            throw new DataStoreContentException(errors().getString(Errors.Keys.NegativeArrayLength_1,
tagPath(tagName(tag))));
         }
     }
 
     /**
+     * Returns the name of a tag to show in error message. The returned name include the
filename.
+     */
+    private String tagPath(final String name) {
+        return getFilename() + DefaultNameSpace.DEFAULT_SEPARATOR + name;
+    }
+
+    /**
      * Aligns position in the stream after reading the given amount of bytes.
      * This method should be invoked only for {@link DataType#BYTE} and {@link DataType#CHAR}.
      *
@@ -367,7 +373,7 @@ public final class ChannelDecoder extends Decoder {
      * Reads a string from the channel in the {@link #NAME_ENCODING}. This is suitable for
the dimension,
      * variable and attribute names in the header. Note that attribute value may have a different
encoding.
      */
-    private String readName() throws IOException, DataStoreException {
+    private String readName() throws IOException, DataStoreContentException {
         final int length = input.readInt();
         if (length < 0) {
             throw malformedHeader();
@@ -387,7 +393,7 @@ public final class ChannelDecoder extends Decoder {
      *
      * @return the value, or {@code null} if it was an empty string or an empty array.
      */
-    private Object readValues(final DataType type, final int length) throws IOException,
DataStoreException {
+    private Object readValues(final DataType type, final int length) throws IOException,
DataStoreContentException {
         if (length == 0) {
             return null;
         }
@@ -466,7 +472,7 @@ public final class ChannelDecoder extends Decoder {
      * @param  nelems  the number of dimensions to read.
      * @return the dimensions in the order they are declared in the netCDF file.
      */
-    private Dimension[] readDimensions(final int nelems) throws IOException, DataStoreException
{
+    private Dimension[] readDimensions(final int nelems) throws IOException, DataStoreContentException
{
         final Dimension[] dimensions = new Dimension[nelems];
         for (int i=0; i<nelems; i++) {
             final String name = readName();
@@ -475,7 +481,7 @@ public final class ChannelDecoder extends Decoder {
             if (isUnlimited) {
                 length = numrecs;
                 if (length == STREAMING) {
-                    throw new DataStoreContentException(errors().getString(Errors.Keys.MissingValueForProperty_1,
"numrecs"));
+                    throw new DataStoreContentException(errors().getString(Errors.Keys.MissingValueForProperty_1,
tagPath("numrecs")));
                 }
             }
             dimensions[i] = new Dimension(name, length, isUnlimited);
@@ -784,6 +790,7 @@ public final class ChannelDecoder extends Decoder {
         if ("trajectory".equalsIgnoreCase(stringValue(CF.FEATURE_TYPE))) try {
             return FeaturesInfo.create(this);
         } catch (IllegalArgumentException e) {
+            // Not a problem with content, but rather with configuration.
             throw new DataStoreException(e.getLocalizedMessage(), e);
         }
         return new FeaturesInfo[0];
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java
index d221142..494e356 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/GridGeometryInfo.java
@@ -22,9 +22,10 @@ import java.util.SortedMap;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.internal.netcdf.Axis;
 import org.apache.sis.internal.netcdf.GridGeometry;
+import org.apache.sis.internal.netcdf.Resources;
+import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.netcdf.AttributeNames;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.util.resources.Errors;
 
 
 /**
@@ -34,7 +35,7 @@ import org.apache.sis.util.resources.Errors;
  * (domain) and output (range) of the function that convert grid indices to geodetic coordinates.</p>
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -76,6 +77,17 @@ final class GridGeometryInfo extends GridGeometry {
     }
 
     /**
+     * Returns the name of the netCDF file containing this grid geometry, or {@code null}
if unknown.
+     */
+    private String getFilename() {
+        for (final VariableInfo info : range) {
+            final String filename = info.getFilename();
+            if (filename != null) return filename;
+        }
+        return null;
+    }
+
+    /**
      * Returns the number of dimensions of source coordinates in the <cite>"grid to
CRS"</cite> conversion.
      * This is the number of dimensions of the <em>grid</em>.
      */
@@ -122,7 +134,7 @@ final class GridGeometryInfo extends GridGeometry {
         for (int i=0; i<range.length; i++) {
             final VariableInfo v = range[i];
             if (variables.put(v, i) != null) {
-                throw new DataStoreException(Errors.format(Errors.Keys.DuplicatedElement_1,
v.getName()));
+                throw new DataStoreContentException(Resources.format(Resources.Keys.DuplicatedReference_2,
getFilename(), v.getName()));
             }
         }
         /*
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
index 22cfc32..19b429b 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
@@ -19,6 +19,8 @@ package org.apache.sis.internal.netcdf.impl;
 import java.util.Map;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import java.io.IOException;
 import java.lang.reflect.Array;
 import ucar.nc2.constants.CF;
@@ -26,14 +28,14 @@ import ucar.nc2.constants.CDM;
 import ucar.nc2.constants._Coordinate;
 import org.apache.sis.internal.netcdf.DataType;
 import org.apache.sis.internal.netcdf.Variable;
+import org.apache.sis.internal.netcdf.Resources;
+import org.apache.sis.internal.system.Modules;
 import org.apache.sis.internal.storage.io.ChannelDataInput;
 import org.apache.sis.internal.storage.io.HyperRectangleReader;
 import org.apache.sis.internal.storage.io.Region;
-import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.netcdf.AttributeNames;
 import org.apache.sis.util.logging.WarningListeners;
-import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.CharSequences;
 import org.apache.sis.math.Vector;
 
@@ -74,6 +76,8 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
 
     /**
      * The variable name.
+     *
+     * @see #getName()
      */
     private final String name;
 
@@ -160,6 +164,11 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
     private final String[] meanings;
 
     /**
+     * Where to report warnings, if any.
+     */
+    private final WarningListeners<?> listeners;
+
+    /**
      * Creates a new variable.
      *
      * @param  input       the channel together with a buffer for reading the variable data.
@@ -169,7 +178,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
      * @param  dataType    the netCDF type of data, or {@code null} if unknown.
      * @param  size        the variable size. May be inaccurate and ignored.
      * @param  offset      the offset where the variable data begins in the netCDF file.
-     * @param  warnings    where to report warnings, if any.
+     * @param  listeners   where to report warnings, if any.
      */
     VariableInfo(final ChannelDataInput      input,
                  final String                name,
@@ -178,7 +187,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
                        DataType              dataType,
                  final int                   size,
                  final long                  offset,
-                 final WarningListeners<?>   warnings) throws DataStoreException
+                 final WarningListeners<?>   listeners) throws DataStoreContentException
     {
         final Object isUnsigned = attributes.get(CDM.UNSIGNED);
         if (isUnsigned != null) {
@@ -188,33 +197,46 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
         this.dimensions = dimensions;
         this.attributes = attributes;
         this.dataType   = dataType;
+        this.listeners  = listeners;
         /*
          * The 'size' value is provided in the netCDF files, but doesn't need to be stored
since it
          * is redundant with the dimension lengths and is not large enough for big variables
anyway.
          * Instead we compute the length ourselves, excluding the unlimited dimension.
          */
-        if (dataType != null) {
-            offsetToNextRecord = dataType.size();               // May be zero if unknown
data type.
+        if (dataType != null && (offsetToNextRecord = dataType.size()) != 0) {
             for (int i=0; i<dimensions.length; i++) {
                 final Dimension dim = dimensions[i];
                 if (!dim.isUnlimited) {
                     offsetToNextRecord = Math.multiplyExact(offsetToNextRecord, dim.length());
                 } else if (i != 0) {
                     // Unlimited dimension, if any, must be first in a netCDF 3 classic format.
-                    throw new DataStoreException(warnings.getLocale(), "netCDF", input.filename,
null);
+                    throw new DataStoreContentException(listeners.getLocale(), "netCDF",
input.filename, null);
                 }
             }
-        }
-        /*
-         * Prepare the object to be used for reading data cube efficiently from the input
channel.
-         * Note that 'dataType' can not be null if 'offsetToNextRecord' is non-zero.
-         */
-        if (offsetToNextRecord != 0) {
             reader = new HyperRectangleReader(dataType.number, input, offset);
         } else {
             reader = null;
-            if (size != -1) {                   // Maximal unsigned value, means possible
overflow.
-                offsetToNextRecord = Integer.toUnsignedLong(size);
+        }
+        /*
+         * If the value that we computed ourselves does not match the value declared in the
netCDF file,
+         * maybe for some reason the writer used a different layout.  For example maybe it
inserted some
+         * additional padding.
+         */
+        if (size != -1) {                           // Maximal unsigned value, means possible
overflow.
+            final long expected = paddedSize();
+            final long actual = Integer.toUnsignedLong(size);
+            if (actual != expected) {
+                if (expected != 0) {
+                    final LogRecord record = resources(listeners).getLogRecord(Level.WARNING,
+                            Resources.Keys.MismatchedVariableSize_3, getFilename(), name,
actual - expected);
+                    record.setLoggerName(Modules.NETCDF);
+                    record.setSourceClassName(ChannelDecoder.class.getName());      // Caller
of this constructor.
+                    record.setSourceMethodName("readVariables");
+                    listeners.warning(record);
+                }
+                if (actual > offsetToNextRecord) {
+                    offsetToNextRecord = actual;
+                }
             }
         }
         /*
@@ -251,6 +273,14 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
     }
 
     /**
+     * Returns the value of {@link #offsetToNextRecord} with padding applied. This value
is true
+     * only if this method is invoked <strong>before</strong> {@link #complete(VariableInfo[])}.
+     */
+    private long paddedSize() {
+        return Math.addExact(offsetToNextRecord, Integer.BYTES - 1) & ~(Integer.BYTES
- 1);
+    }
+
+    /**
      * Performs the final adjustment of the {@link #offsetToNextRecord} field of all the
given variables.
      * This method applies padding except for the special case documented in netCDF specification:
      * <cite>"In the special case when there is only one {@linkplain #isUnlimited()
record variable}
@@ -262,16 +292,15 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
      */
     static void complete(final VariableInfo[] variables) {
         final VariableInfo[] unlimited = new VariableInfo[variables.length];
-        int     count     = 0;                  // Number of valid elements in the 'unlimited'
array.
-        long    total     = 0;                  // Sum of the size of all variables having
a unlimited dimension.
-        boolean isUnknown = false;              // True if 'total' is actually unknown.
+        int     count        = 0;               // Number of valid elements in the 'unlimited'
array.
+        long    recordStride = 0;               // Sum of the size of all variables having
a unlimited dimension.
+        boolean isUnknown    = false;           // True if 'total' is actually unknown.
         for (final VariableInfo variable : variables) {
             if (variable.isUnlimited()) {
-                // Apply 4 bytes padding.
-                final long offsetToNextRecord = Math.addExact(variable.offsetToNextRecord,
Integer.BYTES - 1) & ~(Integer.BYTES - 1);
+                final long paddedSize = variable.paddedSize();
                 unlimited[count++] = variable;
-                isUnknown |= (offsetToNextRecord == 0);
-                total = Math.addExact(total, offsetToNextRecord);
+                isUnknown |= (paddedSize == 0);
+                recordStride = Math.addExact(recordStride, paddedSize);
             }
         }
         if (isUnknown) {
@@ -282,11 +311,18 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
         } else if (count == 1) {
             unlimited[0].offsetToNextRecord = 0;        // Special case cited in method javadoc.
         } else for (int i=0; i<count; i++) {
-            unlimited[i].offsetToNextRecord = total - unlimited[i].offsetToNextRecord;
+            unlimited[i].offsetToNextRecord = recordStride - unlimited[i].offsetToNextRecord;
         }
     }
 
     /**
+     * Returns the name of the netCDF file containing this variable, or {@code null} if unknown.
+     */
+    final String getFilename() {
+        return (reader != null) ? reader.filename() : null;
+    }
+
+    /**
      * Returns the name of this variable.
      */
     @Override
@@ -498,32 +534,45 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Vector read() throws IOException, DataStoreException {
+    public Vector read() throws IOException, DataStoreContentException {
         if (values == null) {
             if (reader == null) {
                 throw new DataStoreContentException(unknownType());
             }
-            final int    dimension    = dimensions.length;
-            final long[] regionLower  = new long[dimension];
-            final long[] regionUpper  = new long[dimension];
-            final int [] subsamplings = new int [dimension];
+            final int    dimension   = dimensions.length;
+            final long[] lower       = new long[dimension];
+            final long[] upper       = new long[dimension];
+            final int [] subsampling = new int [dimension];
             for (int i=0; i<dimension; i++) {
-                regionUpper [i] = dimensions[(dimension - 1) - i].length();
-                subsamplings[i] = 1;
-            }
-            final Region region = new Region(regionUpper, regionLower, regionUpper, subsamplings);
-            if (isUnlimited()) {
-                if (offsetToNextRecord < 0) {
-                    throw new DataStoreException(Errors.format(Errors.Keys.CanNotRead_1,
name));
-                }
-                region.skipAfterLastDimension(offsetToNextRecord / dataType.size());
+                upper[i] = dimensions[(dimension - 1) - i].length();
+                subsampling[i] = 1;
             }
+            final Region region = new Region(upper, lower, upper, subsampling);
+            applyUnlimitedDimensionStride(region);
             values = Vector.create(reader.read(region), dataType.isUnsigned).compress(0);
         }
         return values;
     }
 
     /**
+     * If this variable uses the unlimited dimension, we have to skip the records of all
other unlimited variables
+     * before to reach the next record of this variable.  Current implementation can do that
only if the number of
+     * bytes to skip is a multiple of the data type size. It should be the case most of the
time because variables
+     * in netCDF files have a 4 bytes padding. It may not work however if the variable uses
{@code long} or
+     * {@code double} type.
+     */
+    private void applyUnlimitedDimensionStride(final Region region) throws DataStoreContentException
{
+        if (isUnlimited()) {
+            final int dataSize = reader.dataSize();
+            if (offsetToNextRecord < 0 || (offsetToNextRecord % dataSize) != 0) {
+                throw new DataStoreContentException(resources(listeners)
+                        .getString(Resources.Keys.CanNotComputeVariablePosition_2, getFilename(),
name));
+            }
+            region.increaseStride(dimensions.length - 1, offsetToNextRecord / dataSize);
+        }
+    }
+
+    /**
      * Reads a sub-sampled sub-area of the variable.
      * Multi-dimensional variables are flattened as a one-dimensional array (wrapped in a
vector).
      *
@@ -533,7 +582,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
      * @return the data as an array of a Java primitive type.
      */
     @Override
-    public Vector read(int[] areaLower, int[] areaUpper, int[] subsampling) throws IOException,
DataStoreException {
+    public Vector read(int[] areaLower, int[] areaUpper, int[] subsampling) throws IOException,
DataStoreContentException {
         if (reader == null) {
             throw new DataStoreContentException(unknownType());
         }
@@ -568,7 +617,9 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
             sub  [i] = subsampling[j];
             size [i] = dimensions[j].length();
         }
-        return Vector.create(reader.read(new Region(size, lower, upper, sub)), dataType.isUnsigned);
+        final Region region = new Region(size, lower, upper, sub);
+        applyUnlimitedDimensionStride(region);
+        return Vector.create(reader.read(region), dataType.isUnsigned);
     }
 
     /**
@@ -587,7 +638,7 @@ final class VariableInfo extends Variable implements Comparable<VariableInfo>
{
      * Returns the error message for an unknown data type.
      */
     private String unknownType() {
-        return Errors.format(Errors.Keys.UnknownType_1, "NetCDF:" + dataType);
+        return resources(listeners).getString(Resources.Keys.UnsupportedDataType_3, getFilename(),
name, dataType);
     }
 
     /**
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
index 056124a..268ea20 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
@@ -31,7 +31,6 @@ import org.apache.sis.internal.netcdf.DataType;
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.util.UnmodifiableArrayList;
 import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.storage.DataStoreContentException;
 
 
 /**
@@ -242,7 +241,7 @@ final class VariableWrapper extends Variable {
         try {
             array = variable.read(new Section(areaLower, size, subsampling));
         } catch (InvalidRangeException e) {
-            throw new DataStoreContentException(e);
+            throw new DataStoreException(e);
         }
         return Vector.create(array.get1DJavaArray(array.getElementType()), variable.isUnsigned());
     }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
index 02e92f3..eccdca4 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java
@@ -97,7 +97,7 @@ public final class Resources extends IndexedResourceBundle {
         public static final short CanNotReadFile_3 = 2;
 
         /**
-         * Can not read line {2} (after column {3}) of “{1}” as part of a file in the
{0} format.
+         * Can not read after column {3} of line {2} of “{1}” as part of a file in the
{0} format.
          */
         public static final short CanNotReadFile_4 = 3;
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
index 9f54f2c..6cf0903 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties
@@ -26,7 +26,7 @@ CanNotReadCRS_WKT_1               = Can not read the Coordinate Reference
System
 CanNotReadDirectory_1             = Can not read \u201c{0}\u201d directory.
 CanNotReadFile_2                  = Can not read \u201c{1}\u201d as a file in the {0} format.
 CanNotReadFile_3                  = Can not read line {2} of \u201c{1}\u201d as part of a
file in the {0} format.
-CanNotReadFile_4                  = Can not read line {2} (after column {3}) of \u201c{1}\u201d
as part of a file in the {0} format.
+CanNotReadFile_4                  = Can not read after column {3} of line {2} of \u201c{1}\u201d
as part of a file in the {0} format.
 CanNotRemoveResource_2            = Can not remove resource \u201c{1}\u201d from aggregate
\u201c{0}\u201d.
 CanNotStoreResourceType_2         = Can not save resources of type \u2018{1}\u2019 in a \u201c{0}\u201d
store.
 ClosedReader_1                    = This {0} reader is closed.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
index eeb541d..8edfd0a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties
@@ -31,7 +31,7 @@ CanNotReadCRS_WKT_1               = Ne peut pas lire le syst\u00e8me de
r\u00e9f
 CanNotReadDirectory_1             = Ne peut pas lire le r\u00e9pertoire \u00ab\u202f{0}\u202f\u00bb.
 CanNotReadFile_2                  = Ne peut pas lire \u00ab\u202f{1}\u202f\u00bb comme un
fichier au format {0}.
 CanNotReadFile_3                  = Ne peut pas lire la ligne {2} de \u00ab\u202f{1}\u202f\u00bb
comme une partie d\u2019un fichier au format {0}.
-CanNotReadFile_4                  = Ne peut pas lire la ligne {2} (apr\u00e8s la colonne
{3}) de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}.
+CanNotReadFile_4                  = Ne peut pas lire apr\u00e8s la colonne {3} de la ligne
{2} de \u00ab\u202f{1}\u202f\u00bb comme une partie d\u2019un fichier au format {0}.
 CanNotRemoveResource_2            = Ne peut pas supprimer la ressource \u00ab\u202f{1}\u202f\u00bb
de l\u2019agr\u00e9gat \u00ab\u202f{0}\u202f\u00bb.
 CanNotStoreResourceType_2         = Ne peut pas enregistrer des ressources de type \u2018{1}\u2019
dans un entrep\u00f4t de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb.
 ClosedReader_1                    = Ce lecteur {0} est ferm\u00e9.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/DataTransfer.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/DataTransfer.java
index ed313db..d120eaf 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/DataTransfer.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/DataTransfer.java
@@ -18,7 +18,6 @@ package org.apache.sis.internal.storage.io;
 
 import java.io.IOException;
 import java.nio.Buffer;
-import org.apache.sis.util.Debug;
 
 
 /**
@@ -34,12 +33,15 @@ interface DataTransfer {
     /**
      * Returns a file identifier for error messages or debugging purpose.
      */
-    @Debug
     String filename();
 
     /**
      * Returns the size of the Java primitive type which is the element of the array.
-     * The size is expressed as the number of bits to shift.
+     * The size is expressed as the number of bits to shift:
+     *
+     * {@code java
+     *     dataSize = 1 << dataSizeShift;
+     * }
      */
     int dataSizeShift();
 
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
index e2b0859..4bcebd2 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java
@@ -21,9 +21,7 @@ import java.nio.ByteBuffer;
 import java.io.IOException;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.resources.Errors;
-import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
-import org.apache.sis.util.Debug;
 
 
 /**
@@ -57,10 +55,10 @@ public final class HyperRectangleReader {
      * @param  dataType  the type of elements to read, as one of the constants defined in
{@link Numbers}.
      * @param  input     the channel from which to read the values, together with a buffer
for transferring data.
      * @param  origin    the position in the channel of the first sample value in the hyper-rectangle.
-     * @throws DataStoreException if the given {@code dataType} is not one of the supported
values.
+     * @throws DataStoreContentException if the given {@code dataType} is not one of the
supported values.
      */
     public HyperRectangleReader(final byte dataType, final ChannelDataInput input, final
long origin)
-            throws DataStoreException
+            throws DataStoreContentException
     {
         switch (dataType) {
             case Numbers.BYTE:      reader = input.new BytesReader  (           null); break;
@@ -102,12 +100,20 @@ public final class HyperRectangleReader {
      *
      * @return the file identifier.
      */
-    @Debug
     public String filename() {
         return reader.filename();
     }
 
     /**
+     * Returns the number of bytes in each value to be read.
+     *
+     * @return number of bytes per value.
+     */
+    public int dataSize() {
+        return 1 << reader.dataSizeShift();
+    }
+
+    /**
      * Reads data in the given region. It is caller's responsibility to ensure that the {@code
Region}
      * object has been created with a {@code size} argument equals to this hyper-rectangle
size.
      *
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Region.java
b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Region.java
index 486d425..aba124d 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Region.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/Region.java
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.storage.io;
 
+import org.apache.sis.io.TableAppender;
 import org.apache.sis.internal.util.Numerics;
 
 
@@ -107,17 +108,20 @@ public final class Region {
     }
 
     /**
-     * Increases the number of bytes that need to be skipped before incrementing the index
-     * in the last dimension of the hyper-cube. Current implementation allows to alter the
-     * reading for the last dimension only, because this is the only dimension needed for
-     * supporting netCDF "unlimited" dimension. Future versions may expand to other dimensions
-     * if needed.
+     * Increases the number of values between two consecutive index values in the given dimension
of the hyper-cube.
+     * The strides are computed automatically at construction time, but this method can be
invoked in some rare cases
+     * where those values need to be modified (example: for adapting to the layout of netCDF
"unlimited" variable).
      *
-     * @param n  number of bytes to skip.
+     * <div class="note"><b>Example:</b> in a cube of dimension 10×10×10,
the number of values between indices
+     * (0,0,1) and (0,0,2) is 100. Invoking {@code increaseStride(1, 4)} will increase this
value to 104.
+     * {@link HyperRectangleReader} will still read only the requested 100 values, but will
skip 4 more values
+     * when moving from plane 1 to plane 2.</div>
+     *
+     * @param  dimension  dimension for which to increase the stride.
+     * @param  skip       additional number of values to skip after we finished reading a
block of data in the specified dimension.
      */
-    public void skipAfterLastDimension(final long n) {
-        final int i = skips.length - 2;                     // Reminder: skips.length ==
dimension + 1.
-        skips[i] = Math.addExact(skips[i], n);
+    public void increaseStride(final int dimension, final long skip) {
+        skips[dimension] = Math.addExact(skips[dimension], skip);
     }
 
     /**
@@ -153,4 +157,22 @@ public final class Region {
         }
         return Math.toIntExact(length);
     }
+
+    /**
+     * Returns a string representation of this region for debugging purpose.
+     *
+     * @return a string representation of this region.
+     */
+    @Override
+    public String toString() {
+        final TableAppender table = new TableAppender(" ");
+        table.setCellAlignment(TableAppender.ALIGN_RIGHT);
+        table.append("size").nextColumn();
+        table.append("skip").nextLine();
+        for (int i=0; i<targetSize.length; i++) {
+            table.append(String.valueOf(targetSize[i])).nextColumn();
+            table.append(String.valueOf(skips[i])).nextLine();
+        }
+        return table.toString();
+    }
 }
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java
index 087156f..4e4fc81 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java
@@ -74,6 +74,12 @@ public class DataStoreContentException extends DataStoreException {
      * Location in the file where the error occurred while be fetched from the given {@code
store}
      * argument if possible. If the given store is not recognized, then it will be ignored.
      *
+     * <p>Examples of messages created by this constructor:</p>
+     * <ul>
+     *   <li>Can not read <var>“Foo”</var> as a file in the <var>Bar</var>
format.</li>
+     *   <li>Can not read after column 10 or line 100 of <var>“Foo”</var>
as part of a file in the <var>Bar</var> format.</li>
+     * </ul>
+     *
      * @param locale    the locale of the message to be returned by {@link #getLocalizedMessage()},
or {@code null}.
      * @param format    short name or abbreviation of the data format (e.g. "CSV", "GML",
"WKT", <i>etc</i>).
      * @param filename  name of the file or data store where the error occurred.
diff --git a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreException.java
b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreException.java
index 2bade7a..ae3051a 100644
--- a/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreException.java
+++ b/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreException.java
@@ -103,6 +103,12 @@ public class DataStoreException extends Exception implements LocalizedException
      * or {@link javax.xml.stream.XMLStreamReader#getLocation()} method.
      * If the given {@code store} argument is not one of the recognized types, then it is
ignored.
      *
+     * <p>Examples of messages created by this constructor:</p>
+     * <ul>
+     *   <li>Can not read <var>“Foo”</var> as a file in the <var>Bar</var>
format.</li>
+     *   <li>Can not read after column 10 or line 100 of <var>“Foo”</var>
as part of a file in the <var>Bar</var> format.</li>
+     * </ul>
+     *
      * @param locale    the locale of the message to be returned by {@link #getLocalizedMessage()},
or {@code null}.
      * @param format    short name or abbreviation of the data format (e.g. "CSV", "GML",
"WKT", <i>etc</i>).
      * @param filename  name of the file or data store where the error occurred.


Mime
View raw message