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 <[email protected]>
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.