sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1762647 [10/10] - in /sis/trunk: ./ application/sis-webapp/src/test/ core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-metadata/src/main/java/org/apache/sis/metadata/ core/sis-metadata/src/main/java/org/apache/sis/metadata/is...
Date Wed, 28 Sep 2016 13:01:40 GMT
Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -20,7 +20,6 @@ import java.util.List;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.Locale;
 import java.util.logging.Level;
 import java.util.logging.LogRecord;
@@ -36,6 +35,7 @@ import javax.measure.unit.NonSI;
 import javax.measure.quantity.Duration;
 import org.opengis.metadata.Metadata;
 import org.opengis.util.FactoryException;
+import org.opengis.metadata.maintenance.ScopeCode;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.TemporalCRS;
 import org.opengis.referencing.operation.TransformException;
@@ -57,12 +57,17 @@ import org.apache.sis.util.ObjectConvert
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.resources.IndexedResourceBundle;
+import org.apache.sis.util.collection.BackingStoreException;
 
 // Branch-dependent imports
 import org.apache.sis.internal.jdk8.Instant;
 import org.apache.sis.feature.AbstractFeature;
 import org.apache.sis.feature.AbstractIdentifiedType;
 import org.apache.sis.internal.jdk8.DateTimeException;
+import org.apache.sis.internal.jdk8.Consumer;
+import org.apache.sis.internal.jdk8.Spliterator;
+import org.apache.sis.internal.jdk8.Stream;
+import org.apache.sis.internal.jdk8.StreamSupport;
 import org.apache.sis.setup.OptionKey;
 
 
@@ -85,7 +90,7 @@ public final class Store extends DataSto
     /**
      * The character at the beginning of metadata lines.
      */
-    private static final char METADATA = '@';
+    static final char METADATA = '@';
 
     /**
      * The quote character. Quotes inside quoted texts must be doubled.
@@ -95,12 +100,12 @@ public final class Store extends DataSto
     /**
      * The column separator.
      */
-    private static final char SEPARATOR = ',';
+    static final char SEPARATOR = ',';
 
     /**
      * The separator between ordinate values in a coordinate.
      */
-    private static final char ORDINATE_SEPARATOR = ' ';
+    static final char ORDINATE_SEPARATOR = ' ';
 
     /**
      * The prefix for elements in the {@code @columns} line that specify the data type.
@@ -109,9 +114,9 @@ public final class Store extends DataSto
     private static final String TYPE_PREFIX = "xsd:";
 
     /**
-     * The file name.
+     * The file name, used for reporting error messages.
      */
-    private final String name;
+    private final String filename;
 
     /**
      * The reader, set by the constructor and cleared when no longer needed.
@@ -120,6 +125,8 @@ public final class Store extends DataSto
 
     /**
      * The character encoding, or {@code null} if unspecified (in which case the platform default is assumed).
+     * Note that the default value is different than the moving feature specification, which requires UTF-8.
+     * See "Departures from Moving Features specification" in package javadoc.
      */
     private final Charset encoding;
 
@@ -142,14 +149,7 @@ public final class Store extends DataSto
      *
      * @see #parseFeatureType(List)
      */
-    private final DefaultFeatureType featureType;
-
-    /**
-     * The features created while parsing the CSV file.
-     *
-     * @todo We should not keep them in memory, but instead use some kind of iterator or stream.
-     */
-    private final List<AbstractFeature> features;
+    final DefaultFeatureType featureType;
 
     /**
      * {@code true} if {@link #featureType} contains a trajectory column.
@@ -157,11 +157,11 @@ public final class Store extends DataSto
     private boolean hasTrajectories;
 
     /**
-     * Appearing order of trajectories, or {@code null} if unspecified.
+     * Appearing order of trajectories (time or sequential), or {@code null} if unspecified.
      *
      * @see #parseFoliation(List)
      */
-    private final Foliation foliation;
+    final Foliation foliation;
 
     /**
      * Specifies how time is encoded in the CSV file, or {@code null} if there is no time.
@@ -174,15 +174,15 @@ public final class Store extends DataSto
      * <p>If the CSV file is known to be a Moving Feature file, then the given connector should
      * have an {@link org.apache.sis.setup.OptionKey#ENCODING} associated to the UTF-8 value.</p>
      *
-     * @param  connector Information about the storage (URL, stream, <i>etc</i>).
+     * @param  connector  information about the storage (URL, stream, <i>etc</i>).
      * @throws DataStoreException if an error occurred while opening the stream.
      */
     public Store(final StorageConnector connector) throws DataStoreException {
-        name = connector.getStorageName();
+        filename = connector.getStorageName();
         final Reader r = connector.getStorageAs(Reader.class);
         connector.closeAllExcept(r);
         if (r == null) {
-            throw new DataStoreException(Errors.format(Errors.Keys.CanNotOpen_1, name));
+            throw new DataStoreException(Errors.format(Errors.Keys.CanNotOpen_1, filename));
         }
         source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r);
         GeneralEnvelope envelope = null;
@@ -213,9 +213,6 @@ public final class Store extends DataSto
                             throw new DataStoreContentException(duplicated("@columns"));
                         }
                         featureType = parseFeatureType(elements);
-                        if (foliation == null) {
-                            foliation = Foliation.TIME;
-                        }
                         break;
                     }
                     case "@foliation": {
@@ -238,13 +235,12 @@ public final class Store extends DataSto
             }
             source.reset();
         } catch (IOException | FactoryException | IllegalArgumentException | DateTimeException e) {
-            throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", name), e);
+            throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", filename), e);
         }
         this.encoding    = connector.getOption(OptionKey.ENCODING);
         this.envelope    = envelope;
         this.featureType = featureType;
         this.foliation   = foliation;
