sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1760061 - in /sis/branches/JDK8/storage/sis-storage/src: main/java/org/apache/sis/internal/storage/csv/ test/java/org/apache/sis/internal/storage/csv/
Date Fri, 09 Sep 2016 16:12:52 GMT
Author: desruisseaux
Date: Fri Sep  9 16:12:52 2016
New Revision: 1760061

URL: http://svn.apache.org/viewvc?rev=1760061&view=rev
Log:
More efficient parsing of CSV row by pre-computing more information in the iterator constructor.

Added:
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java
  (with props)
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
  (with props)
Modified:
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java
    sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
    sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java

Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java?rev=1760061&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java
[UTF-8] Fri Sep  9 16:12:52 2016
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.csv;
+
+import java.util.AbstractList;
+
+
+/**
+ * Wraps an array of fixed size. Attempts to add a new element fail when the list reached
its maximal capacity.
+ * Clearing the array does <strong>not</strong> set the references to {@code
null}, since we need to remember
+ * the values of previous passes. This implementation is designed for compliance with the
part of Moving Features
+ * specification saying that "if the value equals the previous value, the text for the value
can be omitted".
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+final class FixedSizeList extends AbstractList<Object> {
+    /**
+     * The array where to store the values.
+     * Elements in this array are usually not null even if this list is empty.
+     */
+    private final Object[] values;
+
+    /**
+     * Number of elements added in the {@link #values} array.
+     */
+    private int size;
+
+    /**
+     * Creates a new list wrapping the given array of values.
+     */
+    FixedSizeList(final Object[] values) {
+        this.values = values;
+    }
+
+    /**
+     * Returns the number of elements added in the values array.
+     */
+    @Override
+    public int size() {
+        return size;
+    }
+
+    /**
+     * Resets the list size to zero, but do <strong>not</strong> reset the references
to {@code null}.
+     */
+    @Override
+    public void clear() {
+        size = 0;
+    }
+
+    /**
+     * Returns the value at the given index without check of index validity. We do not bother
+     * verifying that {@literal index < size} because this list is used only for internal
purpose.
+     */
+    @Override
+    public Object get(final int index) {
+        return values[index];
+    }
+
+    /**
+     * Adds the given value if we have not yet reached the maximal capacity of this list.
+     */
+    @Override
+    public boolean add(final Object value) {
+        if (size < values.length) {
+            values[size++] = value;
+            return true;
+        }
+        return false;
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/FixedSizeList.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java?rev=1760061&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
(added)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
[UTF-8] Fri Sep  9 16:12:52 2016
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.storage.csv;
+
+import org.apache.sis.internal.converter.SurjectiveConverter;
+import org.apache.sis.util.CharSequences;
+
+
+/**
+ * The converter to use for converting a text into a geometry. In current implementation,
+ * geometries are line strings represented by an array of ordinate values.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+final class GeometryParser extends SurjectiveConverter<String,double[]> {
+    /**
+     * The unique instance.
+     */
+    static final GeometryParser INSTANCE = new GeometryParser();
+
+    /**
+     * For the singleton instance.
+     */
+    private GeometryParser() {
+    }
+
+    /**
+     * Returns the type of elements to convert.
+     */
+    @Override
+    public Class<String> getSourceClass() {
+        return String.class;
+    }
+
+    /**
+     * Returns the type of converted elements.
+     */
+    @Override
+    public Class<double[]> getTargetClass() {
+        return double[].class;
+    }
+
+    /**
+     * Converts an element from the CSV file to our current pseudo-geometry type.
+     */
+    @Override
+    public double[] apply(final String text) {
+        return CharSequences.parseDoubles(text, Store.ORDINATE_SEPARATOR);
+    }
+}

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1760061&r1=1760060&r2=1760061&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java
[UTF-8] Fri Sep  9 16:12:52 2016
@@ -108,7 +108,7 @@ public final class Store extends DataSto
     /**
      * 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.
@@ -129,6 +129,7 @@ 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;
 
@@ -151,7 +152,7 @@ public final class Store extends DataSto
      *
      * @see #parseFeatureType(List)
      */
