sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1764119 [10/11] - in /sis/trunk: ./ core/ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/main/java/org/apache/sis/feature/builder/ core/sis-feature/src/main/java/org/apache/sis/internal/feature/ core/sis-metada...
Date Mon, 10 Oct 2016 15:24:06 GMT
Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/ChannelDecoder.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -18,24 +18,29 @@ package org.apache.sis.internal.netcdf.i
 
 import java.util.Set;
 import java.util.Map;
+import java.util.AbstractMap;
 import java.util.LinkedHashSet;
 import java.util.LinkedHashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.Locale;
 import java.util.regex.Pattern;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.channels.ReadableByteChannel;
-import java.lang.reflect.Array;
 import javax.measure.converter.UnitConverter;
 import javax.measure.converter.ConversionException;
 import org.opengis.parameter.InvalidParameterCardinalityException;
+import org.apache.sis.internal.netcdf.DataType;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.netcdf.GridGeometry;
+import org.apache.sis.internal.netcdf.NamedElement;
+import org.apache.sis.internal.netcdf.DiscreteSampling;
+import org.apache.sis.internal.netcdf.Resources;
 import org.apache.sis.internal.storage.ChannelDataInput;
 import org.apache.sis.internal.util.CollectionsExt;
 import org.apache.sis.internal.util.StandardDateFormat;
@@ -45,13 +50,12 @@ import org.apache.sis.util.iso.DefaultNa
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.logging.WarningListeners;
-import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.Debug;
 import org.apache.sis.measure.Units;
+import ucar.nc2.constants.CF;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk8.JDK8;
-import org.apache.sis.internal.jdk8.Function;
 import org.apache.sis.internal.jdk8.DateTimeException;
 
 
@@ -100,7 +104,7 @@ public final class ChannelDecoder extend
      *
      * @see #findAttribute(String)
      */
-    private static final Locale NAME_LOCALE = Locale.US;
+    static final Locale NAME_LOCALE = Locale.US;
 
     /**
      * The pattern to use for separating the component of a time unit.
@@ -169,14 +173,28 @@ public final class ChannelDecoder extend
      *
      * @see #getVariables()
      */
-    private final VariableInfo[] variables;
+    final VariableInfo[] variables;
+
+    /**
+     * Same as {@link #variables}, but as a map for faster search.
+     */
+    private final Map<String,VariableInfo> variableMap;
 
     /**
      * The attributes found in the NetCDF file.
+     * Values in this map give directly the attribute value (there is no {@code Attribute} object).
      *
      * @see #findAttribute(String)
      */
-    private final Map<String,Attribute> attributeMap;
+    private final Map<String,Object> attributeMap;
+
+    /**
+     * All dimensions in the NetCDF files.
+     *
+     * @see #readDimensions(int)
+     * @see #findDimension(String)
+     */
+    private Map<String,Dimension> dimensionMap;
 
     /**
      * The grid geometries, created when first needed.
@@ -187,7 +205,16 @@ public final class ChannelDecoder extend
 
     /**
      * Creates a new decoder for the given file.
-     * This constructor parses immediately the header.
+     * This constructor parses immediately the header, which shall have the following structure:
+     *
+     * <ul>
+     *   <li>Magic number:   'C','D','F'</li>
+     *   <li>Version number: 1 or 2</li>
+     *   <li>Number of records</li>
+     *   <li>List of NetCDF dimensions  (see {@link #readDimensions(int)})</li>
+     *   <li>List of global attributes  (see {@link #readAttributes(int)})</li>
+     *   <li>List of variables          (see {@link #readVariables(int, Dimension[])})</li>
+     * </ul>
      *
      * @param  listeners  where to send the warnings.
      * @param  input      the channel and the buffer from where data are read.
@@ -205,7 +232,7 @@ public final class ChannelDecoder extend
          */
         int version = input.readInt();
         if ((version & 0xFFFFFF00) != MAGIC_NUMBER) {
-            throw new DataStoreContentException(errors().getString(Errors.Keys.UnexpectedFileFormat_2, "NetCDF", input.filename));
+            throw new DataStoreContentException(errors().getString(Errors.Keys.UnexpectedFileFormat_2, "NetCDF", getFilename()));
         }
         /*
          * Check the version number.
@@ -214,7 +241,7 @@ public final class ChannelDecoder extend
         switch (version) {
             case 1:  is64bits = false; break;
             case 2:  is64bits = true;  break;
-            default: throw new DataStoreContentException(errors().getString(Errors.Keys.UnsupportedVersion_1, version));
+            default: throw new DataStoreContentException(errors().getString(Errors.Keys.UnsupportedFormatVersion_2, "NetCDF", version));
             // If more cases are added, remember to increment the MAX_VERSION constant.
         }
         numrecs = input.readInt();
@@ -222,25 +249,30 @@ public final class ChannelDecoder extend
          * Read the dimension, attribute and variable declarations. We expect exactly 3 lists,
          * where any of them can be flagged as absent by a long (64 bits) 0.
          */
-        Dimension[]    dimensions = null;
-        VariableInfo[] variables  = null;
-        Attribute[]    attributes = null;
+        Dimension[]        dimensions = null;
+        VariableInfo[]     variables  = null;
+        Map<String,Object> attributes = null;
         for (int i=0; i<3; i++) {
             final long tn = input.readLong(); // Combination of tag and nelems
             if (tn != 0) {
                 final int tag = (int) (tn >>> Integer.SIZE);
                 final int nelems = (int) tn;
                 ensureNonNegative(nelems, tag);
-                switch (tag) {
-                    case DIMENSION: dimensions = readDimensions(nelems); break;
-                    case VARIABLE:  variables  = readVariables (nelems, dimensions); break;
-                    case ATTRIBUTE: attributes = readAttributes(nelems); break;
-                    default:        throw malformedHeader();
+                try {
+                    switch (tag) {
+                        case DIMENSION: dimensions = readDimensions(nelems); break;
+                        case VARIABLE:  variables  = readVariables (nelems, dimensions); break;
+                        case ATTRIBUTE: attributes = readAttributes(nelems); break;
+                        default:        throw malformedHeader();
+                    }
+                } catch (InvalidParameterCardinalityException e) {
+                    throw new DataStoreContentException(e.getLocalizedMessage());
                 }
             }
         }
-        attributeMap = toMap(attributes, Attribute.NAME_FUNCTION);
+        attributeMap = attributes;
         this.variables = variables;
+        variableMap = NamedElement.toCaseInsensitiveNameMap(variables, NAME_LOCALE);
     }
 
     /**
@@ -266,16 +298,25 @@ public final class ChannelDecoder extend
      *
      * @return the localized error resource bundle.
      */