-        this.features    = new ArrayList<>();
     }
 
     /**
@@ -256,49 +252,57 @@ public final class Store extends DataSto
      *   &#64;stboundedby, urn:ogc:def:crs:CRS:1.3:84, 2D, 50.23 9.23, 50.31 9.27, 2012-01-17T12:33:41Z, 2012-01-17T12:37:00Z, sec
      * }
      *
-     * @param  elements The line elements. The first elements should be {@code "@stboundedby"}.
-     * @return The envelope, or {@code null} if the given list does not contain enough elements.
+     * @param  elements  the line elements. The first elements should be {@code "@stboundedby"}.
+     * @return the envelope, or {@code null} if the given list does not contain enough elements.
      */
     @SuppressWarnings("fallthrough")
     private GeneralEnvelope parseEnvelope(final List<String> elements) throws DataStoreException, FactoryException {
-        double[]       lowerCorner    = null;
-        double[]       upperCorner    = null;
+        CoordinateReferenceSystem crs = null;
+        int spatialDimensionCount     = 2;
+        double[]       lowerCorner    = ArraysExt.EMPTY_DOUBLE;
+        double[]       upperCorner    = ArraysExt.EMPTY_DOUBLE;
         Instant        startTime      = null;
         Instant        endTime        = null;
         Unit<Duration> timeUnit       = SI.SECOND;
         boolean        isTimeAbsolute = false;
-        boolean        is3D           = false;
-        CoordinateReferenceSystem crs = null;
-        GeneralEnvelope envelope      = null;
-        switch (elements.size()) {
-            default:final String unit = elements.get(7);
-                    switch (unit.toLowerCase(Locale.US)) {
-                        case "":
-                        case "sec":
-                        case "second":   /* Already SI.SECOND. */ break;
-                        case "minute":   timeUnit = NonSI.MINUTE; break;
-                        case "hour":     timeUnit = NonSI.HOUR;   break;
-                        case "day":      timeUnit = NonSI.DAY;    break;
-                        case "absolute": isTimeAbsolute = true;   break;
-                        default: throw new DataStoreContentException(errors().getString(Errors.Keys.UnknownUnit_1, unit));
-                    }
-                    // Fall through
-            case 7: endTime     = Instant      .parse(       elements.get(6));
-            case 6: startTime   = Instant      .parse(       elements.get(5));
-            case 5: upperCorner = CharSequences.parseDoubles(elements.get(4), ORDINATE_SEPARATOR);
-            case 4: lowerCorner = CharSequences.parseDoubles(elements.get(3), ORDINATE_SEPARATOR);
-            case 3: final String dimension = elements.get(2);
-                    switch (dimension.toUpperCase(Locale.US)) {
-                        case "":   // Default to 2D.
-                        case "2D": break;
-                        case "3D": is3D = true; break;
-                        default: throw new DataStoreContentException(errors().getString(
-                                        Errors.Keys.IllegalCoordinateSystem_1, dimension));
-                    }
-                    // Fall through
-            case 2: crs = CRS.forCode(elements.get(1));
-            case 1:
-            case 0:
+        int ordinal = -1;
+        for (final String element : elements) {
+            ordinal++;
+            if (!element.isEmpty()) {
+                switch (ordinal) {
+                    case 0: continue;                                       // The "@stboundedby" header.
+                    case 1: crs = CRS.forCode(element); continue;
+                    case 2: if (element.length() == 2 && Character.toUpperCase(element.charAt(1)) == 'D') {
+                                spatialDimensionCount = element.charAt(0) - '0';
+                                if (spatialDimensionCount < 2 || spatialDimensionCount > 3) {
+                                    throw new DataStoreContentException(errors().getString(
+                                        Errors.Keys.IllegalCoordinateSystem_1, element));
+                                }
+                                continue;
+                            }
+                            /*
+                             * According the Moving Feature specification, the [dim] element is optional.
+                             * If we did not recognized the dimension, assume that we have the next element
+                             * (i.e. the lower corner). Fall-through so we can process it.
+                             */
+                            ordinal++;  // Fall through
+                    case 3: lowerCorner = CharSequences.parseDoubles(element, ORDINATE_SEPARATOR); continue;
+                    case 4: upperCorner = CharSequences.parseDoubles(element, ORDINATE_SEPARATOR); continue;
+                    case 5: startTime   = Instant.parse(element); continue;
+                    case 6: endTime     = Instant.parse(element); continue;
+                    case 7: switch (element.toLowerCase(Locale.US)) {
+                                case "sec":
+                                case "second":   /* Already SI.SECOND. */ continue;
+                                case "minute":   timeUnit = NonSI.MINUTE; continue;
+                                case "hour":     timeUnit = NonSI.HOUR;   continue;
+                                case "day":      timeUnit = NonSI.DAY;    continue;
+                                case "absolute": isTimeAbsolute = true;   continue;
+                                default: throw new DataStoreContentException(errors().getString(Errors.Keys.UnknownUnit_1, element));
+                            }
+                }
+                // If we reach this point, there is some remaining unknown elements. Ignore them.
+                break;
+            }
         }
         /*
          * Complete the CRS by adding a vertical component if needed, then a temporal component.
@@ -312,15 +316,19 @@ public final class Store extends DataSto
          *   Assumed never part of the authority code. We need to build the temporal component ourselves
          *   in order to set the origin to the start time.
          */
+        final GeneralEnvelope envelope;
         if (crs != null) {
-            final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3];
-            final int spatialDimension = crs.getCoordinateSystem().getDimension();
             int count = 0;
+            final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3];
             components[count++] = crs;
-            if (is3D && spatialDimension == 2) {
+
+            // If the coordinates are three-dimensional but the CRS is 2D, add a vertical axis.
+            if (spatialDimensionCount >= 3 && crs.getCoordinateSystem().getDimension() == 2) {
                 components[count++] = CommonCRS.Vertical.MEAN_SEA_LEVEL.crs();
             }
+            // Add a temporal axis if we have a start time (no need for end time).
             final GeodeticObjectBuilder builder = new GeodeticObjectBuilder();
+            String name = crs.getName().getCode();
             if (startTime != null) {
                 final TemporalCRS temporal;
                 if (isTimeAbsolute) {
@@ -331,29 +339,36 @@ public final class Store extends DataSto
                     timeEncoding = new TimeEncoding(temporal.getDatum(), timeUnit);
                 }
                 components[count++] = temporal;
+                name = name + " + " + temporal.getName().getCode();
             }
             crs = builder.addName(name).createCompoundCRS(ArraysExt.resize(components, count));
+            envelope = new GeneralEnvelope(crs);
+        } else {
             /*
-             * At this point we got the three- or four-dimensional spatio-temporal CRS.
-             * We can now set the envelope coordinate values.
+             * While illegal in principle, Apache SIS accepts missing CRS.
+             * In such case, use only the number of dimensions.
              */
-            envelope = new GeneralEnvelope(crs);
-            if (lowerCorner != null && upperCorner != null) {
-                int dim;
-                if ((dim = lowerCorner.length) != spatialDimension ||
-                    (dim = upperCorner.length) != spatialDimension)
-                {
-                    throw new DataStoreContentException(errors().getString(
-                            Errors.Keys.MismatchedDimension_2, dim, spatialDimension));
-                }
-                for (int i=0; i<spatialDimension; i++) {
-                    envelope.setRange(i, lowerCorner[i], upperCorner[i]);
-                }
-            }
-            if (startTime != null && endTime != null) {
-                envelope.setRange(spatialDimension, timeEncoding.toCRS(startTime.toEpochMilli()),
-                                                    timeEncoding.toCRS(endTime.toEpochMilli()));
-            }
+            int dim = spatialDimensionCount;
+            if (startTime != null) dim++;           // Same criterion than in above block.
+            envelope = new GeneralEnvelope(dim);
+        }
+        /*
+         * At this point we got the three- or four-dimensional spatio-temporal CRS.
+         * We can now set the envelope coordinate values, including temporal values.
+         */
+        int dim;
+        if ((dim = lowerCorner.length) != spatialDimensionCount ||
+            (dim = upperCorner.length) != spatialDimensionCount)
+        {
+            throw new DataStoreContentException(errors().getString(
+                    Errors.Keys.MismatchedDimension_2, dim, spatialDimensionCount));
+        }
+        for (int i=0; i<spatialDimensionCount; i++) {
+            envelope.setRange(i, lowerCorner[i], upperCorner[i]);
+        }
+        if (startTime != null) {
+            envelope.setRange(spatialDimensionCount, timeEncoding.toCRS(startTime.toEpochMilli()),
+                    (endTime == null) ? Double.NaN : timeEncoding.toCRS(endTime.toEpochMilli()));
         }
         return envelope;
     }