-    private final FeatureType featureType;
+    final FeatureType featureType;
 
     /**
      * {@code true} if {@link #featureType} contains a trajectory column.
@@ -506,158 +507,176 @@ public final class Store extends DataSto
      * @todo Needs to reset the position when doing another pass on the features.
      */
     public Stream<Feature> getFeatures() {
-        return StreamSupport.stream(new Spliterator<Feature>() {
-            /**
-             * Guarantees that we will not return null element.
-             */
-            @Override
-            public int characteristics() {
-                return NONNULL;
-            }
+        return StreamSupport.stream(new Iter(), false);
+    }
 
-            /**
-             * We do not know the number of features.
-             */
-            @Override
-            public long estimateSize() {
-                return Long.MAX_VALUE;
-            }
+    /**
+     * Implementation of the iterator returned by {@link #getFeatures()}.
+     */
+    private final class Iter implements Spliterator<Feature> {
+        /**
+         * Converters from string representations to the values to store in the {@link #values}
array.
+         */
+        private final ObjectConverter<String,?>[] converters;
 
-            /**
-             * Current implementation can not split this iterator.
-             */
-            @Override
-            public Spliterator<Feature> trySplit() {
-                return null;
+        /**
+         * 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 PropertyType> properties = featureType.getProperties(true);
+            converters    = new ObjectConverter[properties.size()];
+            values        = new Object[converters.length];
+            propertyNames = new String[converters.length];
+            int i = -1;
+            for (final PropertyType p : properties) {
+                propertyNames[++i] = p.getName().tip().toString();
+                /*
+                 * 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;
+                        }
+                        /*
+                         * 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.
+                         */
+                    }
+                    case 3: {
+                        if (hasTrajectories) {
+                            c = GeometryParser.INSTANCE;
+                            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: {
+                        c = ObjectConverters.find(String.class, ((AttributeType) p).getValueClass());
+                        break;
+                    }
+                }
+                converters[i] = c;
             }
+        }
 
-            /**
-             * Returns the error message for a file that can not be parsed.
-             */
-            private String canNotParse() {
-                return errors().getString(Errors.Keys.CanNotParseFile_2, "CSV", filename);
+        /**
+         * Executes the given action for the next feature or for all remaining features.
+         *
+         * @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 Feature> action, boolean all) throws
IOException {
+            final FixedSizeList elements = new FixedSizeList(values);
+            String line;
+            while ((line = source.readLine()) != null) {
+                split(line, elements);
+                final Feature 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();
             }
+            return false;
+        }
 
-            /**
-             * Executes the given action only on the next feature, if any.
-             */
-            @Override
-            public boolean tryAdvance(final Consumer<? super Feature> action) {
-                try {
+        /**
+         * Executes the given action only on the next feature, if any.
+         */
+        @Override
+        public boolean tryAdvance(final Consumer<? super Feature> action) {
+            try {
+                synchronized (Store.this) {
                     return read(action, false);
-                } catch (IOException | IllegalArgumentException | DateTimeException e) {
-                    throw new BackingStoreException(canNotParse(), e);
                 }
+            } catch (IOException | IllegalArgumentException | DateTimeException e) {
+                throw new BackingStoreException(canNotParse(), e);
             }
+        }
 
-            /**
-             * Executes the given action only on all remaining features.
-             */
-            @Override
-            public void forEachRemaining(final Consumer<? super Feature> action) {
-                try {
+        /**
+         * Executes the given action only on all remaining features.
+         */
+        @Override
+        public void forEachRemaining(final Consumer<? super Feature> action) {
+            try {
+                synchronized (Store.this) {
                     read(action, true);
-                } catch (IOException | IllegalArgumentException | DateTimeException e) {
-                    throw new BackingStoreException(canNotParse(), e);
                 }
+            } catch (IOException | IllegalArgumentException | DateTimeException e) {
+                throw new BackingStoreException(canNotParse(), e);
             }
+        }
 
-            /**
-             * Executes the given action for the next feature or for all remaining features.
-             *
-             * @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.
-             */
-            @SuppressWarnings({"unchecked", "rawtypes", "fallthrough"})
-            private boolean read(final Consumer<? super Feature> action, boolean all)
throws IOException {
-                final Collection<? extends PropertyType> properties = featureType.getProperties(false);
-                final ObjectConverter<String,?>[] converters = new ObjectConverter[properties.size()];
-                final String[]     propertyNames   = new String[converters.length];
-                final boolean      hasTrajectories = Store.this.hasTrajectories;
-                final TimeEncoding timeEncoding    = Store.this.timeEncoding;
-                final List<String> values          = new ArrayList<>();
-                int i = -1;
-                for (final PropertyType 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, ((AttributeType)
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 Feature 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.
-                             *
-                             * TODO: we should replace that switch case by custom ObjectConverter.
-                             */
-                            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;
-                                }
-                            }
-                            feature.setPropertyValue(name, value);
-                        }
-                        action.accept(feature);
-                        if (!all) return true;
-                    }
-                    values.clear();
-                }
-                return false;
-            }
-        }, false);
+        /**
+         * 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<Feature> 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;
+        }
     }
 
     /**
@@ -667,10 +686,10 @@ public final class Store extends DataSto
      * @param line      the line to parse.
      * @param elements  an initially empty list where to add elements.
      */
-    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)) {
@@ -685,7 +704,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;
                     }