-    private Errors errors() {
+    final Errors errors() {
         return Errors.getResources(listeners.getLocale());
     }
 
     /**
+     * Returns the NetCDF-specific resource bundle for the locale given by {@link #getLocale()}.
+     *
+     * @return the localized error resource bundle.
+     */
+    final Resources resources() {
+        return Resources.forLocale(listeners.getLocale());
+    }
+
+    /**
      * 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() {
-        return new DataStoreContentException(errors().getString(Errors.Keys.CanNotParseFile_2, "NetCDF", input.filename));
+        return new DataStoreContentException(errors().getString(Errors.Keys.CanNotParseFile_2, "NetCDF", getFilename()));
     }
 
     /**
@@ -284,36 +325,27 @@ public final class ChannelDecoder extend
     private void ensureNonNegative(final int nelems, final int tag) throws DataStoreException {
         if (nelems < 0) {
             throw new DataStoreContentException(errors().getString(Errors.Keys.NegativeArrayLength_1,
-                    input.filename + DefaultNameSpace.DEFAULT_SEPARATOR + tagName(tag)));
+                    getFilename() + DefaultNameSpace.DEFAULT_SEPARATOR + tagName(tag)));
         }
     }
 
     /**
-     * Makes sure that the buffer contains at least <var>n</var> remaining elements of the given size.
-     * If the buffer does not have enough bytes available, more bytes will be read from the channel.
-     * If the buffer capacity is not sufficient for reading the given amount of data, an exception
-     * is thrown.
+     * 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}.
      *
-     * <p>The NetCDF format add padding after bytes, characters and short integers in order to align
-     * the data on multiple of 4 bytes. This method adds such padding to the number of bytes to read.</p>
+     * <p>The NetCDF format adds padding after bytes, characters and short integers in order to align the data
+     * on multiple of 4 bytes. This method is used for adding such padding to the number of bytes to read.</p>
      *
-     * @param  n         the number of elements to read.
-     * @param  dataSize  the size of each element, in bytes.
-     * @param  name      the name of the element to read, used only in case of error for formatting the message.
-     * @return the number of bytes to read, rounded to the next multiple of 4.
+     * @param  length   number of byte reads.
+     * @throws IOException if an error occurred while skipping bytes.
      */
-    private int ensureBufferContains(final int n, final int dataSize, String name) throws IOException, DataStoreException {
-        // (n+3) & ~3  is a trick for rounding 'n' to the next multiple of 4.
-        final long size = ((n & 0xFFFFFFFFL) * dataSize + 3) & ~3;
-        if (size > input.buffer.capacity()) {
-            name = input.filename + DefaultNameSpace.DEFAULT_SEPARATOR + name;
-            final Errors errors = errors();
-            throw new DataStoreContentException(n < 0 ?
-                    errors.getString(Errors.Keys.NegativeArrayLength_1, name) :
-                    errors.getString(Errors.Keys.ExcessiveListSize_2, name, n));
+    private void align(int length) throws IOException {
+        length &= 3;
+        if (length != 0) {
+            length = 4 - length;
+            input.ensureBufferContains(length);
+            input.buffer.position(input.buffer.position() + length);
         }
-        input.ensureBufferContains((int) size);
-        return (int) size;
     }
 
     /**
@@ -324,17 +356,18 @@ public final class ChannelDecoder extend
     }
 
     /**
-     * Reads a string from the buffer in the {@value #NAME_ENCODING}. This is suitable for the dimension,
+     * Reads a string from the channel in the {@value #NAME_ENCODING}. This is suitable for the dimension,
      * variable and attribute names in the header. Note that attribute value may have a different encoding.
+     *
+     * @param  length  number of bytes to read. The number of bytes actually read may be greater.
      */
     private String readName() throws IOException, DataStoreException {
         final int length = input.readInt();
         if (length < 0) {
             throw malformedHeader();
         }
-        final int size = ensureBufferContains(length, 1, "<name>");
         final String text = input.readString(length, NAME_ENCODING);
-        input.buffer.position(input.buffer.position() + (size - length));
+        align(length);
         return text;
     }
 
@@ -348,56 +381,72 @@ public final class ChannelDecoder extend
      *
      * @return the value, or {@code null} if it was an empty string or an empty array.
      */
-    private Object readValues(final String name, final int type, final int length) throws IOException, DataStoreException {
+    private Object readValues(final DataType type, final int length) throws IOException, DataStoreException {
         if (length == 0) {
             return null;
         }
-        final ByteBuffer buffer = input.buffer;
-        final int size = ensureBufferContains(length, VariableInfo.sizeOf(type), name);
-        final int position = buffer.position(); // Must be after 'ensureBufferContains'
-        final Object result;
+        if (length < 0) {
+            throw malformedHeader();
+        }
+        if (length == 1) {
+            switch (type) {
+                case BYTE:   {final byte  v =         input.readByte();          align(1); return v;}
+                case UBYTE:  {final short v = (short) input.readUnsignedByte();  align(1); return v;}
+                case SHORT:  {final short v =         input.readShort();         align(2); return v;}
+                case USHORT: {final int   v =         input.readUnsignedShort(); align(2); return v;}
+                case INT:    return input.readInt();
+                case INT64:  return input.readLong();
+                case UINT:   return input.readUnsignedInt();
+                case FLOAT:  return input.readFloat();
+                case DOUBLE: return input.readDouble();
+            }
+        }
         switch (type) {
-            case VariableInfo.CHAR: {
-                final String text = input.readString(length, encoding).trim();
-                result = text.isEmpty() ? null : text;
-                break;
+            case CHAR: {
+                final String text = input.readString(length, encoding);
+                align(length);
+                return text.isEmpty() ? null : text;
             }
-            case VariableInfo.BYTE: {
+            case BYTE:
+            case UBYTE: {
                 final byte[] array = new byte[length];
-                buffer.get(array);
-                result = array;
-                break;
+                input.readFully(array);
+                align(length);
+                return array;
             }
-            case VariableInfo.SHORT: {
+            case SHORT:
+            case USHORT: {
                 final short[] array = new short[length];
-                buffer.asShortBuffer().get(array);
-                result = array;
-                break;
+                input.readFully(array, 0, length);
+                align(length << 1);
+                return array;
             }
-            case VariableInfo.INT: {
+            case INT:
+            case UINT: {
                 final int[] array = new int[length];
-                buffer.asIntBuffer().get(array);
-                result = array;
-                break;
+                input.readFully(array, 0, length);
+                return array;
             }
-            case VariableInfo.FLOAT: {
+            case INT64:
+            case UINT64: {
+                final long[] array = new long[length];
+                input.readFully(array, 0, length);
+                return array;
+            }
+            case FLOAT: {
                 final float[] array = new float[length];
-                buffer.asFloatBuffer().get(array);
-                result = array;
-                break;
+                input.readFully(array, 0, length);
+                return array;
             }
-            case VariableInfo.DOUBLE: {
+            case DOUBLE: {
                 final double[] array = new double[length];
-                buffer.asDoubleBuffer().get(array);
-                result = array;
-                break;
+                input.readFully(array, 0, length);
+                return array;
             }
             default: {
                 throw malformedHeader();
             }
         }
-        buffer.position(position + size);
-        return result;
     }
 
     /**
@@ -424,6 +473,7 @@ public final class ChannelDecoder extend
             }
             dimensions[i] = new Dimension(name, length);
         }
+        dimensionMap = Dimension.toCaseInsensitiveNameMap(dimensions, NAME_LOCALE);
         return dimensions;
     }
 
@@ -443,17 +493,16 @@ public final class ChannelDecoder extend
      *
      * @param  nelems  the number of attributes to read.
      */
-    private Attribute[] readAttributes(final int nelems) throws IOException, DataStoreException {
-        final Attribute[] attributes = new Attribute[nelems];
-        int count = 0;
-        for (int i=0; i<nelems; i++) {
+    private Map<String,Object> readAttributes(int nelems) throws IOException, DataStoreException {
+        final List<Map.Entry<String,Object>> attributes = new ArrayList<>(nelems);
+        while (--nelems >= 0) {
             final String name = readName();
-            final Object value = readValues(name, input.readInt(), input.readInt());
+            final Object value = readValues(DataType.valueOf(input.readInt()), input.readInt());
             if (value != null) {
-                attributes[count++] = new Attribute(name, value);
+                attributes.add(new AbstractMap.SimpleEntry<>(name, value));
             }
         }
-        return ArraysExt.resize(attributes, count);
+        return CollectionsExt.toCaseInsensitiveNameMap(attributes, NAME_LOCALE);
     }
 
     /**
@@ -473,14 +522,14 @@ public final class ChannelDecoder extend
      *   <li>Offset where data begins   (use {@link #readOffset()})</li>
      * </ul>
      *
-     * @param  nelems      the number of variables to read.
-     * @param  dimensions  the dimensions previously read by {@link #readDimensions(int)}.
+     * @param  nelems         the number of variables to read.
+     * @param  allDimensions  the dimensions previously read by {@link #readDimensions(int)}.
      */
-    private VariableInfo[] readVariables(final int nelems, final Dimension[] dimensions)
+    private VariableInfo[] readVariables(final int nelems, final Dimension[] allDimensions)
             throws IOException, DataStoreException
     {
-        if (dimensions == null) {
-            throw malformedHeader();
+        if (allDimensions == null) {
+            throw malformedHeader();        // May happen if readDimensions(…) has not been invoked.
         }
         final VariableInfo[] variables = new VariableInfo[nelems];
         for (int j=0; j<nelems; j++) {
@@ -489,7 +538,7 @@ public final class ChannelDecoder extend
             final Dimension[] varDims = new Dimension[n];
             try {
                 for (int i=0; i<n; i++) {
-                    varDims[i] = dimensions[input.readInt()];
+                    varDims[i] = allDimensions[input.readInt()];
                 }
             } catch (IndexOutOfBoundsException cause) {
                 final DataStoreException e = malformedHeader();
@@ -500,7 +549,7 @@ public final class ChannelDecoder extend
              * Following block is almost a copy-and-paste of similar block in the contructor,
              * but with less cases in the "switch" statements.
              */
-            Attribute[] attributes = null;
+            Map<String,Object> attributes = null;
             final long tn = input.readLong();
             if (tn != 0) {
                 final int tag = (int) (tn >>> Integer.SIZE);
@@ -512,36 +561,12 @@ public final class ChannelDecoder extend
                     default:        throw malformedHeader();
                 }
             }
-            variables[j] = new VariableInfo(input, name, varDims, dimensions,
-                    toMap(attributes, Attribute.NAME_FUNCTION), input.readInt(), input.readInt(), readOffset());
+            variables[j] = new VariableInfo(input, name, varDims, attributes,
+                    DataType.valueOf(input.readInt()), input.readInt(), readOffset());
         }
         return variables;
     }
 
-    /**
-     * Creates a (<cite>name</cite>, <cite>element</cite>) mapping for the given array of elements.
-     * If the name of an element is not all lower cases, then this method also adds an entry for the
-     * lower cases version of that name in order to allow case-insensitive searches.
-     *
-     * <p>Code searching in the returned map shall ask for the original (non lower-case) name
-     * <strong>before</strong> to ask for the lower-cases version of that name.</p>
-     *
-     * @param  <E>           the type of elements.
-     * @param  elements      the elements to store in the map, or {@code null} if none.
-     * @param  nameFunction  the function for computing a name from an element.
-     * @return a (<cite>name</cite>, <cite>element</cite>) mapping with lower cases entries where possible.
-     * @throws DataStoreException if the same name is used for more than one element.
-     *
-     * @see #findAttribute(String)
-     */
-    private <E> Map<String,E> toMap(final E[] elements, final Function<E,String> nameFunction) throws DataStoreException {
-        try {
-            return CollectionsExt.toCaseInsensitiveNameMap(Arrays.asList(elements), nameFunction, NAME_LOCALE);
-        } catch (InvalidParameterCardinalityException e) {
-            throw new DataStoreContentException(errors().getString(Errors.Keys.ValueAlreadyDefined_1, e.getParameterName()));
-        }
-    }
-
 
 
     // --------------------------------------------------------------------------------------------
@@ -549,17 +574,25 @@ public final class ChannelDecoder extend
     // --------------------------------------------------------------------------------------------
 
     /**
+     * Returns a filename for information purpose only. This is used for formatting error messages.
+     *
+     * @return a filename to report in warning or error messages.
+     */
+    @Override
+    public final String getFilename() {
+        return input.filename;
+    }
+
+    /**
      * Defines the groups where to search for named attributes, in preference order.
      * The {@code null} group name stands for the global attributes.
      *
      * <p>Current implementation does nothing, since the NetCDF binary files that {@code ChannelDecoder}
      * can read do not have groups anyway. Future SIS implementations may honor the given group names if
      * groups support is added.</p>
-     *
-     * @throws IOException {@inheritDoc}
      */
     @Override
-    public void setSearchPath(final String... groupNames) throws IOException {
+    public void setSearchPath(final String... groupNames) {
     }
 
     /**
@@ -568,30 +601,67 @@ public final class ChannelDecoder extend
      * groups which have been found in the NetCDF file are returned by this method.
      *
      * @return {@inheritDoc}
-     * @throws IOException {@inheritDoc}
      */
     @Override
-    public String[] getSearchPath() throws IOException {
+    public String[] getSearchPath() {
         return new String[1];
     }
 
     /**
+     * Returns the dimension of the given name (eventually ignoring case), or {@code null} if none.
+     * This method searches in all dimensions found in the NetCDF file, regardless of variables.
+     * The search will ignore case only if no exact match is found for the given name.
+     *
+     * @param  dimName  the name of the dimension to search.
+     * @return dimension of the given name, or {@code null} if none.
+     */
+    final Dimension findDimension(final String dimName) {
+        Dimension dim = dimensionMap.get(dimName);         // Give precedence to exact match before to ignore case.
+        if (dim == null) {
+            final String lower = dimName.toLowerCase(ChannelDecoder.NAME_LOCALE);
+            if (lower != dimName) {                         // Identity comparison is okay here.
+                dim = dimensionMap.get(lower);
+            }
+        }
+        return dim;
+    }
+
+    /**
+     * Returns the NetCDF variable of the given name, or {@code null} if none.
+     *
+     * @param  name  the name of the variable to search, or {@code null}.
+     * @return the attribute value, or {@code null} if none.
+     */
+    final VariableInfo findVariable(final String name) {
+        VariableInfo v = variableMap.get(name);
+        if (v == null && name != null) {
+            final String lower = name.toLowerCase(NAME_LOCALE);
+            // Identity comparison is ok since following check is only an optimization for a common case.
+            if (lower != name) {
+                v = variableMap.get(lower);
+            }
+        }
+        return v;
+    }
+
+    /**
      * Returns the NetCDF attribute of the given name, or {@code null} if none.
      * The {@code name} argument is typically (but is not restricted too) one of
      * the constants defined in the {@link AttributeNames} class.
      *
      * @param  name  the name of the attribute to search, or {@code null}.
-     * @return the attribute, or {@code null} if none.
+     * @return the attribute value, or {@code null} if none.
      */
-    private Attribute findAttribute(final String name) {
-        Attribute attribute = attributeMap.get(name);
-        if (attribute == null && name != null) {
+    private Object findAttribute(final String name) {
+        Object value = attributeMap.get(name);
+        if (value == null && name != null) {
             final String lower = name.toLowerCase(NAME_LOCALE);
-            if (lower != name) { // Identity comparison is ok since this check is only an optimization for a common case.
-                attribute = attributeMap.get(lower);
+            // Identity comparison is ok since following check is only an optimization for a common case.
+            if (lower != name) {
+                value = attributeMap.get(lower);
             }
         }
-        return attribute;
+        return value;
     }
 
     /**
@@ -599,15 +669,11 @@ public final class ChannelDecoder extend
      *
      * @param  name  the name of the attribute to search, or {@code null}.
      * @return the attribute value, or {@code null} if none or empty or if the given name was null.
-     * @throws IOException {@inheritDoc}
      */
     @Override
-    public String stringValue(final String name) throws IOException {
-        final Attribute attribute = findAttribute(name);
-        if (attribute != null) {
-            return attribute.value.toString();
-        }
-        return null;
+    public String stringValue(final String name) {
+        final Object value = findAttribute(name);
+        return (value != null) ? value.toString() : null;
     }
 
     /**
@@ -615,18 +681,15 @@ public final class ChannelDecoder extend
      * If there is more than one numeric value, only the first one is returned.
      *
      * @return {@inheritDoc}
-     * @throws IOException {@inheritDoc}
      */
     @Override
-    public Number numericValue(final String name) throws IOException {
-        final Attribute attribute = findAttribute(name);
-        if (attribute != null && attribute.value != null) {
-            if (attribute.value instanceof String) {
-                return parseNumber((String) attribute.value);
-            }
-            return (Number) Array.get(attribute.value, 0);
+    public Number numericValue(final String name) {
+        final Object value = findAttribute(name);
+        if (value instanceof String) {
+            return parseNumber((String) value);
         }
-        return null;
+        final Number[] v = VariableInfo.numberValues(value);
+        return (v.length != 0) ? v[0] : null;
     }
 
     /**
@@ -634,17 +697,14 @@ public final class ChannelDecoder extend
      * If there is more than one numeric value, only the first one is returned.
      *
      * @return {@inheritDoc}
-     * @throws IOException {@inheritDoc}
      */
     @Override
-    public Date dateValue(final String name) throws IOException {
-        final Attribute attribute = findAttribute(name);
-        if (attribute != null) {
-            if (attribute.value instanceof CharSequence) try {
-                return JDK8.parseDateTime(StandardDateFormat.dateToISO((CharSequence) attribute.value, 0, false));
-            } catch (IllegalArgumentException e) {
-                listeners.warning(null, e);
-            }
+    public Date dateValue(final String name) {
+        final Object value = findAttribute(name);
+        if (value instanceof CharSequence) try {
+            return JDK8.parseDateTime(StandardDateFormat.dateToISO((CharSequence) value, 0, false));
+        } catch (IllegalArgumentException e) {
+            listeners.warning(null, e);
         }
         return null;
     }
@@ -655,10 +715,9 @@ public final class ChannelDecoder extend
      *
      * @param  values  the values to convert. May contain {@code null} elements.
      * @return the converted values. May contain {@code null} elements.
-     * @throws IOException {@inheritDoc}
      */
     @Override
-    public Date[] numberToDate(final String symbol, final Number... values) throws IOException {
+    public Date[] numberToDate(final String symbol, final Number... values) {
         final Date[] dates = new Date[values.length];
         final String[] parts = TIME_UNIT_PATTERN.split(symbol);
         if (parts.length == 2) try {
@@ -681,28 +740,41 @@ public final class ChannelDecoder extend
      * This method returns a direct reference to an internal array - do not modify.
      *
      * @return {@inheritDoc}
-     * @throws IOException {@inheritDoc}
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public Variable[] getVariables() throws IOException {
+    public Variable[] getVariables() {
         return variables;
     }
 
     /**
+     * If this decoder can handle the file content as features, returns handlers for them.
+     *
+     * @return {@inheritDoc}
+     * @throws IOException if an I/O operation was necessary but failed.
+     * @throws DataStoreException if a logical error occurred.
+     */
+    @Override
+    public DiscreteSampling[] getDiscreteSampling() throws IOException, DataStoreException {
+        if ("trajectory".equalsIgnoreCase(stringValue(CF.FEATURE_TYPE))) {
+            return FeaturesInfo.create(this);
+        }
+        return new FeaturesInfo[0];
+    }
+
+    /**
      * Returns all grid geometries found in the NetCDF file.
      * This method returns a direct reference to an internal array - do not modify.
      *
      * @return {@inheritDoc}
-     * @throws IOException {@inheritDoc}
      */
     @Override
     @SuppressWarnings("ReturnOfCollectionOrArrayField")
-    public GridGeometry[] getGridGeometries() throws IOException {
+    public GridGeometry[] getGridGeometries() {
         if (gridGeometries == null) {
             /*
-             * First, find all variables which are used as coordinate system axis. The keys are the
-             * grid dimensions which are the domain of the variable (i.e. the sources of the conversion
+             * First, find all variables which are used as coordinate system axis. The keys in the map are
+             * the grid dimensions which are the domain of the variable (i.e. the sources of the conversion
              * from grid coordinates to CRS coordinates).
              */
             final Map<Dimension, List<VariableInfo>> dimToAxes = new IdentityHashMap<>();
@@ -714,7 +786,7 @@ public final class ChannelDecoder extend
                 }
             }
             /*
-             * For each variables, gets the list of all axes associated to their dimensions. The association
+             * For each variables, get the list of all axes associated to their dimensions. The association
              * is given by the above 'dimToVars' map. More than one variable may have the same dimensions,
              * and consequently the same axes, so we will remember the previously created instances in order
              * to share them.
@@ -772,7 +844,7 @@ nextVar:    for (final VariableInfo vari
     @Override
     public String toString() {
         final StringBuilder buffer = new StringBuilder();
-        buffer.append("SIS driver: “").append(input.filename).append('”');
+        buffer.append("SIS driver: “").append(getFilename()).append('”');
         if (!input.channel.isOpen()) {
             buffer.append(" (closed)");
         }

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/Dimension.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/Dimension.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/Dimension.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/Dimension.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -16,6 +16,7 @@
  */
 package org.apache.sis.internal.netcdf.impl;
 
+import org.apache.sis.internal.netcdf.NamedElement;
 import org.apache.sis.util.Debug;
 
 
@@ -27,10 +28,10 @@ import org.apache.sis.util.Debug;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.7
+ * @version 0.8
  * @module
  */
-final class Dimension {
+final class Dimension extends NamedElement {
     /**
      * The dimension name.
      */
@@ -53,6 +54,14 @@ final class Dimension {
     }
 
     /**
+     * Returns the name of this NetCDF dimension.
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
      * Returns the number of grid cell value along this dimension.
      */
     final long length() {

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/impl/VariableInfo.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -18,9 +18,11 @@ package org.apache.sis.internal.netcdf.i
 
 import java.util.Map;
 import java.io.IOException;
+import java.lang.reflect.Array;
 import ucar.nc2.constants.CF;
 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.storage.ChannelDataInput;
 import org.apache.sis.internal.storage.HyperRectangleReader;
@@ -28,7 +30,9 @@ import org.apache.sis.internal.storage.R
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.util.Numbers;
+import org.apache.sis.math.Vector;
 
 
 /**
@@ -42,6 +46,11 @@ import org.apache.sis.util.Numbers;
  */
 final class VariableInfo extends Variable {
     /**
+     * The array to be returned by {@link #numberValues(Object)} when the given value is null.
+     */
+    private static final Number[] EMPTY = new Number[0];
+
+    /**
      * The names of attributes where to look for the description to be returned by {@link #getDescription()}.
      * We use the same attributes than the one documented in the {@link ucar.nc2.Variable#getDescription()} javadoc.
      */
@@ -53,52 +62,6 @@ final class VariableInfo extends Variabl
     };
 
     /**
-     * The NetCDF type of data. Number of bits and endianness are same as in the Java language except {@code CHAR},
-     * which is defined as an unsigned 8-bits value.
-     */
-    static final int BYTE=1, CHAR=2, SHORT=3, INT=4, FLOAT=5, DOUBLE=6;
-
-    /**
-     * Mapping from the NetCDF data type to the enumeration used by our {@link Numbers} class.
-     */
-    private static final byte[] NUMBER_TYPES = new byte[] {
-        Numbers.BYTE,
-        Numbers.BYTE,       // NOT Numbers.CHARACTER
-        Numbers.SHORT,
-        Numbers.INTEGER,
-        Numbers.FLOAT,
-        Numbers.DOUBLE,
-    };
-
-    /**
-     * The size in bytes of the above constants.
-     *
-     * @see #sizeOf(int)
-     */
-    private static final byte[] SIZES = new byte[] {
-        Byte   .SIZE / Byte.SIZE,
-        Byte   .SIZE / Byte.SIZE,      // NOT Character.BYTES
-        Short  .SIZE / Byte.SIZE,
-        Integer.SIZE / Byte.SIZE,
-        Float  .SIZE / Byte.SIZE,
-        Double .SIZE / Byte.SIZE,
-    };
-
-    /**
-     * The Java primitive type of the above constants.
-     *
-     * @see #getDataType()
-     */
-    private static final Class<?>[] TYPES = new Class<?>[] {
-       byte  .class,
-       char  .class,
-       short .class,
-       int   .class,
-       float .class,
-       double.class
-    };
-
-    /**
      * Helper class for reading a sub-area with a sub-sampling,
      * or {@code null} if {@code dataType} is not a supported type.
      */
@@ -110,24 +73,35 @@ final class VariableInfo extends Variabl
     private final String name;
 
     /**
-     * The dimensions of this variable.
+     * The dimensions of this variable, in order. When iterating over the values stored in this variable
+     * (a flattened one-dimensional sequence of values), index in the domain of {@code dimensions[0]}
+     * varies faster, followed by index in the domain of {@code dimensions[1]}, <i>etc.</i>
      */
     final Dimension[] dimensions;
 
     /**
-     * All dimensions in the NetCDF files.
-     */
-    private final Dimension[] allDimensions;
-
-    /**
      * The attributes associates to the variable, or an empty map if none.
+     * Values can be:
+     *
+     * <ul>
+     *   <li>a {@link String}</li>
+     *   <li>A {@link Number}</li>
+     *   <li>an array of primitive type</li>
+     * </ul>
+     *
+     * If the value is a {@code String}, then leading and trailing spaces and control characters
+     * should be trimmed by {@link String#trim()}.
+     *
+     * @see #stringValues(Object)
+     * @see #numberValues(Object)
+     * @see #booleanValue(Object)
      */
-    private final Map<String,Attribute> attributes;
+    private final Map<String,Object> attributes;
 
     /**
-     * The type of data, as one of the {@code BYTE}, {@code SHORT} and similar constants defined in this class.
+     * The NetCDF type of data, or {@code null} if unknown.
      */
-    private final int dataType;
+    private final DataType dataType;
 
     /**
      * The grid geometry associated to this variable,
@@ -136,36 +110,67 @@ final class VariableInfo extends Variabl
     GridGeometryInfo gridGeometry;
 
     /**
+     * {@code true} if this variable seems to be a coordinate system axis, as determined by comparing its name
+     * with the name of all dimensions in the NetCDF file. This information is computed at construction time
+     * because requested more than once.
+     *
+     * @see #isCoordinateSystemAxis()
+     */
+    private final boolean isCoordinateSystemAxis;
+
+    /**
      * Creates a new variable.
      *
      * @param  input          the channel together with a buffer for reading the variable data.
      * @param  name           the variable name.
      * @param  dimensions     the dimensions of this variable.
-     * @param  allDimensions  all dimensions in the NetCDF files.
      * @param  attributes     the attributes associates to the variable, or an empty map if none.
-     * @param  dataType       the type of data, as one of the {@code BYTE} and similar constants defined in this class.
+     * @param  dataType       the NetCDF type of data, or {@code null} if unknown.
      * @param  size           the variable size, used for verification purpose only.
      * @param  offset         the offset where the variable data begins in the NetCDF file.
      */
-    VariableInfo(final ChannelDataInput input, final String name,
-            final Dimension[] dimensions, final Dimension[] allDimensions,
-            final Map<String,Attribute> attributes, int dataType, final int size, final long offset)
-            throws DataStoreException
+    VariableInfo(final ChannelDataInput      input,
+                 final String                name,
+                 final Dimension[]           dimensions,
+                 final Map<String,Object>    attributes,
+                       DataType              dataType,
+                 final int                   size,
+                 final long                  offset) throws DataStoreException
     {
-        this.name          = name;
-        this.dimensions    = dimensions;
-        this.allDimensions = allDimensions;
-        this.attributes    = attributes;
-        this.dataType      = dataType;
+        final Object isUnsigned = attributes.get(CDM.UNSIGNED);
+        if (isUnsigned != null) {
+            dataType = dataType.unsigned(booleanValue(isUnsigned));
+        }
+        this.name       = name;
+        this.dimensions = dimensions;
+        this.attributes = attributes;
+        this.dataType   = dataType;
         /*
          * 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.
          */
-        if (--dataType >= 0 && dataType < NUMBER_TYPES.length) {
-            reader = new HyperRectangleReader(NUMBER_TYPES[dataType], input, offset);
+        if (dataType != null && dataType.number >= Numbers.BYTE && dataType.number <= Numbers.DOUBLE) {
+            reader = new HyperRectangleReader(dataType.number, input, offset);
         } else {
             reader = null;
         }
+        /*
+         * If the "_CoordinateAliasForDimension" attribute is defined, then its value will be used
+         * instead of the variable name when determining if the variable is a coordinate system axis.
+         * "_CoordinateVariableAlias" seems to be a legacy attribute name for the same purpose.
+         */
+        if (dimensions.length == 1) {
+            Object value = getAttributeValue(_Coordinate.AliasForDimension, "_coordinatealiasfordimension");
+            if (value == null) {
+                value = getAttributeValue("_CoordinateVariableAlias", "_coordinatevariablealias");
+                if (value == null) {
+                    value = name;
+                }
+            }
+            isCoordinateSystemAxis = dimensions[0].name.equals(value);
+        } else {
+            isCoordinateSystemAxis = false;
+        }
     }
 
     /**
@@ -184,9 +189,9 @@ final class VariableInfo extends Variabl
     @Override
     public String getDescription() {
         for (final String attributeName : DESCRIPTION_ATTRIBUTES) {
-            final Attribute attribute = attributes.get(attributeName);
-            if (attribute != null && attribute.value instanceof String) {
-                return (String) attribute.value;
+            final Object value = getAttributeValue(attributeName);
+            if (value instanceof String) {
+                return (String) value;
             }
         }
         return null;
@@ -197,38 +202,18 @@ final class VariableInfo extends Variabl
      */
     @Override
     public String getUnitsString() {
-        final Attribute attribute = attributes.get(CDM.UNITS);
-        if (attribute != null && attribute.value instanceof String) {
-            return (String) attribute.value;
-        }
-        return null;
+        final Object value = getAttributeValue(CDM.UNITS);
+        return (value instanceof String) ? (String) value : null;
     }
 
     /**
-     * Returns the type of data as a Java primitive type if possible,
-     * or {@code null} if the data type is unknown to this method.
+     * Returns the type of data, or {@code UNKNOWN} if the data type is unknown to this method.
+     * If this variable has a {@code "_Unsigned = true"} attribute, then the returned data type
+     * will be a unsigned variant.
      */
     @Override
-    public Class<?> getDataType() {
-        final int i = dataType - 1;
-        return (i >= 0 && i < TYPES.length) ? TYPES[i] : null;
-    }
-
-    /**
-     * Returns the size of the given data type, or 0 if unknown.
-     */
-    static int sizeOf(int datatype) {
-        return (--datatype >= 0 && datatype < SIZES.length) ? SIZES[datatype] : 0;
-    }
-
-    /**
-     * Returns {@code true} if the integer values shall be considered as unsigned.
-     * Current implementation searches for an {@code "_Unsigned = true"} attribute.
-     */
-    @Override
-    public boolean isUnsigned() {
-        final Attribute attribute = attributes.get(CDM.UNSIGNED);
-        return (attribute != null) && attribute.booleanValue();
+    public DataType getDataType() {
+        return dataType;
     }
 
     /**
@@ -237,29 +222,15 @@ final class VariableInfo extends Variabl
      */
     @Override
     public boolean isCoordinateSystemAxis() {
-        String name = this.name;
-        final Attribute attribute = attributes.get(_CoordinateVariableAlias);
-        if (attribute != null && attribute.value instanceof String) {
-            name = (String) attribute.value;
-        }
-        for (final Dimension dimension : allDimensions) {
-            if (name.equals(dimension.name)) {
-                // This variable is a dimension of another variable.
-                return true;
-            }
-        }
-        return false;
+        return isCoordinateSystemAxis;
     }
 
     /**
      * Returns the value of the {@code "_CoordinateAxisType"} attribute, or {@code null} if none.
      */
     final String getAxisType() {
-        final Attribute attribute = attributes.get(_Coordinate.AxisType);
-        if (attribute != null && attribute.value instanceof String) {
-            return (String) attribute.value;
-        }
-        return null;
+        final Object value = getAttributeValue(_Coordinate.AxisType, "_coordinateaxistype");
+        return (value instanceof String) ? (String) value : null;
     }
 
     /**
@@ -291,24 +262,100 @@ final class VariableInfo extends Variabl
     }
 
     /**
+     * Returns the value of the given attribute, or {@code null} if none.
+     * This method should be invoked only for hard-coded names that mix lower-case and upper-case letters.
+     *
+     * @param  attributeName  name of attribute to search, in the expected case.
+     * @param  lowerCase      the all lower-case variant of {@code attributeName}.
+     * @return variable attribute value of the given name, or {@code null} if none.
+     */
+    private Object getAttributeValue(final String attributeName, final String lowerCase) {
+        Object value = attributes.get(attributeName);
+        if (value == null) {
+            value = attributes.get(lowerCase);
+        }
+        return value;
+    }
+
+    /**
+     * Returns the value of the given attribute, or {@code null} if none.
+     * This method does not search the lower-case variant of the given name because the argument given to this method
+     * is usually a hard-coded value from {@link CF} or {@link CDM} conventions, which are already in lower-cases.
+     *
+     * @param  attributeName  name of attribute to search, in the expected case.
+     * @return variable attribute value of the given name, or {@code null} if none.
+     */
+    final Object getAttributeValue(final String attributeName) {
+        return attributes.get(attributeName);
+    }
+
+    /**
      * Returns the sequence of values for the given attribute, or an empty array if none.
      * The elements will be of class {@link String} if {@code numeric} is {@code false},
      * or {@link Number} if {@code numeric} is {@code true}.
      */
     @Override
     public Object[] getAttributeValues(final String attributeName, final boolean numeric) {
-        Attribute attribute = attributes.get(attributeName);
-        if (attribute != null) {
-            return numeric ? attribute.numberValues() : attribute.stringValues();
+        final Object value = getAttributeValue(attributeName);
+        return numeric ? numberValues(value) : stringValues(value);
+    }
+
+    /**
+     * Returns the attribute values as an array of {@link String}s, or an empty array if none.
+     * The given argument is typically a value of the {@link #attributes} map.
+     *
+     * @see #getAttributeValues(String, boolean)
+     */
+    static String[] stringValues(final Object value) {
+        if (value == null) {
+            return CharSequences.EMPTY_ARRAY;
+        }
+        if (value.getClass().isArray()) {
+            final String[] values = new String[Array.getLength(value)];
+            for (int i=0; i<values.length; i++) {
+                values[i] = Array.get(value, i).toString();
+            }
+            return values;
         }
-        return new Object[0];
+        return new String[] {value.toString()};
+    }
+
+    /**
+     * Returns the attribute values as an array of {@link Number}, or an empty array if none.
+     * The given argument is typically a value of the {@link #attributes} map.
+     *
+     * @see #getAttributeValues(String, boolean)
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
+    static Number[] numberValues(final Object value) {
+        if (value != null) {
+            if (value.getClass().isArray()) {
+                final Number[] values = new Number[Array.getLength(value)];
+                for (int i=0; i<values.length; i++) {
+                    values[i] = (Number) Array.get(value, i);
+                }
+                return values;
+            }
+            if (value instanceof Number) {
+                return new Number[] {(Number) value};
+            }
+        }
+        return EMPTY;
+    }
+
+    /**
+     * Returns the attribute value as a boolean, or {@code false} if the attribute is not a boolean.
+     * The given argument is typically a value of the {@link #attributes} map.
+     */
+    private static boolean booleanValue(final Object value) {
+        return (value instanceof String) && Boolean.valueOf((String) value);
     }
 
     /**
      * Reads all the data for this variable and returns them as an array of a Java primitive type.
      */
     @Override
-    public Object read() throws IOException, DataStoreException {
+    public Vector read() throws IOException, DataStoreException {
         if (reader == null) {
             throw new DataStoreContentException(unknownType());
         }
@@ -326,7 +373,7 @@ final class VariableInfo extends Variabl
             sub [i] = 1;
             size[i] = dimensions[(dimension - 1) - i].length();
         }
-        return reader.read(new Region(size, new long[dimension], size, sub));
+        return Vector.create(reader.read(new Region(size, new long[dimension], size, sub)), dataType.isUnsigned);
     }
 
     /**
@@ -338,7 +385,7 @@ final class VariableInfo extends Variabl
      * @return the data as an array of a Java primitive type.
      */
     @Override
-    public Object read(int[] areaLower, int[] areaUpper, int[] subsampling) throws IOException, DataStoreException {
+    public Vector read(int[] areaLower, int[] areaUpper, int[] subsampling) throws IOException, DataStoreException {
         if (reader == null) {
             throw new DataStoreContentException(unknownType());
         }
@@ -373,7 +420,7 @@ final class VariableInfo extends Variabl
             sub  [i] = subsampling[j];
             size [i] = dimensions[j].length();
         }
-        return reader.read(new Region(size, lower, upper, sub));
+        return Vector.create(reader.read(new Region(size, lower, upper, sub)), dataType.isUnsigned);
     }
 
     /**

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/DecoderWrapper.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -19,9 +19,9 @@ package org.apache.sis.internal.netcdf.u
 import java.util.Date;
 import java.util.List;
 import java.util.EnumSet;
+import java.util.Formatter;
 import java.io.IOException;
 import ucar.nc2.Group;
-import ucar.nc2.Dimension;
 import ucar.nc2.Attribute;
 import ucar.nc2.VariableIF;
 import ucar.nc2.NetcdfFile;
@@ -32,12 +32,17 @@ import ucar.nc2.units.DateUnit;
 import ucar.nc2.time.Calendar;
 import ucar.nc2.time.CalendarDate;
 import ucar.nc2.time.CalendarDateFormatter;
+import ucar.nc2.ft.FeatureDataset;
+import ucar.nc2.ft.FeatureDatasetPoint;
+import ucar.nc2.ft.FeatureDatasetFactoryManager;
+import ucar.nc2.ft.FeatureCollection;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.logging.WarningListeners;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.internal.netcdf.GridGeometry;
+import org.apache.sis.internal.netcdf.DiscreteSampling;
 
 
 /**
@@ -45,7 +50,7 @@ import org.apache.sis.internal.netcdf.Gr
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 public final class DecoderWrapper extends Decoder implements CancelTask {
@@ -76,6 +81,11 @@ public final class DecoderWrapper extend
     private transient Variable[] variables;
 
     /**
+     * The discrete sampling features, or {@code null} if none.
+     */
+    private transient FeatureDataset features;
+
+    /**
      * The grid geometries, computed when first needed.
      *
      * @see #getGridGeometries()
@@ -102,12 +112,23 @@ public final class DecoderWrapper extend
      * @param  filename   the name of the NetCDF file from which to read data.
      * @throws IOException if an error occurred while opening the NetCDF file.
      */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")
     public DecoderWrapper(final WarningListeners<?> listeners, final String filename) throws IOException {
         super(listeners);
         file = NetcdfDataset.openDataset(filename, false, this);
     }
 
     /**
+     * Returns a filename for information purpose only. This is used for formatting error messages.
+     *
+     * @return a filename to report in warning or error messages.
+     */
+    @Override
+    public String getFilename() {
+        return file.getLocation();
+    }
+
+    /**
      * Defines the groups where to search for named attributes, in preference order.
      * The {@code null} group name stands for the global attributes.
      */
@@ -299,17 +320,40 @@ public final class DecoderWrapper extend
     @SuppressWarnings({"ReturnOfCollectionOrArrayField", "null"})
     public Variable[] getVariables() {
         if (variables == null) {
-            final List<Dimension> dimensions = file.getDimensions();
             final List<? extends VariableIF> all = file.getVariables();
             variables = new Variable[(all != null) ? all.size() : 0];
             for (int i=0; i<variables.length; i++) {
-                variables[i] = new VariableWrapper(all.get(i), dimensions);
+                variables[i] = new VariableWrapper(all.get(i));
             }
         }
         return variables;
     }
 
     /**
+     * If this decoder can handle the file content as features, returns handlers for them.
+     *
+     * @return {@inheritDoc}
+     * @throws IOException if an I/O operation was necessary but failed.
+     */
+    @Override
+    @SuppressWarnings("null")
+    public DiscreteSampling[] getDiscreteSampling() throws IOException {
+        if (features == null && file instanceof NetcdfDataset) {
+            features = FeatureDatasetFactoryManager.wrap(null, (NetcdfDataset) file, this,
+                    new Formatter(new LogAdapter(listeners), listeners.getLocale()));
+        }
+        List<FeatureCollection> fc = null;
+        if (features instanceof FeatureDatasetPoint) {
+            fc = ((FeatureDatasetPoint) features).getPointFeatureCollectionList();
+        }
+        final FeaturesWrapper[] wrappers = new FeaturesWrapper[(fc != null) ? fc.size() : 0];
+        for (int i=0; i<wrappers.length; i++) {
+            wrappers[i] = new FeaturesWrapper(fc.get(i));
+        }
+        return wrappers;
+    }
+
+    /**
      * Returns all grid geometries (related to coordinate systems) found in the NetCDF file.
      * This method returns a direct reference to an internal array - do not modify.
      *
@@ -375,6 +419,10 @@ public final class DecoderWrapper extend
      */
     @Override
     public void close() throws IOException {
+        if (features != null) {
+            features.close();
+            features = null;
+        }
         file.close();
     }
 
@@ -385,6 +433,6 @@ public final class DecoderWrapper extend
     @Debug
     @Override
     public String toString() {
-        return "UCAR driver: “" + file.getLocation() + '”';
+        return "UCAR driver: “" + getFilename() + '”';
     }
 }

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/ucar/VariableWrapper.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -24,6 +24,8 @@ import ucar.ma2.InvalidRangeException;
 import ucar.nc2.Attribute;
 import ucar.nc2.Dimension;
 import ucar.nc2.VariableIF;
+import org.apache.sis.math.Vector;
+import org.apache.sis.internal.netcdf.DataType;
 import org.apache.sis.internal.netcdf.Variable;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
@@ -45,16 +47,10 @@ final class VariableWrapper extends Vari
     private final VariableIF variable;
 
     /**
-     * The list of all dimensions in the NetCDF file.
-     */
-    private final List<? extends Dimension> all;
-
-    /**
      * Creates a new variable wrapping the given NetCDF interface.
      */
-    VariableWrapper(final VariableIF variable, List<? extends Dimension> all) {
+    VariableWrapper(final VariableIF variable) {
         this.variable = variable;
-        this.all = all;
     }
 
     /**
@@ -82,43 +78,32 @@ final class VariableWrapper extends Vari
     }
 
     /**
-     * Returns the variable data type, as a primitive type if possible.
-     * This method may return {@code null} (UCAR code seems to allow that).
-     */
-    @Override
-    public Class<?> getDataType() {
-        return variable.getDataType().getPrimitiveClassType();
-    }
-
-    /**
-     * Returns {@code true} if the integer values shall be considered as unsigned.
+     * Returns the variable data type.
+     * This method may return {@code UNKNOWN} if the datatype is unknown.
      */
     @Override
-    public boolean isUnsigned() {
-        return variable.isUnsigned();
+    public DataType getDataType() {
+        final DataType type;
+        switch (variable.getDataType()) {
+            case STRING: return DataType.STRING;
+            case CHAR:   return DataType.CHAR;
+            case BYTE:   type = DataType.BYTE;   break;
+            case SHORT:  type = DataType.SHORT;  break;
+            case INT:    type = DataType.INT;    break;
+            case LONG:   type = DataType.INT64;  break;
+            case FLOAT:  return DataType.FLOAT;
+            case DOUBLE: return DataType.DOUBLE;
+            default:     return DataType.UNKNOWN;
+        }
+        return type.unsigned(variable.isUnsigned());
     }
 
     /**
-     * Returns {@code true} if this variable seems to be a coordinate system axis,
-     * determined by comparing its name with the name of all dimensions in the NetCDF file.
+     * Returns {@code true} if this variable seems to be a coordinate system axis.
      */
     @Override
     public boolean isCoordinateSystemAxis() {
-        String name = null;
-        final Attribute attribute = variable.findAttributeIgnoreCase(_CoordinateVariableAlias);
-        if (attribute != null) {
-            name = attribute.getStringValue();
-        }
-        if (name == null) {
-            name = getName();
-        }
-        for (final Dimension dimension : all) {
-            if (name.equals(dimension.getShortName())) {
-                // This variable is a dimension of another variable.
-                return true;
-            }
-        }
-        return false;
+        return variable.isCoordinateVariable();
     }
 
     /**
@@ -182,9 +167,9 @@ final class VariableWrapper extends Vari
      * Reads all the data for this variable and returns them as an array of a Java primitive type.
      */
     @Override
-    public Object read() throws IOException {
+    public Vector read() throws IOException {
         final Array array = variable.read();
-        return array.get1DJavaArray(array.getElementType());
+        return Vector.create(array.get1DJavaArray(array.getElementType()), variable.isUnsigned());
     }
 
     /**
@@ -196,7 +181,7 @@ final class VariableWrapper extends Vari
      * @return the data as an array of a Java primitive type.
      */
     @Override
-    public Object read(final int[] areaLower, final int[] areaUpper, final int[] subsampling)
+    public Vector read(final int[] areaLower, final int[] areaUpper, final int[] subsampling)
             throws IOException, DataStoreException
     {
         final int[] size = new int[areaUpper.length];
@@ -209,6 +194,6 @@ final class VariableWrapper extends Vari
         } catch (InvalidRangeException e) {
             throw new DataStoreContentException(e);
         }
-        return array.get1DJavaArray(array.getElementType());
+        return Vector.create(array.get1DJavaArray(array.getElementType()), variable.isUnsigned());
     }
 }

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/AttributeNames.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -17,14 +17,16 @@
 package org.apache.sis.storage.netcdf;
 
 /*
- * All imports below except "CF" are for javadoc only. The "CF" import is used only
- * for its static final String constants, which are inlined by javac. Consequently
- * the compiled file of this class should have no dependency to the UCAR packages.
+ * All imports below except "CF" and "ACDD" are for javadoc only.
+ * The "CF" and "ACDD" imports are used only for its static final String constants,
+ * which are inlined by javac. Consequently the compiled file of this class should
+ * have no dependency to the UCAR packages.
  */
 import java.io.Serializable;
 import ucar.nc2.NetcdfFile;
 import ucar.nc2.VariableSimpleIF;
 import ucar.nc2.constants.CF;
+import ucar.nc2.constants.ACDD;
 import org.opengis.metadata.Metadata;
 import org.opengis.metadata.Identifier;
 import org.opengis.metadata.citation.*;
@@ -128,7 +130,7 @@ import org.opengis.metadata.extent.Geogr
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 public class AttributeNames {
@@ -145,7 +147,7 @@ public class AttributeNames {
      * @see NetcdfFile#getTitle()
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#title_Attribute">UCAR reference</a>
      */
-    public static final String TITLE = "title";
+    public static final String TITLE = ACDD.title;
 
     /**
      * The {@value} attribute name for a paragraph describing the dataset
@@ -157,7 +159,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#summary_Attribute">UCAR reference</a>
      */
-    public static final String SUMMARY = "summary";
+    public static final String SUMMARY = ACDD.summary;
 
     /**
      * The {@value} attribute name for an identifier (<em>Recommended</em>).
@@ -176,7 +178,7 @@ public class AttributeNames {
      * @see NetcdfFile#getId()
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#id_Attribute">UCAR reference</a>
      */
-    public static final String IDENTIFIER = "id";
+    public static final String IDENTIFIER = ACDD.id;
 
     /**
      * The {@value} attribute name for the identifier authority (<em>Recommended</em>).
@@ -195,7 +197,7 @@ public class AttributeNames {
      * @see MetadataReader#getFileIdentifier()
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#id_Attribute">UCAR reference</a>
      */
-    public static final String NAMING_AUTHORITY = "naming_authority";
+    public static final String NAMING_AUTHORITY = ACDD.naming_authority;
 
     /**
      * The {@value} attribute name for a long descriptive name for the variable taken from a controlled
@@ -227,7 +229,7 @@ public class AttributeNames {
      * @see #VOCABULARY
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#standard_name_vocabulary_Attribute">UCAR reference</a>
      */
-    public static final String STANDARD_NAME_VOCABULARY = "standard_name_vocabulary";
+    public static final String STANDARD_NAME_VOCABULARY = ACDD.standard_name_vocabulary;
 
     /**
      * The {@value} attribute name for a comma separated list of key words and phrases
@@ -242,7 +244,7 @@ public class AttributeNames {
      * @see #STANDARD_NAME
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#keywords_Attribute">UCAR reference</a>
      */
-    public static final String KEYWORDS = "keywords";
+    public static final String KEYWORDS = ACDD.keywords;
 
     /**
      * The {@value} attribute name for the guideline for the words/phrases in the
@@ -258,7 +260,7 @@ public class AttributeNames {
      * @see #STANDARD_NAME_VOCABULARY
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#keywords_vocabulary_Attribute">UCAR reference</a>
      */
-    public static final String VOCABULARY = "keywords_vocabulary";
+    public static final String VOCABULARY = ACDD.keywords_vocabulary;
 
     /**
      * The {@value} attribute name for a high-level geographic data thematic classification.
@@ -291,7 +293,7 @@ public class AttributeNames {
      * @see SpatialRepresentationType
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#cdm_data_type_Attribute">UCAR reference</a>
      */
-    public static final String DATA_TYPE = "cdm_data_type";
+    public static final String DATA_TYPE = ACDD.cdm_data_type;
 
     /**
      * The {@value} attribute name for providing an audit trail for modifications to the
@@ -304,7 +306,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#history_Attribute">UCAR reference</a>
      */
-    public static final String HISTORY = "history";
+    public static final String HISTORY = ACDD.history;
 
     /**
      * The {@value} attribute name for miscellaneous information about the data
@@ -316,7 +318,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#comment_Attribute">UCAR reference</a>
      */
-    public static final String COMMENT = "comment";
+    public static final String COMMENT = ACDD.comment;
 
     /**
      * The {@value} attribute name for the date on which the metadata was created
@@ -340,7 +342,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#date_created_Attribute">UCAR reference</a>
      */
-    public static final String DATE_CREATED = "date_created";
+    public static final String DATE_CREATED = ACDD.date_created;
 
     /**
      * The {@value} attribute name for the date on which this data was last modified
@@ -354,7 +356,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#date_modified_Attribute">UCAR reference</a>
      */
-    public static final String DATE_MODIFIED = "date_modified";
+    public static final String DATE_MODIFIED = ACDD.date_modified;
 
     /**
      * The {@value} attribute name for a date on which this data was formally issued
@@ -523,8 +525,8 @@ public class AttributeNames {
      * @see #PUBLISHER
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#creator_name_Attribute">UCAR reference</a>
      */
-    public static final Responsible CREATOR = new Responsible("creator_name",
-            "institution", "creator_url", "creator_email", null, Role.ORIGINATOR);
+    public static final Responsible CREATOR = new Responsible(ACDD.creator_name,
+            "institution", ACDD.creator_url, ACDD.creator_email, null, Role.ORIGINATOR);
 
     /**
      * The set of attribute names for the contributor (<em>Suggested</em>).
@@ -556,8 +558,8 @@ public class AttributeNames {
      * @see #CONTRIBUTOR
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#publisher_name_Attribute">UCAR reference</a>
      */
-    public static final Responsible PUBLISHER = new Responsible("publisher_name",
-            null, "publisher_url", "publisher_email", null, Role.PUBLISHER);
+    public static final Responsible PUBLISHER = new Responsible(ACDD.publisher_name,
+            null, ACDD.publisher_url, ACDD.publisher_email, null, Role.PUBLISHER);
 
     /**
      * The {@value} attribute name for the scientific project that produced the data
@@ -602,7 +604,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#processing_level_Attribute">UCAR reference</a>
      */
-    public static final String PROCESSING_LEVEL = "processing_level";
+    public static final String PROCESSING_LEVEL = ACDD.processing_level;
 
     /**
      * The {@value} attribute name for a place to acknowledge various type of support for
@@ -627,7 +629,7 @@ public class AttributeNames {
      *
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#license_Attribute">UCAR reference</a>
      */
-    public static final String LICENSE = "license";
+    public static final String LICENSE = ACDD.license;
 
     /**
      * The {@value} attribute name for the access constraints applied to assure the protection of
@@ -831,8 +833,7 @@ public class AttributeNames {
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#geospatial_lat_min_Attribute">UCAR reference</a>
      */
     public static final Dimension LATITUDE = new Dimension(DimensionNameType.ROW,
-            "geospatial_lat_min", "geospatial_lat_max", null,
-            "geospatial_lat_resolution", "geospatial_lat_units", null);
+            ACDD.LAT_MIN, ACDD.LAT_MAX, null, ACDD.LAT_RESOLUTION, ACDD.LAT_UNITS, null);
 
     /**
      * The set of attribute names for the minimal and maximal longitudes of the bounding box,
@@ -856,8 +857,7 @@ public class AttributeNames {
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#geospatial_lon_min_Attribute">UCAR reference</a>
      */
     public static final Dimension LONGITUDE = new Dimension(DimensionNameType.COLUMN,
-            "geospatial_lon_min", "geospatial_lon_max", null,
-            "geospatial_lon_resolution", "geospatial_lon_units", null);
+            ACDD.LON_MIN, ACDD.LON_MAX, null, ACDD.LON_RESOLUTION, ACDD.LON_UNITS, null);
 
     /**
      * The set of attribute names for the minimal and maximal elevations of the bounding box,
@@ -881,8 +881,7 @@ public class AttributeNames {
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#geospatial_vertical_min_Attribute">UCAR reference</a>
      */
     public static final Dimension VERTICAL = new Dimension(DimensionNameType.VERTICAL,
-            "geospatial_vertical_min", "geospatial_vertical_max", null,
-            "geospatial_vertical_resolution", "geospatial_vertical_units", "geospatial_vertical_positive");
+            ACDD.VERT_MIN, ACDD.VERT_MAX, null, ACDD.VERT_RESOLUTION, ACDD.VERT_UNITS, ACDD.VERT_IS_POSITIVE);
 
     /**
      * The set of attribute names for the start and end times of the bounding box, resolution and
@@ -904,8 +903,7 @@ public class AttributeNames {
      * @see <a href="http://www.unidata.ucar.edu/software/netcdf-java/formats/DataDiscoveryAttConvention.html#time_coverage_start_Attribute">UCAR reference</a>
      */
     public static final Dimension TIME = new Dimension(DimensionNameType.TIME,
-            "time_coverage_start", "time_coverage_end", "time_coverage_duration",
-            "time_coverage_resolution", "time_coverage_units", null);
+            ACDD.TIME_START, ACDD.TIME_END, ACDD.TIME_DURATION, ACDD.TIME_RESOLUTION, "time_coverage_units", null);
 
     /**
      * The {@value} attribute name for the designation associated with a range element.
@@ -932,9 +930,9 @@ public class AttributeNames {
     public static final String FLAG_MASKS = "flag_masks";
 
     /**
-     * The {@value} attribute name for sample values to be flagged. The {@linkplain #FLAG_MASKS
-     * flag masks}, flag values and {@linkplain #FLAG_MEANINGS flag meaning} attributes, used
-     * together, describe a blend of independent boolean conditions and enumerated status codes.
+     * The {@value} attribute name for sample values to be flagged. The {@linkplain #FLAG_MASKS flag masks},
+     * flag values and {@linkplain #FLAG_MEANINGS flag meaning} attributes, used together, describe a blend
+     * of independent boolean conditions and enumerated status codes.
      * A flagged condition is identified by a bitwise AND of the variable value and each flag masks
      * value; a result that matches the flag values value indicates a true condition.
      *

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/MetadataReader.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -451,7 +451,7 @@ final class MetadataReader {
                 if (resource == null) resource = createOnlineResource(url);
                 contact = createContact(address, resource);
             }
-            if (individualName != null || organisationName != null || contact != null) { // Do not test role.
+            if (individualName != null || organisationName != null || contact != null) {        // Do not test role.
                 AbstractParty party = null;
                 if (individualName   != null) party = new DefaultIndividual(individualName, null, null);
                 if (organisationName != null) party = new DefaultOrganisation(organisationName, null, (DefaultIndividual) party, null);
@@ -474,9 +474,9 @@ final class MetadataReader {
     private Citation createCitation(final Identifier identifier) throws IOException {
         String title = stringValue(TITLE);
         if (title == null) {
-            title = stringValue("full_name"); // THREDDS attribute documented in TITLE javadoc.
+            title = stringValue("full_name");   // THREDDS attribute documented in TITLE javadoc.
             if (title == null) {
-                title = stringValue("name"); // THREDDS attribute documented in TITLE javadoc.
+                title = stringValue("name");    // THREDDS attribute documented in TITLE javadoc.
                 if (title == null) {
                     title = decoder.getTitle();
                 }
@@ -853,7 +853,8 @@ final class MetadataReader {
         String name = variable.getName();
         if (name != null && !(name = name.trim()).isEmpty()) {
             if (nameFactory == null) {
-                nameFactory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class); // Real dependency injection to be used in a future version.
+                nameFactory = DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class);
+                // Real dependency injection to be used in a future version.
             }
             band.setSequenceIdentifier(nameFactory.createMemberName(null, name,
                     nameFactory.createTypeName(null, variable.getDataTypeName())));

Modified: sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/main/java/org/apache/sis/storage/netcdf/NetcdfStore.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -28,6 +28,9 @@ import org.apache.sis.storage.DataStoreC
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.internal.netcdf.Decoder;
 import org.apache.sis.metadata.ModifiableMetadata;
+import org.apache.sis.util.CharSequences;
+import org.apache.sis.util.Version;
+import ucar.nc2.constants.CDM;
 
 
 /**
@@ -97,6 +100,25 @@ public class NetcdfStore extends DataSto
     }
 
     /**
+     * Returns the version number of the Climate and Forecast (CF) conventions used in the NetCDF file.
+     * The use of CF convention is mandated by the OGC 11-165r2 standard
+     * (<cite>CF-netCDF3 Data Model Extension standard</cite>).
+     *
+     * @return CF-convention version, or {@code null} if no information about CF convention has been found.
+     * @throws DataStoreException if an error occurred while reading the data.
+     *
+     * @since 0.8
+     */
+    public synchronized Version getConventionVersion() throws DataStoreException {
+        for (final CharSequence value : CharSequences.split(decoder.stringValue(CDM.CONVENTIONS), ',')) {
+            if (CharSequences.regionMatches(value, 0, "CF-", true)) {
+                return new Version(value.subSequence(3, value.length()).toString());
+            }
+        }
+        return null;
+    }
+
+    /**
      * Closes this NetCDF store and releases any underlying resources.
      *
      * @throws DataStoreException if an error occurred while closing the NetCDF file.

Modified: sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/DecoderTest.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -34,7 +34,7 @@ import static org.apache.sis.storage.net
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3
- * @version 0.3
+ * @version 0.8
  * @module
  */
 public strictfp class DecoderTest extends TestCase {
@@ -112,4 +112,22 @@ public strictfp class DecoderTest extend
             date("1993-04-10 00:00:00")
         }, decoder.numberToDate("days since 1970-01-01T00:00:00Z", 8.75, -2.75, 8500));
     }
+
+    /**
+     * Tests {@link Decoder#getTitle()} and {@link Decoder#getId()}.
+     *
+     * @throws IOException if an I/O error occurred while opening the file.
+     * @throws DataStoreException if a logical error occurred.
+     */
+    @Test
+    public void testGetTitleAndID() throws IOException, DataStoreException {
+        final Decoder decoder = selectDataset(NCEP);
+        /*
+         * Actually we really want a null value, even if the NCEP file contains 'title' and 'id' attributes,
+         * because the decoder methods are supposed to check only for the "_Title" and "_Id" attributes as a
+         * last resort fallback when MetadataReader failed to find the title and identifier by itself.
+         */
+        assertNull("title", decoder.getTitle());
+        assertNull("id",    decoder.getId());
+    }
 }

Modified: sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridGeometryTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridGeometryTest.java?rev=1764119&r1=1764118&r2=1764119&view=diff
==============================================================================
--- sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridGeometryTest.java [UTF-8] (original)
+++ sis/trunk/storage/sis-netcdf/src/test/java/org/apache/sis/internal/netcdf/GridGeometryTest.java [UTF-8] Mon Oct 10 15:24:03 2016
@@ -46,8 +46,8 @@ public strictfp class GridGeometryTest e
      * {@code GridGeometryInfoTest} in order to ignore one-dimensional coordinate systems created
      * by {@code GridGeometry} but not by the UCAR library.
      *
-     * @param  geometries The grid geometries created by {@link Decoder}.
-     * @return The grid geometries to test.
+     * @param  geometries  the grid geometries created by {@link Decoder}.
+     * @return the grid geometries to test.
      */
     protected GridGeometry[] filter(final GridGeometry[] geometries) {
         return geometries;



Mime
View raw message