@@ -367,8 +382,8 @@ public final class Store extends DataSto
      *   &#64;columns, mfidref, trajectory, state,xsd:token, "type code",xsd:integer
      * }
      *
-     * @param  elements The line elements. The first elements should be {@code "@columns"}.
-     * @return The column metadata, or {@code null} if the given list does not contain enough elements.
+     * @param  elements  the line elements. The first elements should be {@code "@columns"}.
+     * @return the column metadata, or {@code null} if the given list does not contain enough elements.
      */
     private DefaultFeatureType parseFeatureType(final List<String> elements) throws DataStoreException {
         final int size = elements.size();
@@ -423,6 +438,11 @@ public final class Store extends DataSto
             }
             properties.add(createProperty(name, type, minOccurrence));
         }
+        String name = filename;
+        final int s = name.lastIndexOf('.');
+        if (s > 0) {                            // Exclude 0 because shall not be the first character.
+            name = name.substring(0, s);
+        }
         return new DefaultFeatureType(Collections.singletonMap(DefaultFeatureType.NAME_KEY, name),
                 false, null, properties.toArray(new AbstractIdentifiedType[properties.size()]));
     }
@@ -436,15 +456,15 @@ public final class Store extends DataSto
 
     /**
      * Parses the metadata described by the header line starting with {@code @foliation}.
-     * The value returned by this method will be stored in the {@link #order} field.
+     * The value returned by this method will be stored in the {@link #foliation} field.
      *
      * <p>Example:</p>
      * {@preformat text
      *   &#64;foliation,Sequential
      * }
      *
-     * @param  elements The line elements. The first elements should be {@code "@foliation"}.
-     * @return The foliation metadata.
+     * @param  elements  the line elements. The first elements should be {@code "@foliation"}.
+     * @return the foliation metadata.
      */
     private Foliation parseFoliation(final List<String> elements) {
         if (elements.size() >= 2) {
@@ -456,133 +476,223 @@ public final class Store extends DataSto
     /**
      * Returns the metadata associated to the CSV file, or {@code null} if none.
      *
-     * @return The metadata associated to the CSV file, or {@code null} if none.
+     * @return the metadata associated to the CSV file, or {@code null} if none.
      * @throws DataStoreException if an error occurred during the parsing process.
      */
     @Override
-    public Metadata getMetadata() throws DataStoreException {
+    public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) {
             final MetadataBuilder builder = new MetadataBuilder();
             builder.add(encoding);
+            builder.addFormat(timeEncoding != null && hasTrajectories ? "CSV/MF" : "CSV");
+            builder.add(ScopeCode.DATASET);
             try {
-                builder.add(envelope);
+                builder.addExtent(envelope);
             } catch (TransformException e) {
-                throw new DataStoreContentException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", name), e);
+                throw new DataStoreContentException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", filename), e);
+            } catch (UnsupportedOperationException e) {
+                // Failed to set the temporal components if the sis-temporal module was
+                // not on the classpath, but the other dimensions still have been set.
+                listeners.warning(null, e);
             }
-            metadata = builder.result();
+            builder.add(featureType, null);
+            metadata = builder.build(true);
         }
         return metadata;
     }
 
     /**
-     * Returns an iterator over the features.
+     * Returns the stream of features.
      *
-     * @todo THIS IS AN EXPERIMENTAL API. We may change the return type to {@link java.util.stream.Stream} later.
-     * @todo Current implementation is inefficient. We should not parse all features immediately.
+     * @return a stream over all features in the CSV file.
      *
-     * @return An iterator over all features in the CSV file.
-     * @throws DataStoreException if an error occurred while creating the iterator.
+     * @todo Needs to reset the position when doing another pass on the features.
+     */
+    public Stream<AbstractFeature> getFeatures() {
+        return StreamSupport.stream(new Iter(), false);
+    }
+
+    /**
+     * Implementation of the iterator returned by {@link #getFeatures()}.
      */
-    @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
-    public Iterator<AbstractFeature> getFeatures() throws DataStoreException {
-        if (features.isEmpty()) try {
-            final Collection<? extends AbstractIdentifiedType> properties = featureType.getProperties(false);
-            final ObjectConverter<String,?>[] converters = new ObjectConverter[properties.size()];
-            final String[]     propertyNames   = new String[converters.length];
-            final boolean      hasTrajectories = this.hasTrajectories;
-            final TimeEncoding timeEncoding    = this.timeEncoding;
-            final List<String> values          = new ArrayList<>();
+    private final class Iter extends Spliterator<AbstractFeature> {
+        /**
+         * Converters from string representations to the values to store in the {@link #values} array.
+         */
+        private final ObjectConverter<String,?>[] converters;
+
+        /**
+         * All values found in a row. We need to remember those values between different executions
+         * of the {@link #tryAdvance(Consumer)} method because the Moving Feature Specification said:
+         * "If the value equals the previous value, the text for the value can be omitted."
+         */
+        private final Object[] values;
+
+        /**
+         * Name of the property where to store a value.
+         */
+        private final String[] propertyNames;
+
+        /**
+         * Creates a new iterator.
+         */
+        @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
+        Iter() {
+            final Collection<? extends AbstractIdentifiedType> properties = featureType.getProperties(true);
+            converters    = new ObjectConverter[properties.size()];
+            values        = new Object[converters.length];
+            propertyNames = new String[converters.length];
             int i = -1;
             for (final AbstractIdentifiedType p : properties) {
                 propertyNames[++i] = p.getName().tip().toString();
-                switch (i) {    // This switch shall follow the same cases than the swith in the loop.
-                    case 1:
-                    case 2: if (timeEncoding != null) continue;     // else fall through
-                    case 3: if (hasTrajectories) continue;
-                }
-                converters[i] = ObjectConverters.find(String.class, ((DefaultAttributeType) p).getValueClass());
-            }
-            /*
-             * Above lines prepared the constants. Now parse all lines.
-             * TODO: We should move the code below this point in a custom Iterator implementation.
-             */
-            String line;
-            while ((line = source.readLine()) != null) {
-                split(line, values);
-                final int length = Math.min(propertyNames.length, values.size());
-                if (length != 0) {
-                    final AbstractFeature feature = featureType.newInstance();
-                    for (i=0; i<length; i++) {
-                        final String text = values.get(i);
-                        final String name = propertyNames[i];
-                        final Object value;
+                /*
+                 * According Moving Features specification:
+                 *   Column 0 is the feature identifier (mfidref). There is nothing special to do here.
+                 *   Column 1 is the start time.
+                 *   Column 2 is the end time.
+                 *   Column 3 is the trajectory.
+                 *   Columns 4+ are custom attributes.
+                 */
+                final ObjectConverter<String,?> c;
+                switch (i) {
+                    case 1: // Fall through
+                    case 2: {
+                        if (timeEncoding != null) {
+                            c = timeEncoding;
+                            break;
+                        }
                         /*
-                         * According Moving Features specification:
-                         *   Column 0 is the feature identifier (mfidref). There is nothing special to do here.
-                         *   Column 1 is the start time.
-                         *   Column 2 is the end time.
-                         *   Column 3 is the trajectory.
-                         *   Columns 4+ are custom attributes.
-                         *
-                         * TODO: we should replace that switch case by custom ObjectConverter.
+                         * If there is no time columns, then this column may be the trajectory (note that allowing
+                         * CSV files without time is obviously a departure from Moving Features specification.
+                         * The intend is to have a CSV format applicable to other features than moving ones).
+                         * Fall through in order to process trajectory.
                          */
-                        switch (i) {
-                            case 1:
-                            case 2: {
-                                if (timeEncoding != null) {
-                                    if (timeEncoding == TimeEncoding.ABSOLUTE) {
-                                        value = Instant.parse(text).toEpochMilli();
-                                    } else {
-                                        value = Instant.ofEpochMilli(timeEncoding.toMillis(Double.parseDouble(text)));
-                                    }
-                                    break;
-                                }
-                                /*
-                                 * If there is no time columns, then this column may the trajectory (note that allowing
-                                 * CSV files without time is obviously a departure from Moving Features specification.
-                                 * The intend is to have a CSV format applicable to other features than moving ones).
-                                 * Fall through in order to process trajectory.
-                                 */
-                            }
-                            case 3: {
-                                if (hasTrajectories) {
-                                    value = CharSequences.parseDoubles(text, ORDINATE_SEPARATOR);
-                                    break;
-                                }
-                                /*
-                                 * If there is no trajectory columns, than this column is a custum attribute.
-                                 * CSV files without trajectories are not compliant with Moving Feature spec.,
-                                 * but we try to keep this reader a little bit more generic.
-                                 */
-                            }
-                            default: {
-                                value = converters[i].apply(text);
-                                break;
-                            }
+                    }
+                    case 3: {
+                        if (hasTrajectories) {
+                            c = GeometryParser.INSTANCE;
+                            break;
                         }
-                        feature.setPropertyValue(name, value);
+                        /*
+                         * If there is no trajectory columns, than this column is a custum attribute.
+                         * CSV files without trajectories are not compliant with Moving Feature spec.,
+                         * but we try to keep this reader a little bit more generic.
+                         */
+                    }
+                    default: {
+                        c = ObjectConverters.find(String.class, ((DefaultAttributeType) p).getValueClass());
+                        break;
                     }
-                    features.add(feature);
                 }
-                values.clear();
+                converters[i] = c;
+            }
+        }
+
+        /**
+         * Executes the given action for the next feature or for all remaining features.
+         *
+         * <p><b>Multi-threading:</b>
+         * There is no need for {@code synchronize(Store.this)} statement since this method uses only final and
+         * either immutable or thread-safe objects from {@link Store}. The only object that need synchronization
+         * is {@link Store#source}, which is already synchronized.</p>
+         *
+         * @param  action  the action to execute.
+         * @param  all     {@code true} for executing the given action on all remaining features.
+         * @return {@code false} if there is no remaining feature after this method call.
+         * @throws IOException if an I/O error occurred while reading a feature.
+         * @throws IllegalArgumentException if parsing of a number failed, or other error.
+         * @throws DateTimeException if parsing of a date failed.
+         */
+        private boolean read(final Consumer<? super AbstractFeature> action, boolean all) throws IOException {
+            final FixedSizeList elements = new FixedSizeList(values);
+            String line;
+            while ((line = source.readLine()) != null) {
+                split(line, elements);
+                final AbstractFeature feature = featureType.newInstance();
+                int i, n = elements.size();
+                for (i=0; i<n; i++) {
+                    values[i] = converters[i].apply((String) values[i]);
+                    feature.setPropertyValue(propertyNames[i], values[i]);
+                }
+                n = values.length;
+                for (; i<n; i++) {
+                    // For omitted elements, reuse previous value.
+                    feature.setPropertyValue(propertyNames[i], values[i]);
+                }
+                action.accept(feature);
+                if (!all) return true;
+                elements.clear();
             }
-        } catch (IOException | IllegalArgumentException | DateTimeException e) {
-            throw new DataStoreException(errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", name), e);
+            return false;
+        }
+
+        /**
+         * Executes the given action only on the next feature, if any.
+         */
+        @Override
+        public boolean tryAdvance(final Consumer<? super AbstractFeature> action) {
+            try {
+                return read(action, false);
+            } catch (IOException | IllegalArgumentException | DateTimeException e) {
+                throw new BackingStoreException(canNotParse(), e);
+            }
+        }
+
+        /**
+         * Executes the given action on all remaining features.
+         */
+        @Override
+        public void forEachRemaining(final Consumer<? super AbstractFeature> action) {
+            try {
+                read(action, true);
+            } catch (IOException | IllegalArgumentException | DateTimeException e) {
+                throw new BackingStoreException(canNotParse(), e);
+            }
+        }
+
+        /**
+         * Returns the error message for a file that can not be parsed.
+         */
+        private String canNotParse() {
+            return errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", filename);
+        }
+
+        /**
+         * Current implementation can not split this iterator.
+         */
+        @Override
+        public Spliterator<AbstractFeature> trySplit() {
+            return null;
+        }
+
+        /**
+         * We do not know the number of features.
+         */
+        @Override
+        public long estimateSize() {
+            return Long.MAX_VALUE;
+        }
+
+        /**
+         * Guarantees that we will not return null element.
+         */
+        @Override
+        public int characteristics() {
+            return NONNULL;
         }
-        return features.iterator();
     }
 
     /**
      * Splits the content of the given line around the column separator.
      * Quotes are taken in account. The elements are added in the given list.
      *
-     * @param line the line to parse.
-     * @param elements an initially empty list where to add elements.
+     * @param line      the line to parse.
+     * @param elements  an initially empty list where to add elements.
      */
-    private static void split(final String line, final List<String> elements) {
+    static void split(final String line, final List<? super String> elements) {
         int startAt = 0;
-        boolean hasQuotes = false;
-        boolean isQuoting = false;
+        boolean isQuoting = false;        // If a quote has been opened and not yet closed.
+        boolean hasQuotes = false;        // If the value contains at least one quote (not used for quoting the value).
         final int length = line.length();
         for (int i=0; i<length; i++) {
             switch (line.charAt(i)) {
@@ -597,7 +707,9 @@ public final class Store extends DataSto
                 }
                 case SEPARATOR: {
                     if (!isQuoting) {
-                        elements.add(decode(line, startAt, i, hasQuotes));
+                        if (!elements.add(decode(line, startAt, i, hasQuotes))) {
+                            return;     // Reached the maximal capacity of the list.
+                        }
                         startAt = i+1;
                         hasQuotes = false;
                     }
@@ -611,9 +723,15 @@ public final class Store extends DataSto
     /**
      * Extracts a substring from the given line and replaces double quotes by single quotes.
      *
-     * @todo Needs also to check escape characters {@code \s \t \b &lt; &gt; &amp; &quot; &apos;}.
-     * @todo Should modify double quote policy: process only if the text had quotes at the beginning and end.
-     *       Those "todo" should be done only when we detected that the CSV file is a moving features file.
+     * <div class="section">Departure from Moving Features specification</div>
+     * The Moving Features specification said:
+     *
+     *   <blockquote>Some characters may need to be escaped here. {@literal <} (less than), {@literal >}
+     *   (greater than), " (double quotation), ‘ (single quotation), and {@literal &} (ampersand) must be
+     *   replaced with the entity references defined in XML. Space, tab, and comma are written in escape
+     *   sequences \\s, \\t, and \\b, respectively.</blockquote>
+     *
+     * This part of the specification is currently ignored (its purpose is still unclear).
      */
     private static String decode(CharSequence text, final int lower, final int upper, final boolean hasQuotes) {
         if (hasQuotes) {
@@ -626,7 +744,7 @@ public final class Store extends DataSto
             }
             text = CharSequences.trimWhitespaces(buffer);
         } else {
-            text = CharSequences.trimWhitespaces(text, lower, upper).toString();
+            text = CharSequences.trimWhitespaces(text, lower, upper);
         }
         return text.toString();
     }
@@ -648,13 +766,12 @@ public final class Store extends DataSto
     /**
      * Closes this data store and releases any underlying resources.
      *
-     * @throws DataStoreException If an error occurred while closing this data store.
+     * @throws DataStoreException if an error occurred while closing this data store.
      */
     @Override
-    public void close() throws DataStoreException {
+    public synchronized void close() throws DataStoreException {
         final BufferedReader s = source;
         source = null;                  // Cleared first in case of failure.
-        features.clear();
         if (s != null) try {
             s.close();
         } catch (IOException e) {

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -20,9 +20,13 @@ import javax.measure.unit.Unit;
 import javax.measure.unit.NonSI;
 import javax.measure.quantity.Duration;
 import org.opengis.referencing.datum.TemporalDatum;
+import org.apache.sis.internal.converter.SurjectiveConverter;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.measure.Units;
 
+// Branch-dependent imports
+import org.apache.sis.internal.jdk8.Instant;
+
 
 /**
  * Specifies how time is encoded in the CSV file.
@@ -31,10 +35,10 @@ import org.apache.sis.measure.Units;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
- * @version 0.7
+ * @version 0.8
  * @module
  */
-final class TimeEncoding {
+class TimeEncoding extends SurjectiveConverter<String,Instant> {
     /**
      * The temporal coordinate reference system to use for {@link #ABSOLUTE} time encoding.
      */
@@ -43,7 +47,11 @@ final class TimeEncoding {
     /**
      * Times are formatted as ISO dates.
      */
-    static final TimeEncoding ABSOLUTE = new TimeEncoding(DEFAULT.datum(), NonSI.DAY);
+    static final TimeEncoding ABSOLUTE = new TimeEncoding(DEFAULT.datum(), NonSI.DAY) {
+        @Override public Instant apply(final String time) {
+            return Instant.parse(time);
+        }
+    };
 
     /**
      * Date of value zero on the time axis, in milliseconds since January 1st 1970 at midnight UTC.
@@ -64,22 +72,41 @@ final class TimeEncoding {
     }
 
     /**
-     * Converts the given timestamp to the values used in the temporal coordinate reference system.
+     * Returns the type of values to convert.
+     */
+    @Override
+    public final Class<String> getSourceClass() {
+        return String.class;
+    }
+
+    /**
+     * Returns the type of converted values.
+     */
+    @Override
+    public final Class<Instant> getTargetClass() {
+        return Instant.class;
+    }
+
+    /**
+     * Returns the instant for the given string, which is usually a time elapsed since the CRS temporal origin.
      *
-     * @param time Number of milliseconds elapsed since January 1st, 1970 midnight UTC.
-     * @return The value to use with the temporal coordinate reference system.
+     * @param  time  the string representation of the time to parse, often as a number since the CRS temporal origin.
+     * @return the instant parsed from the given string.
      */
-    final double toCRS(final long time) {
-        return (time - origin) / interval;
+    @Override
+    public Instant apply(final String time) {
+        final double value = Double.parseDouble(time) * interval;
+        final long millis = Math.round(value);
+        return Instant.ofEpochMilli(millis + origin).plusNanos(Math.round((value - millis)*1E6));
     }
 
     /**
-     * Reverse of {@link #toCRS(long)}.
+     * Converts the given timestamp to the values used in the temporal coordinate reference system.
      *
-     * @param time The value used with the temporal coordinate reference system.
-     * @return Number of milliseconds elapsed since January 1st, 1970 midnight UTC.
+     * @param  time  number of milliseconds elapsed since January 1st, 1970 midnight UTC.
+     * @return the value to use with the temporal coordinate reference system.
      */
-    final long toMillis(final double time) {
-        return Math.round(time * interval) + origin;
+    final double toCRS(final long time) {
+        return (time - origin) / interval;
     }
 }

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -23,14 +23,38 @@
  *   <li><a href="http://docs.opengeospatial.org/is/14-084r2/14-084r2.html">OGC® Moving Features Encoding Extension:
  *     Simple Comma Separated Values (CSV)</a> with some rules relaxed. The Apache SIS implementation allows the CSV
  *     file to have no date, thus allowing use of OGC 14-084 syntax for static features in addition to moving ones.</li>
+ *   <li><a href="http://www.ietf.org/rfc/rfc4180.txt">Common Format and MIME Type for Comma-Separated Values (CSV) Files</a>.
+ *     This is supported indirectly since above OGC specification is an extension of this IETF specification.</li>
  * </ul>
  *
- * The above extends the <a href="http://www.ietf.org/rfc/rfc4180.txt">Common Format and MIME Type for
- * Comma-Separated Values (CSV) Files</a> specification.
+ * Example of moving features CSV file (adapted from OGC specification):
+ *
+ * {@preformat text
+ *   &#64;stboundedby, urn:x-ogc:def:crs:EPSG::4326, 2D, 9.23 50.23, 9.27 50.31, 2012-01-17T12:33:41Z, 2012-01-17T12:37:00Z, sec
+ *   &#64;columns,mfidref,trajectory,state,xsd:token,”type code”,xsd:integer
+ *   a,  10, 150, 11.0 2.0 12.0 3.0, walking, 1
+ *   b,  10, 190, 10.0 2.0 11.0 3.0, walking, 2
+ *   a, 150, 190, 12.0 3.0 10.0 3.0, walking, 2
+ *   c,  10, 190, 12.0 1.0 10.0 2.0 11.0 3.0, vehicle, 1
+ * }
+ *
+ * <div class="section">Departures from OGC specification</div>
+ * Current implementation is not strictly compliant with the Moving Features specification.
+ * Departures are:
+ *
+ * <ul>
+ *   <li>If the character encoding is not explicitely specified, then Apache SIS uses the platform default instead of UTF-8.
+ *       If a file to read is known to be a Moving Features compliant file, the UTF-8 encoding should be associated to
+ *       {@link org.apache.sis.setup.OptionKey#ENCODING} in the {@link org.apache.sis.storage.StorageConnector}.</li>
+ *   <li>The Apache SIS implementation does not replace the XML entities by the referenced characters.
+ *       XML entities, if present, are included verbatim in the parsed text.</li>
+ *   <li>The Apache SIS implementation does not replace the \\s, \\t, and \\b escape sequences by space, tab, and comma.
+ *       Those escape sequences are included verbatim in the parsed text.</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
- * @version 0.7
+ * @version 0.8
  * @module
  */
 package org.apache.sis.internal.storage.csv;

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -140,7 +140,7 @@ final class Store extends DataStore {
      * @throws DataStoreException if an error occurred during the parsing process.
      */
     @Override
-    public Metadata getMetadata() throws DataStoreException {
+    public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) {
             parse();
             DefaultMetadata md = null;
@@ -161,7 +161,7 @@ final class Store extends DataStore {
      * @throws DataStoreException if an error occurred while closing this data store.
      */
     @Override
-    public void close() throws DataStoreException {
+    public synchronized void close() throws DataStoreException {
         final Reader s = source;
         source = null;                  // Cleared first in case of failure.
         objects.clear();

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -168,11 +168,11 @@ final class Store extends DataStore {
      *
      * Other cases may be added in any future SIS version.
      *
-     * @return The metadata associated to the unmarshalled object, or {@code null} if none.
+     * @return the metadata associated to the unmarshalled object, or {@code null} if none.
      * @throws DataStoreException if an error occurred during the unmarshalling process.
      */
     @Override
-    public Metadata getMetadata() throws DataStoreException {
+    public synchronized Metadata getMetadata() throws DataStoreException {
         if (metadata == null) {
             unmarshal();
             if (object instanceof Metadata) {
@@ -192,7 +192,7 @@ final class Store extends DataStore {
      * @throws DataStoreException if an error occurred while closing this data store.
      */
     @Override
-    public void close() throws DataStoreException {
+    public synchronized void close() throws DataStoreException {
         object = null;
         final Closeable in = input(source);
         source = null;                          // Cleared first in case of failure.

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStore.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -29,9 +29,8 @@ import org.apache.sis.util.logging.Warni
  * Manages a series of features, coverages or sensor data.
  *
  * <div class="section">Thread safety policy</div>
- * This {@code DataStore} base class is thread-safe. However subclasses are usually not.
- * Unless otherwise specified by subclasses, users should assume that {@code DataStore}
- * instances are not thread-safe.
+ * This {@code DataStore} base class is thread-safe. However subclasses do not need to be thread-safe.
+ * Unless otherwise specified, users should assume that {@code DataStore} instances are not thread-safe.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
@@ -77,7 +76,7 @@ public abstract class DataStore implemen
     /**
      * Sets the locale to use for formatting warnings and other messages.
      *
-     * @param locale The new locale to use.
+     * @param locale  the new locale to use.
      */
     public synchronized void setLocale(final Locale locale) {
         ArgumentChecks.ensureNonNull("locale", locale);
@@ -89,7 +88,7 @@ public abstract class DataStore implemen
      * information such as the spatiotemporal extent of the dataset, contact information about the creator
      * or distributor, data quality, update frequency, usage constraints and more.
      *
-     * @return Information about the dataset, or {@code null} if none.
+     * @return information about the dataset, or {@code null} if none.
      * @throws DataStoreException if an error occurred while reading the data.
      */
     public abstract Metadata getMetadata() throws DataStoreException;
@@ -117,7 +116,7 @@ public abstract class DataStore implemen
      *     }
      * }
      *
-     * @param  listener The listener to add.
+     * @param  listener  the listener to add.
      * @throws IllegalArgumentException if the given listener is already registered in this data store.
      */
     public void addWarningListener(final WarningListener<? super DataStore> listener)
@@ -129,7 +128,7 @@ public abstract class DataStore implemen
     /**
      * Removes a previously registered listener.
      *
-     * @param  listener The listener to remove.
+     * @param  listener  the listener to remove.
      * @throws NoSuchElementException if the given listener is not registered in this data store.
      */
     public void removeWarningListener(final WarningListener<? super DataStore> listener)

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreClosedException.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreClosedException.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreClosedException.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreClosedException.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -18,7 +18,7 @@ package org.apache.sis.storage;
 
 
 /**
- * Thrown when a Data store is closed and can no more return data.
+ * Thrown when a data store is closed and can no more return data.
  *
  * @author  Marc Le Bihan
  * @version 0.6
@@ -26,23 +26,25 @@ package org.apache.sis.storage;
  * @module
  */
 public class DataStoreClosedException extends DataStoreException {
-    /** Serial UID. */
+    /**
+     * For cross-version compatibility.
+     */
     private static final long serialVersionUID = 7205119080377665796L;
 
     /**
-     * Constructs an exception with the specified details message.
+     * Creates an exception with the specified details message.
      *
-     * @param message Message of the exception.
+     * @param message  the detail message of the exception.
      */
     public DataStoreClosedException(String message) {
         super(message);
     }
 
     /**
-     * Constructs an exception with the specified details message and cause.
+     * Creates an exception with the specified details message and cause.
      *
-     * @param message Message of the exception.
-     * @param cause Root cause of the exception.
+     * @param message  the detail message of the exception.
+     * @param cause    the cause root for the exception.
      */
     public DataStoreClosedException(String message, Throwable cause) {
         super(message, cause);

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreContentException.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -49,7 +49,7 @@ public class DataStoreContentException e
     }
 
     /**
-     * Creates an exception with the specified cause.
+     * Creates an exception with the specified cause and no details message.
      *
      * @param cause  the cause for this exception.
      */

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStoreRegistry.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -63,7 +63,7 @@ final class DataStoreRegistry {
     /**
      * Creates a new registry which will look for data stores accessible to the given class loader.
      *
-     * @param loader The class loader to use for loading {@link DataStoreProvider} implementations.
+     * @param  loader  the class loader to use for loading {@link DataStoreProvider} implementations.
      */
     public DataStoreRegistry(final ClassLoader loader) {
         ArgumentChecks.ensureNonNull("loader", loader);
@@ -73,9 +73,9 @@ final class DataStoreRegistry {
     /**
      * Returns the MIME type of the storage file format, or {@code null} if unknown or not applicable.
      *
-     * @param  storage The input/output object as a URL, file, image input stream, <i>etc.</i>.
-     * @return The storage MIME type, or {@code null} if unknown or not applicable.
-     * @throws DataStoreException If an error occurred while opening the storage.
+     * @param  storage  the input/output object as a URL, file, image input stream, <i>etc.</i>.
+     * @return the storage MIME type, or {@code null} if unknown or not applicable.
+     * @throws DataStoreException if an error occurred while opening the storage.
      */
     public String probeContentType(final Object storage) throws DataStoreException {
         ArgumentChecks.ensureNonNull("storage", storage);
@@ -97,10 +97,10 @@ final class DataStoreRegistry {
      *   <li>An existing {@link StorageConnector} instance.</li>
      * </ul>
      *
-     * @param  storage The input/output object as a URL, file, image input stream, <i>etc.</i>.
-     * @return The object to use for reading geospatial data from the given storage.
+     * @param  storage  the input/output object as a URL, file, image input stream, <i>etc.</i>.
+     * @return the object to use for reading geospatial data from the given storage.
      * @throws UnsupportedStorageException if no {@link DataStoreProvider} is found for a given storage object.
-     * @throws DataStoreException If an error occurred while opening the storage.
+     * @throws DataStoreException if an error occurred while opening the storage.
      */
     public DataStore open(final Object storage) throws UnsupportedStorageException, DataStoreException {
         ArgumentChecks.ensureNonNull("storage", storage);
@@ -110,10 +110,10 @@ final class DataStoreRegistry {
     /**
      * Implementation of {@link #probeContentType(Object)} and {@link #open(Object)}.
      *
-     * @param  storage The input/output object as a URL, file, image input stream, <i>etc.</i>.
-     * @param  open {@code true} for creating a {@link DataStore}, or {@code false} if not needed.
+     * @param  storage  the input/output object as a URL, file, image input stream, <i>etc.</i>.
+     * @param  open     {@code true} for creating a {@link DataStore}, or {@code false} if not needed.
      * @throws UnsupportedStorageException if no {@link DataStoreProvider} is found for a given storage object.
-     * @throws DataStoreException If an error occurred while opening the storage.
+     * @throws DataStoreException if an error occurred while opening the storage.
      */
     private ProbeProviderPair lookup(final Object storage, final boolean open) throws DataStoreException {
         StorageConnector connector;

Modified: sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStores.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStores.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStores.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/java/org/apache/sis/storage/DataStores.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -80,9 +80,9 @@ public final class DataStores extends St
     /**
      * Returns the MIME type of the storage file format, or {@code null} if unknown or not applicable.
      *
-     * @param  storage The input/output object as a URL, file, image input stream, <i>etc.</i>.
-     * @return The storage MIME type, or {@code null} if unknown or not applicable.
-     * @throws DataStoreException If an error occurred while opening the storage.
+     * @param  storage  the input/output object as a URL, file, image input stream, <i>etc.</i>.
+     * @return the storage MIME type, or {@code null} if unknown or not applicable.
+     * @throws DataStoreException if an error occurred while opening the storage.
      */
     public static String probeContentType(final Object storage) throws DataStoreException {
         return registry().probeContentType(storage);
@@ -102,10 +102,10 @@ public final class DataStores extends St
      *   <li>An existing {@link StorageConnector} instance.</li>
      * </ul>
      *
-     * @param  storage The input/output object as a URL, file, image input stream, <i>etc.</i>.
-     * @return The object to use for reading geospatial data from the given storage.
+     * @param  storage  the input/output object as a URL, file, image input stream, <i>etc.</i>.
+     * @return the object to use for reading geospatial data from the given storage.
      * @throws UnsupportedStorageException if no {@link DataStoreProvider} is found for a given storage object.
-     * @throws DataStoreException If an error occurred while opening the storage.
+     * @throws DataStoreException if an error occurred while opening the storage.
      */
     public static DataStore open(final Object storage) throws UnsupportedStorageException, DataStoreException {
         return registry().open(storage);

Modified: sis/trunk/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/main/resources/META-INF/services/org.apache.sis.storage.DataStoreProvider [UTF-8] Wed Sep 28 13:01:39 2016
@@ -1,2 +1,3 @@
 org.apache.sis.internal.storage.xml.StoreProvider
 org.apache.sis.internal.storage.wkt.StoreProvider
+org.apache.sis.internal.storage.csv.StoreProvider

Modified: sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/MetadataBuilderTest.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -73,7 +73,7 @@ public final strictfp class MetadataBuil
         final MetadataBuilder builder = new MetadataBuilder();
         builder.parseLegalNotice(notice);
         final DefaultLegalConstraints constraints = (DefaultLegalConstraints) getSingleton(getSingleton(
-                builder.result().getIdentificationInfo()).getResourceConstraints());
+                builder.build(false).getIdentificationInfo()).getResourceConstraints());
 
         assertEquals("useConstraints", Restriction.COPYRIGHT, getSingleton(constraints.getUseConstraints()));
         final Citation ref = getSingleton(constraints.getReferences());

Modified: sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -19,20 +19,24 @@ package org.apache.sis.internal.storage.
 import java.util.Iterator;
 import java.io.StringReader;
 import org.opengis.metadata.Metadata;
+import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.opengis.metadata.extent.SpatialTemporalExtent;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
 
 import static org.junit.Assert.*;
+import static org.apache.sis.test.TestUtilities.date;
 import static org.apache.sis.test.TestUtilities.getSingleton;
 
 // Branch-dependent imports
 import org.apache.sis.feature.AbstractFeature;
-import org.apache.sis.metadata.iso.extent.DefaultSpatialTemporalExtent;
+import org.apache.sis.feature.AbstractIdentifiedType;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.feature.DefaultFeatureType;
 import org.apache.sis.metadata.iso.identification.AbstractIdentification;
+import org.apache.sis.internal.jdk8.Instant;
 
 
 /**
@@ -40,21 +44,31 @@ import org.apache.sis.metadata.iso.ident
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.7
- * @version 0.7
+ * @version 0.8
  * @module
  */
-public final class StoreTest extends TestCase {
+public final strictfp class StoreTest extends TestCase {
     /**
      * An example of Moving Features file.
      * Derived from the example provided in OGC 14-084r2.
      */
-    private static final String TEST_DATA =
-            "@stboundedby, urn:ogc:def:crs:CRS:1.3:84, 2D,  50.23 9.23 , 50.31 9.27,  2012-01-17T12:33:41Z, 2012-01-17T12:37:00Z, sec\n" +
+    static StringReader testData() {
+        return new StringReader(
+            "@stboundedby, urn:ogc:def:crs:CRS:1.3:84, 2D,  50.23 9.23,  50.31 9.27,  2012-01-17T12:33:41Z, 2012-01-17T12:37:00Z, sec\n" +
             "@columns, mfidref, trajectory, state,xsd:string, \"\"\"type\"\" code\",xsd:integer\n" +
-            "a,10,150,11.0 2.0 12.0 3.0,walking,1\n" +
-            "b,10,190,10.0 2.0 11.0 3.0,walking,2\n" +
-            "a,150,190,12.0 3.0 10.0 3.0,walking,2\n" +
-            "c,10,190,12.0 1.0 10.0 2.0 11.0 3.0,vehicle,1\n";
+            "@foliation,Time\n" +
+            "a,  10, 150, 11.0 2.0 12.0 3.0, walking, 1\n" +
+            "b,  10, 190, 10.0 2.0 11.0 3.0, walking, 2\n" +
+            "a, 150, 190, 12.0 3.0 10.0 3.0\n" +                        // Omitted values are same as previous line.
+            "c,  10, 190, 12.0 1.0 10.0 2.0 11.0 3.0, vehicle, 1\n");
+    }
+
+    /**
+     * Returns the instant for the given time at the day of the test.
+     */
+    private static Instant instant(final String time) {
+        return Instant.ofEpochMilli(date("2012-01-17 " + time).getTime());
+    }
 
     /**
      * Tests {@link Store#getMetadata()}.
@@ -62,49 +76,77 @@ public final class StoreTest extends Tes
      * @throws DataStoreException if an error occurred while parsing the data.
      */
     @Test
-    @org.junit.Ignore("Pending completion of sis-referencing")
     public void testGetMetadata() throws DataStoreException {
         final Metadata metadata;
-        try (Store store = new Store(new StorageConnector(new StringReader(TEST_DATA)))) {
+        try (Store store = new Store(new StorageConnector(testData()))) {
             metadata = store.getMetadata();
         }
-        final AbstractIdentification id = (AbstractIdentification) getSingleton(metadata.getIdentificationInfo());
-        final SpatialTemporalExtent extent = (SpatialTemporalExtent) getSingleton(getSingleton(id.getExtents()).getTemporalElements());
-        final GeographicBoundingBox bbox = (GeographicBoundingBox) getSingleton(extent.getSpatialExtent());
+        final Extent extent = getSingleton(((AbstractIdentification) getSingleton(metadata.getIdentificationInfo())).getExtents());
+        final GeographicBoundingBox bbox = (GeographicBoundingBox) getSingleton(extent.getGeographicElements());
         assertEquals("westBoundLongitude", 50.23, bbox.getWestBoundLongitude(), STRICT);
         assertEquals("eastBoundLongitude", 50.31, bbox.getEastBoundLongitude(), STRICT);
         assertEquals("southBoundLatitude",  9.23, bbox.getSouthBoundLatitude(), STRICT);
         assertEquals("northBoundLatitude",  9.27, bbox.getNorthBoundLatitude(), STRICT);
-        assertNull("Should not have a vertical extent.", ((DefaultSpatialTemporalExtent) extent).getVerticalExtent());
-        assertNotNull("Should have a temporal extent", extent.getExtent());
+        assertTrue("Should not have a vertical extent.", extent.getVerticalElements().isEmpty());
     }
 
     /**
-     * Tests {@link Store#getFeatures()}.
+     * Verifies the feature type, then tests {@link Store#getFeatures()}.
      *
      * @throws DataStoreException if an error occurred while parsing the data.
      */
     @Test
     public void testGetFeatures() throws DataStoreException {
-        try (Store store = new Store(new StorageConnector(new StringReader(TEST_DATA)))) {
-            final Iterator<AbstractFeature> it = store.getFeatures();
-            assertFeatureEquals(it.next(), "a", new double[] {11, 2, 12, 3},        "walking", 1);
-            assertFeatureEquals(it.next(), "b", new double[] {10, 2, 11, 3},        "walking", 2);
-            assertFeatureEquals(it.next(), "a", new double[] {12, 3, 10, 3},        "walking", 2);
-            assertFeatureEquals(it.next(), "c", new double[] {12, 1, 10, 2, 11, 3}, "vehicle", 1);
+        try (Store store = new Store(new StorageConnector(testData()))) {
+            verifyFeatureType(store.featureType);
+            assertEquals("foliation", Foliation.TIME, store.foliation);
+            final Iterator<AbstractFeature> it = store.getFeatures().iterator();
+            assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:11", new double[] {11, 2, 12, 3},        "walking", 1);
+            assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3},        "walking", 2);
+            assertPropertyEquals(it.next(), "a", "12:36:11", "12:36:51", new double[] {12, 3, 10, 3},        "walking", 2);
+            assertPropertyEquals(it.next(), "c", "12:33:51", "12:36:51", new double[] {12, 1, 10, 2, 11, 3}, "vehicle", 1);
             assertFalse(it.hasNext());
         }
     }
 
     /**
-     * Asserts that the given feature has the given properties.
+     * Verifies that the feature type is equal to the expected one.
+     */
+    private static void verifyFeatureType(final DefaultFeatureType type) {
+        final Iterator<? extends AbstractIdentifiedType> it = type.getProperties(true).iterator();
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "mfidref",       String.class,   1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "startTime",     Instant.class,  1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "endTime",       Instant.class,  1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "trajectory",    double[].class, 1);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "state",         String.class,   0);
+        assertPropertyTypeEquals((DefaultAttributeType<?>) it.next(), "\"type\" code", Integer.class,  0);
+        assertFalse(it.hasNext());
+    }
+
+    /**
+     * Asserts that the given property type has the given information.
+     */
+    private static void assertPropertyTypeEquals(final DefaultAttributeType<?> p,
+            final String name, final Class<?> valueClass, final int minOccurs)
+    {
+        assertEquals("name",       name,       p.getName().toString());
+        assertEquals("valueClass", valueClass, p.getValueClass());
+        assertEquals("minOccurs",  minOccurs,  p.getMinimumOccurs());
+        assertEquals("maxOccurs",  1,          p.getMaximumOccurs());
+    }
+
+    /**
+     * Asserts that the property of the given name in the given feature has expected information.
      */
-    private static void assertFeatureEquals(final AbstractFeature f, final String mfidref,
-            final double[] trajectory, final String state, final int typeCode)
+    private static void assertPropertyEquals(final AbstractFeature f, final String mfidref,
+            final String startTime, final String endTime, final double[] trajectory,
+            final String state, final int typeCode)
     {
-        assertEquals     ("mfidref",    mfidref,  f.getPropertyValue("mfidref"));
-        assertEquals     ("state",      state,    f.getPropertyValue("state"));
-        assertEquals     ("typeCode",   typeCode, f.getPropertyValue("\"type\" code"));
+        assertEquals     ("mfidref",    mfidref,               f.getPropertyValue("mfidref"));
+        assertEquals     ("startTime",  instant(startTime),    f.getPropertyValue("startTime"));
+        assertEquals     ("endTime",    instant(endTime),      f.getPropertyValue("endTime"));
+        assertEquals     ("state",      state,                 f.getPropertyValue("state"));
+        assertEquals     ("typeCode",   typeCode,              f.getPropertyValue("\"type\" code"));
         assertArrayEquals("trajectory", trajectory, (double[]) f.getPropertyValue("trajectory"), STRICT);
     }
 }

Modified: sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java
URL: http://svn.apache.org/viewvc/sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java?rev=1762647&r1=1762646&r2=1762647&view=diff
==============================================================================
--- sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java [UTF-8] (original)
+++ sis/trunk/storage/sis-storage/src/test/java/org/apache/sis/test/suite/StorageTestSuite.java [UTF-8] Wed Sep 28 13:01:39 2016
@@ -45,6 +45,7 @@ import org.junit.BeforeClass;
     org.apache.sis.internal.storage.xml.StoreTest.class,
     org.apache.sis.internal.storage.wkt.StoreProviderTest.class,
     org.apache.sis.internal.storage.wkt.StoreTest.class,
+    org.apache.sis.internal.storage.csv.StoreProviderTest.class,
     org.apache.sis.internal.storage.csv.StoreTest.class,
     org.apache.sis.storage.DataStoresTest.class,
     org.apache.sis.index.GeoHashCoderTest.class



Mime
View raw message