@@ -699,9 +720,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) {

Modified: sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java?rev=1760061&r1=1760060&r2=1760061&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java
[UTF-8] Fri Sep  9 16:12:52 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 java.time.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/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java?rev=1760061&r1=1760060&r2=1760061&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/package-info.java
[UTF-8] Fri Sep  9 16:12:52 2016
@@ -39,7 +39,18 @@
  * }
  *
  * <div class="section">Departures from OGC specification</div>
- * If the character encoding is not explicitely specified, then Apache SIS uses the platform
default instead of UTF-8.
+ * 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

Modified: sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java?rev=1760061&r1=1760060&r2=1760061&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java
[UTF-8] Fri Sep  9 16:12:52 2016
@@ -27,10 +27,15 @@ 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 java.time.Instant;
 import org.opengis.feature.Feature;
+import org.opengis.feature.FeatureType;
+import org.opengis.feature.PropertyType;
+import org.opengis.feature.AttributeType;
 
 
 /**
@@ -47,12 +52,19 @@ public final class StoreTest extends Tes
      * 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" +
+            "@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";
+            "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";
+
+    /**
+     * Returns the instant for the given time at the day of the test.
+     */
+    private static Instant instant(final String time) {
+        return date("2012-01-17 " + time).toInstant();
+    }
 
     /**
      * Tests {@link Store#getMetadata()}.
@@ -75,31 +87,61 @@ public final class StoreTest extends Tes
     }
 
     /**
-     * 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))))
{
+            verifyFeatureType(store.featureType);
             final Iterator<Feature> it = store.getFeatures().iterator();
-            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);
+            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 FeatureType type) {
+        final Iterator<? extends PropertyType> it = type.getProperties(true).iterator();
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref",       String.class,
  1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime",     Instant.class,
 1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime",       Instant.class,
 1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory",    double[].class,
1);
+        assertPropertyTypeEquals((AttributeType<?>) it.next(), "state",         String.class,
  0);
+        assertPropertyTypeEquals((AttributeType<?>) 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 AttributeType<?> 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 Feature f, final String mfidref,
-            final double[] trajectory, final String state, final int typeCode)
+    private static void assertPropertyEquals(final Feature 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);
     }
 }




Mime
View raw message