Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -149,6 +149,11 @@ public final class Resources extends Ind public static final short ProcessingExecutedOn_1 = 12; /** + * The “{1}” element must be declared before “{0}”. + */ + public static final short ShallBeDeclaredBefore_2 = 22; + + /** * Can not move backward in the “{0}” stream. */ public static final short StreamIsForwardOnly_1 = 13;
Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties [ISO-8859-1] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources.properties [ISO-8859-1] Wed Jul 26 16:14:09 2017 @@ -36,6 +36,7 @@ IllegalOutputTypeForWriter_2 = The InconsistentNameComponents_2 = Components of the \u201c{1}\u201d name are inconsistent with those of the name previously binded in \u201c{0}\u201d data store. MissingSchemeInURI_1 = Missing scheme in \u201c{0}\u201d URI. ProcessingExecutedOn_1 = Processing executed on {0}. +ShallBeDeclaredBefore_2 = The \u201c{1}\u201d element must be declared before \u201c{0}\u201d. StreamIsForwardOnly_1 = Can not move backward in the \u201c{0}\u201d stream. StreamIsReadOnce_1 = The \u201c{0}\u201d data store can be read only once. StreamIsWriteOnce_1 = Can not modify previously written data in \u201c{0}\u201d. Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties [ISO-8859-1] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/Resources_fr.properties [ISO-8859-1] Wed Jul 26 16:14:09 2017 @@ -41,6 +41,7 @@ IllegalOutputTypeForWriter_2 = Le l InconsistentNameComponents_2 = Les \u00e9l\u00e9ments qui composent le nom \u00ab\u202f{1}\u202f\u00bb ne sont pas coh\u00e9rents avec ceux du nom qui avait \u00e9t\u00e9 pr\u00e9c\u00e9demment li\u00e9 dans les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb. MissingSchemeInURI_1 = Il manque le sch\u00e9ma dans l\u2019URI \u00ab\u202f{0}\u202f\u00bb. ProcessingExecutedOn_1 = Traitement ex\u00e9cut\u00e9 sur {0}. +ShallBeDeclaredBefore_2 = L\u2019\u00e9l\u00e9ment \u00ab\u202f{1}\u202f\u00bb doit \u00eatre d\u00e9clar\u00e9 avant \u00ab\u202f{0}\u202f\u00bb. StreamIsForwardOnly_1 = Ne peut pas reculer dans le flux de donn\u00e9es \u00ab\u202f{0}\u202f\u00bb. StreamIsReadOnce_1 = Les donn\u00e9es de \u00ab\u202f{0}\u202f\u00bb ne peuvent \u00eatre lues qu\u2019une seule fois. StreamIsWriteOnce_1 = Ne peut pas revenir sur les donn\u00e9es d\u00e9j\u00e0 \u00e9crites dans \u00ab\u202f{0}\u202f\u00bb. Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/GeometryParser.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -21,8 +21,10 @@ 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. + * The converter to use for converting a text into a geometry. + * This converter performs only the first step, the conversion to a {@code double[]} array. + * The second step (the conversion to a geometry object) is performed after we collected all arrays. + * The resulting geometry class depends on the library available at runtime. * * @author Martin Desruisseaux (Geomatys) * @version 0.8 @@ -58,7 +60,7 @@ final class GeometryParser extends Surje } /** - * Converts an element from the CSV file to our current pseudo-geometry type. + * Converts an element from the CSV file to the array type. */ @Override public double[] apply(final String text) { Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/Store.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -18,7 +18,6 @@ package org.apache.sis.internal.storage. import java.util.List; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Locale; @@ -43,12 +42,16 @@ import org.apache.sis.feature.DefaultFea import org.apache.sis.referencing.CRS; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.internal.referencing.GeodeticObjectBuilder; +import org.apache.sis.internal.util.UnmodifiableArrayList; import org.apache.sis.internal.storage.MetadataBuilder; import org.apache.sis.internal.storage.io.IOUtilities; +import org.apache.sis.internal.storage.FeatureStore; +import org.apache.sis.internal.feature.Geometries; +import org.apache.sis.internal.feature.MovingFeature; +import org.apache.sis.internal.storage.Resources; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.metadata.sql.MetadataStoreException; -import org.apache.sis.internal.storage.FeatureStore; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; import org.apache.sis.storage.DataStoreReferencingException; @@ -57,17 +60,12 @@ import org.apache.sis.storage.StorageCon import org.apache.sis.setup.OptionKey; import org.apache.sis.util.ArraysExt; import org.apache.sis.util.CharSequences; -import org.apache.sis.util.ObjectConverter; -import org.apache.sis.util.ObjectConverters; import org.apache.sis.util.resources.Errors; -import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.measure.Units; // Branch-dependent imports import java.time.Instant; import java.time.DateTimeException; -import java.util.function.Consumer; -import java.util.Spliterator; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.opengis.feature.Feature; @@ -120,6 +118,8 @@ public final class Store extends Feature /** * The reader, set by the constructor and cleared when no longer needed. + * + * @see #readLine() */ private BufferedReader source; @@ -153,10 +153,27 @@ public final class Store extends Feature /** * {@code true} if {@link #featureType} contains a trajectory column. + * This field should be considered immutable after {@code Store} construction. + * + * @see #hasTrajectories() */ private boolean hasTrajectories; /** + * The number of dimensions other than time in the coordinate reference system. + * Shall be 2 or 3 according Moving Features CSV encoding specification, but Apache SIS + * may be tolerant to other values (depending on the backing geometry library). + * + * @see #spatialDimensionCount() + */ + private short spatialDimensionCount; + + /** + * The factory to use for creating geometries. + */ + final Geometries<?> geometries; + + /** * Appearing order of trajectories (time or sequential), or {@code null} if unspecified. * * @see #parseFoliation(List) @@ -165,20 +182,39 @@ public final class Store extends Feature /** * Specifies how time is encoded in the CSV file, or {@code null} if there is no time. + * This field should be considered immutable after {@code Store} construction. + * + * @see #timeEncoding() */ private TimeEncoding timeEncoding; /** + * {@code true} if this reader should create a separated {@code Feature} instance for each line in the CSV file. + * By default, this is {@code true} if the CSV files does not seem to contain moving features. + * But the user can also force this value to {@code true}, for example for testing purposes. + */ + private boolean dissociate; + + /** + * All parsed moving features, or {@code null} if none or if not yet parsed. If {@link #dissociate} + * is {@code false}, then this list will be created by {@link #features(boolean)} when first needed. + */ + private transient List<Feature> movingFeatures; + + /** * Creates a new CSV store from the given file, URL or stream. * * <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> + * have an {@link org.apache.sis.setup.OptionKey#ENCODING} value set to UTF-8.</p> * * @param provider the factory that created this {@code DataStore} instance, or {@code null} if unspecified. * @param connector information about the storage (URL, stream, <i>etc</i>). + * @param immediate {@code true} for forcing the creation of a distinct {@code Feature} instance for each line. * @throws DataStoreException if an error occurred while opening the stream. */ - public Store(final StoreProvider provider, final StorageConnector connector) throws DataStoreException { + public Store(final StoreProvider provider, final StorageConnector connector, final boolean immediate) + throws DataStoreException + { super(provider, connector); final Reader r = connector.getStorageAs(Reader.class); connector.closeAllExcept(r); @@ -186,6 +222,8 @@ public final class Store extends Feature throw new DataStoreException(Errors.format(Errors.Keys.CanNotOpen_1, super.getDisplayName())); } source = (r instanceof BufferedReader) ? (BufferedReader) r : new LineNumberReader(r); + geometries = Geometries.implementation(connector.getOption(OptionKey.GEOMETRY_LIBRARY)); + dissociate = immediate; GeneralEnvelope envelope = null; FeatureType featureType = null; Foliation foliation = null; @@ -206,7 +244,12 @@ public final class Store extends Feature if (envelope != null) { throw new DataStoreContentException(duplicated("@stboundedby")); } - envelope = parseEnvelope(elements); + if (featureType != null) { + throw new DataStoreContentException(Resources.forLocale(getLocale()) + .getString(Resources.Keys.ShallBeDeclaredBefore_2, "@columns", "@stboundedby")); + } + envelope = parseEnvelope(elements); // Also set 'timeEncoding' and 'spatialDimensionCount'. + dissociate |= (timeEncoding == null); // Need to be updated before parseFeatureType(…) execution. break; } case "@columns": { @@ -236,16 +279,17 @@ public final class Store extends Feature } source.reset(); } catch (IOException e) { - throw new DataStoreException(getLocale(), "CSV", super.getDisplayName(), source).initCause(e); + throw new DataStoreException(getLocale(), StoreProvider.NAME, super.getDisplayName(), source).initCause(e); } catch (FactoryException e) { - throw new DataStoreReferencingException(getLocale(), "CSV", super.getDisplayName(), source).initCause(e); + throw new DataStoreReferencingException(getLocale(), StoreProvider.NAME, super.getDisplayName(), source).initCause(e); } catch (IllegalArgumentException | DateTimeException e) { - throw new DataStoreContentException(getLocale(), "CSV", super.getDisplayName(), source).initCause(e); + throw new DataStoreContentException(getLocale(), StoreProvider.NAME, super.getDisplayName(), source).initCause(e); } this.encoding = connector.getOption(OptionKey.ENCODING); this.envelope = envelope; this.featureType = featureType; this.foliation = foliation; + this.dissociate |= (timeEncoding == null); } /** @@ -257,6 +301,8 @@ public final class Store extends Feature * @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 * } * + * This method sets {@link #timeEncoding} and {@link #spatialDimensionCount} as a side-effect. + * * @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. */ @@ -264,6 +310,7 @@ public final class Store extends Feature private GeneralEnvelope parseEnvelope(final List<String> elements) throws DataStoreException, FactoryException { CoordinateReferenceSystem crs = null; int spatialDimensionCount = 2; + boolean isDimExplicit = false; double[] lowerCorner = ArraysExt.EMPTY_DOUBLE; double[] upperCorner = ArraysExt.EMPTY_DOUBLE; Instant startTime = null; @@ -278,8 +325,9 @@ public final class Store extends Feature 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') { + isDimExplicit = true; spatialDimensionCount = element.charAt(0) - '0'; - if (spatialDimensionCount < 2 || spatialDimensionCount > 3) { + if (spatialDimensionCount < 1 || spatialDimensionCount > 3) { throw new DataStoreReferencingException(errors().getString( Errors.Keys.IllegalCoordinateSystem_1, element)); } @@ -326,12 +374,33 @@ public final class Store extends Feature int count = 0; final CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3]; components[count++] = crs; - - // 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(); + /* + * If the coordinates are three-dimensional but the CRS is 2D, add a vertical axis. + * The vertical axis shall be the third one, however we do not enforce that rule + * since Apache SIS should work correctly even if the vertical axis is elsewhere. + */ + int dimension = crs.getCoordinateSystem().getDimension(); + if (isDimExplicit) { + if (spatialDimensionCount > dimension) { + components[count++] = CommonCRS.Vertical.MEAN_SEA_LEVEL.crs(); + dimension++; + } + if (dimension != spatialDimensionCount) { + throw new DataStoreReferencingException(errors().getString( + Errors.Keys.MismatchedDimension_3, "@stboundedby(CRS)", spatialDimensionCount, dimension)); + } + } + if (dimension > Short.MAX_VALUE) { + throw new DataStoreReferencingException(errors().getString( + Errors.Keys.ExcessiveNumberOfDimensions_1, dimension)); } - // Add a temporal axis if we have a start time (no need for end time). + spatialDimensionCount = dimension; + /* + * Add a temporal axis if we have a start time (no need for end time). + * This block presumes that the CRS does not already have a time axis. + * If a time axis was already present, an exception will be thrown at + * builder.createCompoundCRS(…) invocation time. + */ final GeodeticObjectBuilder builder = new GeodeticObjectBuilder(); String name = crs.getName().getCode(); if (startTime != null) { @@ -366,7 +435,7 @@ public final class Store extends Feature (dim = upperCorner.length) != spatialDimensionCount) { throw new DataStoreReferencingException(errors().getString( - Errors.Keys.MismatchedDimension_2, dim, spatialDimensionCount)); + Errors.Keys.MismatchedDimension_3, "@stboundedby(BBOX)", spatialDimensionCount, dim)); } for (int i=0; i<spatialDimensionCount; i++) { envelope.setRange(i, lowerCorner[i], upperCorner[i]); @@ -375,6 +444,7 @@ public final class Store extends Feature envelope.setRange(spatialDimensionCount, timeEncoding.toCRS(startTime.toEpochMilli()), (endTime == null) ? Double.NaN : timeEncoding.toCRS(endTime.toEpochMilli())); } + this.spatialDimensionCount = (short) spatialDimensionCount; return envelope; } @@ -387,10 +457,15 @@ public final class Store extends Feature * @columns, mfidref, trajectory, state,xsd:token, "type code",xsd:integer * } * - * @param elements the line elements. The first elements should be {@code "@columns"}. + * This method needs {@link #timeEncoding} and {@link #dissociate} to be computed. + * This methods sets {@link #hasTrajectories} as a side-effect. + * + * @param elements the line elements. The first element should be {@code "@columns"}. * @return the column metadata, or {@code null} if the given list does not contain enough elements. */ + @SuppressWarnings("rawtypes") // "rawtypes" because of generic array creation. private FeatureType parseFeatureType(final List<String> elements) throws DataStoreException { + AttributeType[] characteristics = null; final int size = elements.size(); final List<PropertyType> properties = new ArrayList<>(); for (int i=1; i<size; i++) { @@ -412,6 +487,7 @@ public final class Store extends Feature } } int minOccurrence = 0; + int maxOccurrence = dissociate ? 1 : Integer.MAX_VALUE; if (type == null) { /* * If the column name was not followed by a type, default to a String type except in the special @@ -426,22 +502,33 @@ public final class Store extends Feature */ type = String.class; switch (--i) { - case 1: minOccurrence = 1; break; - case 2: { + case 0: // "@column" (should not happen actually) + case 1: { + minOccurrence = 1; // "mfidref" + maxOccurrence = 1; + break; + } + case 2: { // "trajectory" or property. if (name.equalsIgnoreCase("trajectory")) { hasTrajectories = true; if (timeEncoding != null) { - properties.add(createProperty("startTime", Instant.class, 1)); - properties.add(createProperty( "endTime", Instant.class, 1)); + properties.add(createProperty("startTime", Instant.class, 1, 1, null)); + properties.add(createProperty( "endTime", Instant.class, 1, 1, null)); + } + if (dissociate) { + type = double[].class; + } else { + type = geometries.polylineClass; + characteristics = new AttributeType[] {MovingFeature.TIME}; } - type = double[].class; minOccurrence = 1; + maxOccurrence = 1; } break; } } } - properties.add(createProperty(name, type, minOccurrence)); + properties.add(createProperty(name, type, minOccurrence, maxOccurrence, characteristics)); } String name = super.getDisplayName(); final int s = name.lastIndexOf('.'); @@ -454,9 +541,13 @@ public final class Store extends Feature /** * Creates a property type for the given name and type. + * This is a helper method for {@link #parseFeatureType(List)}. */ - private static PropertyType createProperty(final String name, final Class<?> type, final int minOccurrence) { - return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name), type, minOccurrence, 1, null); + private static PropertyType createProperty(final String name, final Class<?> type, + final int minOccurrence, final int maxOccurrence, final AttributeType<?>[] characteristics) + { + return new DefaultAttributeType<>(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name), + type, minOccurrence, maxOccurrence, null, characteristics); } /** @@ -489,16 +580,16 @@ public final class Store extends Feature if (metadata == null) { final MetadataBuilder builder = new MetadataBuilder(); try { - builder.setFormat(timeEncoding != null && hasTrajectories ? "CSV-MF" : "CSV"); + builder.setFormat(timeEncoding != null && hasTrajectories ? StoreProvider.MOVING : StoreProvider.NAME); } catch (MetadataStoreException e) { listeners.warning(null, e); } - builder.add(encoding, MetadataBuilder.Scope.ALL); - builder.add(ScopeCode.DATASET); + builder.addEncoding(encoding, MetadataBuilder.Scope.ALL); + builder.addResourceScope(ScopeCode.DATASET, null); try { builder.addExtent(envelope); } catch (TransformException e) { - throw new DataStoreReferencingException(getLocale(), "CSV", getDisplayName(), source).initCause(e); + throw new DataStoreReferencingException(getLocale(), StoreProvider.NAME, getDisplayName(), source).initCause(e); } catch (UnsupportedOperationException e) { /* * Failed to set the temporal components if the sis-temporal module was @@ -506,7 +597,7 @@ public final class Store extends Feature */ listeners.warning(null, e); } - builder.add(featureType, null); + builder.addFeatureType(featureType, null); metadata = builder.build(true); } return metadata; @@ -540,182 +631,31 @@ public final class Store extends Feature /** * Returns the stream of features. * + * @param parallel {@code true} for a parallel stream, or {@code false} for a sequential stream. * @return a stream over all features in the CSV file. + * @throws DataStoreException if an error occurred while creating the feature stream. * * @todo Needs to reset the position when doing another pass on the features. + * @todo If sequential order, publish Feature as soon as identifier changed. */ @Override - public Stream<Feature> features() { - return StreamSupport.stream(new Iter(), false); - } - - /** - * Implementation of the iterator returned by {@link #features()}. - */ - 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; - - /** - * 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; - } - } - - /** - * 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 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 { - return read(action, false); - } catch (IOException | IllegalArgumentException | DateTimeException e) { - throw new BackingStoreException(canNotParseFile(), e); - } - } - - /** - * Executes the given action on all remaining features. - */ - @Override - public void forEachRemaining(final Consumer<? super Feature> action) { - try { - read(action, true); - } catch (IOException | IllegalArgumentException | DateTimeException e) { - throw new BackingStoreException(canNotParseFile(), e); - } - } - - /** - * 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; - } - - /** - * Returns the characteristics of the iteration over feature instances. - * The iteration is assumed {@link #ORDERED} in the declaration order in the CSV file. - * The iteration is {@link #NONNULL} (i.e. {@link #tryAdvance(Consumer)} is not allowed - * to return null value) and {@link #IMMUTABLE} (i.e. we do not support modification of - * the CSV file while an iteration is in progress). - * - * @return characteristics of iteration over the features in the CSV file. - */ - @Override - public int characteristics() { - return ORDERED | NONNULL | IMMUTABLE; + public synchronized Stream<Feature> features(final boolean parallel) throws DataStoreException { + /* + * If the user asks for one feature instance per line, then we can return a FeatureIter instance directly. + * Since each feature is fully constructed from a single line and each line are read atomically, we can + * parallelize this mode. + */ + if (dissociate) { + return StreamSupport.stream(new FeatureIterator(this), parallel); + } + if (movingFeatures == null) try { + final MovingFeatureIterator iter = new MovingFeatureIterator(this); + iter.readMoving(null, true); + movingFeatures = UnmodifiableArrayList.wrap(iter.createMovingFeatures()); + } catch (IOException | IllegalArgumentException | DateTimeException e) { + throw new DataStoreException(canNotParseFile(), e); } + return movingFeatures.stream(); } /** @@ -786,6 +726,34 @@ public final class Store extends Feature } /** + * Returns {@code true} if {@link #featureType} contains a trajectory column. + */ + final boolean hasTrajectories() { + return hasTrajectories; + } + + /** + * Returns the number of dimensions other than time in the coordinate reference system. + */ + final int spatialDimensionCount() { + return spatialDimensionCount; + } + + /** + * Returns an indication of how time is encoded in the CSV file, or {@code null} if there is no time. + */ + final TimeEncoding timeEncoding() { + return timeEncoding; + } + + /** + * Reads the next line from the source CSV file. + */ + final String readLine() throws IOException { + return source.readLine(); + } + + /** * Returns an error message for a duplicated element. */ private String duplicated(final String name) { @@ -797,8 +765,7 @@ public final class Store extends Feature * The error message will contain the line number if available. */ final String canNotParseFile() { - final Object[] parameters = IOUtilities.errorMessageParameters("CSV", getDisplayName(), source); - return errors().getString(IOUtilities.errorMessageKey(parameters), parameters); + return IOUtilities.canNotReadFile(getLocale(), StoreProvider.NAME, getDisplayName(), source); } /** @@ -809,6 +776,16 @@ public final class Store extends Feature } /** + * Logs a warning as if it originated from the {@link #features(boolean)} method. + * This is a callback method for {@link FeatureIterator}. + */ + final void log(final LogRecord warning) { + warning.setSourceClassName(Store.class.getName()); + warning.setSourceMethodName("features"); + listeners.warning(warning); + } + + /** * Closes this data store and releases any underlying resources. * * @throws DataStoreException if an error occurred while closing this data store. Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/StoreProvider.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -42,6 +42,11 @@ import org.apache.sis.internal.storage.w @Capabilities(Capability.READ) public final class StoreProvider extends DataStoreProvider { /** + * The format names for static features and moving features. + */ + static final String NAME = "CSV", MOVING = "CSV-MF"; + + /** * The object to use for verifying if the first keyword is the expected one. */ private static final class Peek extends FirstKeywordPeek { @@ -108,7 +113,7 @@ public final class StoreProvider extends */ @Override public String getShortName() { - return "CSV"; + return NAME; } /** @@ -133,6 +138,6 @@ public final class StoreProvider extends */ @Override public DataStore open(final StorageConnector connector) throws DataStoreException { - return new Store(this, connector); + return new Store(this, connector, false); } } Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/csv/TimeEncoding.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -20,6 +20,7 @@ import javax.measure.Unit; import javax.measure.quantity.Time; import org.opengis.referencing.datum.TemporalDatum; import org.apache.sis.internal.converter.SurjectiveConverter; +import org.apache.sis.internal.util.StandardDateFormat; import org.apache.sis.referencing.CommonCRS; import org.apache.sis.measure.Units; @@ -96,7 +97,12 @@ class TimeEncoding extends SurjectiveCon 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)); + return Instant.ofEpochMilli(millis + origin) + .plusNanos(Math.round((value - millis) * StandardDateFormat.NANOS_PER_MILLISECOND)); + /* + * Performance note: the call to .plusNano(…) will usually return the same 'Instant' instance + * (without creating new object) since the time granularity is rarely finer than milliseconds. + */ } /** Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/ChannelDataInput.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -29,6 +29,7 @@ import java.nio.DoubleBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.SeekableByteChannel; import org.apache.sis.internal.storage.Resources; +import org.apache.sis.io.InvalidSeekException; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.Debug; @@ -879,7 +880,7 @@ public class ChannelDataInput extends Ch /* * Requested position is before the current buffer limits and we can not seek. */ - throw new IOException(Resources.format(Resources.Keys.StreamIsForwardOnly_1, filename)); + throw new InvalidSeekException(Resources.format(Resources.Keys.StreamIsForwardOnly_1, filename)); } clearBitOffset(); } Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/HyperRectangleReader.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -45,8 +45,11 @@ public final class HyperRectangleReader /** * The {@code input} position of the first sample (ignoring sub-area and sub-sampling). + * This is the {@code origin} argument given to the constructor, copied verbatim. + * This field is public for callers wanting to order {@code HyperRectangleReader} instances + * in increasing file offset order, for more sequential reading (less seek operations). */ - private final long origin; + public final long origin; /** * Creates a new reader for the given input and source region. Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -528,6 +528,23 @@ public final class IOUtilities extends S } /** + * Returns the error message for a file that can not be parsed. + * The error message will contain the line number if available. + * + * @param locale the language for the error message. + * @param format abbreviation of the file format (e.g. "CSV", "GML", "WKT", <i>etc</i>). + * @param filename name of the file or the data store. + * @param store the input or output object, or {@code null}. + * @return the parameters for a localized error message for a file that can not be processed. + * + * @since 0.8 + */ + public static String canNotReadFile(final Locale locale, final String format, final String filename, final Object store) { + final Object[] parameters = errorMessageParameters(format, filename, store); + return Resources.forLocale(locale).getString(errorMessageKey(parameters), parameters); + } + + /** * Returns the {@link Resources.Keys} value together with the parameters given by {@code errorMessageParameters(…)}. * * @param parameters the result of {@code errorMessageParameters(…)} method call. Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/wkt/Store.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -26,23 +26,25 @@ import java.io.IOException; import java.text.ParsePosition; import java.text.ParseException; import org.opengis.metadata.Metadata; +import org.opengis.util.FactoryException; import org.opengis.referencing.ReferenceSystem; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.internal.storage.Resources; +import org.apache.sis.internal.system.Loggers; import org.apache.sis.io.wkt.WKTFormat; import org.apache.sis.io.wkt.Warnings; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.StorageConnector; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; +import org.apache.sis.internal.referencing.DefinitionVerifier; import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.util.resources.Errors; import org.apache.sis.util.CharSequences; -import org.apache.sis.referencing.CRS; /** * A data store which creates data objects from a WKT definition. - * This {@code DataStore} implementation is basically a facade for the {@link CRS#fromWKT(String)} method. * * @author Martin Desruisseaux (Geomatys) * @version 0.8 @@ -118,17 +120,35 @@ final class Store extends DataStore { } finally { in.close(); } + /* + * At this point we copied all the file content into a String. This string usually contain exactly + * one WKT definitions, but this DataStore nevertheless allows an arbitrary number of consecutive + * definitions. + */ final ParsePosition pos = new ParsePosition(0); final WKTFormat parser = new WKTFormat(null, null); do { - objects.add(parser.parse(wkt, pos)); + final Object obj = parser.parse(wkt, pos); + objects.add(obj); pos.setIndex(CharSequences.skipLeadingWhitespaces(wkt, pos.getIndex(), wkt.length())); final Warnings warnings = parser.getWarnings(); if (warnings != null) { - final LogRecord record = new LogRecord(Level.WARNING, warnings.toString()); - record.setSourceClassName(Store.class.getName()); - record.setSourceMethodName("getMetadata"); // Public facade for this method. - listeners.warning(record); + log(new LogRecord(Level.WARNING, warnings.toString())); + } + /* + * The WKT has been parsed. Below is a verification of whether the parsed WKT is conform with + * the authority definition (if an authority code has been specified). This verification is not + * really necessary since we will use the WKT definition anyway even if we find discrepancies. + * But non-conform WKT definitions happen so often in practice that we are better to check. + */ + if (obj instanceof CoordinateReferenceSystem) try { + final DefinitionVerifier v = DefinitionVerifier.withAuthority((CoordinateReferenceSystem) obj, null, false); + if (v != null) { + final LogRecord warning = v.warning(false); + if (warning != null) log(warning); + } + } catch (FactoryException e) { + listeners.warning(null, e); } } while (pos.getIndex() < wkt.length()); } catch (ParseException e) { @@ -139,6 +159,16 @@ final class Store extends DataStore { } /** + * Reports a warning. + */ + private void log(final LogRecord record) { + record.setSourceClassName(Store.class.getName()); + record.setSourceMethodName("getMetadata"); // Public facade for the parse() method. + record.setLoggerName(Loggers.WKT); + listeners.warning(record); + } + + /** * Returns the metadata associated to the parsed objects, or {@code null} if none. * The current implementation retains only instances of {@link ReferenceSystem} * and ignore other cases. Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/xml/Store.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -26,7 +26,9 @@ import java.io.IOException; import javax.xml.bind.JAXBException; import javax.xml.transform.stream.StreamSource; import org.opengis.metadata.Metadata; +import org.opengis.util.FactoryException; import org.opengis.referencing.ReferenceSystem; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.apache.sis.xml.XML; import org.apache.sis.storage.DataStore; import org.apache.sis.storage.StorageConnector; @@ -34,6 +36,8 @@ import org.apache.sis.storage.DataStoreE import org.apache.sis.metadata.iso.DefaultMetadata; import org.apache.sis.util.logging.WarningListener; import org.apache.sis.util.resources.Errors; +import org.apache.sis.internal.system.Loggers; +import org.apache.sis.internal.referencing.DefinitionVerifier; import static java.util.Collections.singleton; @@ -124,6 +128,7 @@ final class Store extends DataStore { /** Reports the occurrence of a non-fatal error during XML unmarshalling. */ @Override public void warningOccured(final Object source, final LogRecord warning) { + warning.setLoggerName(Loggers.XML); listeners.warning(warning); } }); @@ -150,6 +155,26 @@ final class Store extends DataStore { } catch (JAXBException | IOException e) { throw new DataStoreException(Errors.format(Errors.Keys.CanNotRead_1, getDisplayName()), e); } + if (object instanceof CoordinateReferenceSystem) try { + final DefinitionVerifier v = DefinitionVerifier.withAuthority((CoordinateReferenceSystem) object, null, false); + if (v != null) { + log(v.warning(false)); + } + } catch (FactoryException e) { + listeners.warning(null, e); + } + } + + /** + * Reports a warning, if non-null. + */ + private void log(final LogRecord record) { + if (record != null) { + record.setSourceClassName(Store.class.getName()); + record.setSourceMethodName("getMetadata"); // Public facade for the parse() method. + record.setLoggerName(Loggers.XML); + listeners.warning(record); + } } /** Modified: sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/main/java/org/apache/sis/storage/StorageConnector.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -474,8 +474,6 @@ public class StorageConnector implements ByteBuffer buffer = getOption(OptionKey.BYTE_BUFFER); if (buffer == null) { buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); - // TODO: we do not create direct buffer yet, but this is something - // we may want to consider in a future SIS version. } if (asImageInputStream) { asDataInput = new ChannelImageInputStream(name, channel, buffer, false); Modified: sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/csv/StoreTest.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -16,6 +16,7 @@ */ package org.apache.sis.internal.storage.csv; +import java.util.Arrays; import java.util.Iterator; import java.io.StringReader; import org.opengis.metadata.Metadata; @@ -25,8 +26,11 @@ import org.apache.sis.storage.DataStoreE import org.apache.sis.storage.StorageConnector; import org.apache.sis.test.TestCase; import org.junit.Test; +import com.esri.core.geometry.Point2D; +import com.esri.core.geometry.Polyline; import static org.junit.Assert.*; +import static java.util.Collections.singletonList; import static org.apache.sis.test.TestUtilities.date; import static org.apache.sis.test.TestUtilities.getSingleton; @@ -48,6 +52,11 @@ import org.opengis.feature.AttributeType */ public final strictfp class StoreTest extends TestCase { /** + * {@code true} if testing a moving feature, or {@code false} (the default) if testing a static feature. + */ + private boolean isMovingFeature; + + /** * An example of Moving Features file. * Derived from the example provided in OGC 14-084r2. */ @@ -77,7 +86,7 @@ public final strictfp class StoreTest ex @Test public void testGetMetadata() throws DataStoreException { final Metadata metadata; - try (Store store = new Store(null, new StorageConnector(testData()))) { + try (Store store = new Store(null, new StorageConnector(testData()), true)) { metadata = store.getMetadata(); } final Extent extent = getSingleton(getSingleton(metadata.getIdentificationInfo()).getExtents()); @@ -90,16 +99,16 @@ public final strictfp class StoreTest ex } /** - * Verifies the feature type, then tests {@link Store#features()}. + * Verifies the feature type, then tests {@link Store#features(boolean)}. * * @throws DataStoreException if an error occurred while parsing the data. */ @Test - public void testGetFeatures() throws DataStoreException { - try (Store store = new Store(null, new StorageConnector(testData()))) { - verifyFeatureType(store.featureType); + public void testStaticFeatures() throws DataStoreException { + try (Store store = new Store(null, new StorageConnector(testData()), true)) { + verifyFeatureType(store.featureType, double[].class, 1); assertEquals("foliation", Foliation.TIME, store.foliation); - final Iterator<Feature> it = store.features().iterator(); + final Iterator<Feature> it = store.features(false).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); @@ -109,16 +118,44 @@ public final strictfp class StoreTest ex } /** + * Tests reading the data as a moving features. In the following data: + * + * {@preformat text + * 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 + * c, 10, 190, 12.0 1.0 10.0 2.0 11.0 3.0, vehicle, 1 + * } + * + * the two rows for the "a" features shall be merged in a single trajectory. + * + * @throws DataStoreException if an error occurred while parsing the data. + */ + @Test + public void testMovingFeatures() throws DataStoreException { + isMovingFeature = true; + try (Store store = new Store(null, new StorageConnector(testData()), false)) { + verifyFeatureType(store.featureType, Polyline.class, Integer.MAX_VALUE); + assertEquals("foliation", Foliation.TIME, store.foliation); + final Iterator<Feature> it = store.features(false).iterator(); + assertPropertyEquals(it.next(), "a", "12:33:51", "12:36:51", new double[] {11, 2, 12, 3, 10, 3}, singletonList("walking"), Arrays.asList(1, 2)); + assertPropertyEquals(it.next(), "b", "12:33:51", "12:36:51", new double[] {10, 2, 11, 3}, singletonList("walking"), singletonList(2)); + assertPropertyEquals(it.next(), "c", "12:33:51", "12:36:51", new double[] {12, 1, 10, 2, 11, 3}, singletonList("vehicle"), singletonList(1)); + assertFalse(it.hasNext()); + } + } + + /** * Verifies that the feature type is equal to the expected one. */ - private static void verifyFeatureType(final FeatureType type) { + private static void verifyFeatureType(final FeatureType type, final Class<?> geometryType, final int maxOccurs) { 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); + assertPropertyTypeEquals((AttributeType<?>) it.next(), "mfidref", String.class, 1, 1); + assertPropertyTypeEquals((AttributeType<?>) it.next(), "startTime", Instant.class, 1, 1); + assertPropertyTypeEquals((AttributeType<?>) it.next(), "endTime", Instant.class, 1, 1); + assertPropertyTypeEquals((AttributeType<?>) it.next(), "trajectory", geometryType, 1, 1); + assertPropertyTypeEquals((AttributeType<?>) it.next(), "state", String.class, 0, maxOccurs); + assertPropertyTypeEquals((AttributeType<?>) it.next(), "\"type\" code", Integer.class, 0, maxOccurs); assertFalse(it.hasNext()); } @@ -126,26 +163,42 @@ public final strictfp class StoreTest ex * 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) + final String name, final Class<?> valueClass, final int minOccurs, final int maxOccurs) { assertEquals("name", name, p.getName().toString()); assertEquals("valueClass", valueClass, p.getValueClass()); assertEquals("minOccurs", minOccurs, p.getMinimumOccurs()); - assertEquals("maxOccurs", 1, p.getMaximumOccurs()); + assertEquals("maxOccurs", maxOccurs, p.getMaximumOccurs()); } /** * Asserts that the property of the given name in the given feature has expected information. */ - private static void assertPropertyEquals(final Feature f, final String mfidref, + private void assertPropertyEquals(final Feature f, final String mfidref, final String startTime, final String endTime, final double[] trajectory, - final String state, final int typeCode) + final Object state, final Object typeCode) { - 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); + 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")); + if (isMovingFeature) { + assertPolylineEquals(trajectory, (Polyline) f.getPropertyValue("trajectory")); + } else { + assertArrayEquals("trajectory", trajectory, (double[]) f.getPropertyValue("trajectory"), STRICT); + } + } + + /** + * Asserts that the given polyline contains the expected coordinate values. + */ + private static void assertPolylineEquals(final double[] trajectory, final Polyline polyline) { + assertEquals("pointCount", trajectory.length / 2, polyline.getPointCount()); + for (int i=0; i < trajectory.length;) { + final Point2D xy = polyline.getXY(i / 2); + assertEquals("x", trajectory[i++], xy.x, STRICT); + assertEquals("y", trajectory[i++], xy.y, STRICT); + } } } Modified: sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-storage/src/test/java/org/apache/sis/storage/StorageConnectorTest.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -34,6 +34,7 @@ import org.apache.sis.test.DependsOn; import org.apache.sis.test.TestCase; import org.junit.Test; +import static org.junit.Assume.*; import static org.opengis.test.Assert.*; @@ -41,7 +42,7 @@ import static org.opengis.test.Assert.*; * Tests {@link StorageConnector}. * * @author Martin Desruisseaux (Geomatys) - * @version 0.7 + * @version 0.8 * @since 0.3 * @module */ @@ -179,7 +180,11 @@ public final strictfp class StorageConne /* * Request again the InputStream and read the same amount of bytes than above. The intend of this test * is to verify that StorageConnector has reseted the InputStream position before to return it. + * Note that this test requires InputStream implementations supporting mark/reset operations + * (which is the case when the resource is an ordinary file, not an entry inside a JAR file), + * otherwise the call to connection.getStorageAs(…) throws a DataStoreException. */ + assumeTrue("Can not use a JAR file entry for this test.", in.markSupported()); assertSame(in, connection.getStorageAs(InputStream.class)); final byte[] actual = new byte[sample.length]; assertEquals("Should read all requested bytes.", actual.length, in.read(actual)); Modified: sis/branches/JDK9/storage/sis-xmlstore/pom.xml URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-xmlstore/pom.xml?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-xmlstore/pom.xml (original) +++ sis/branches/JDK9/storage/sis-xmlstore/pom.xml Wed Jul 26 16:14:09 2017 @@ -112,6 +112,11 @@ Read and write files in the GPX format. <artifactId>sis-storage</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>com.esri.geometry</groupId> + <artifactId>esri-geometry-api</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> Modified: sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/GroupAsPolylineOperation.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -17,9 +17,9 @@ package org.apache.sis.internal.storage.gpx; import java.util.Map; +import java.util.Iterator; import java.util.Collection; import java.util.Collections; -import com.esri.core.geometry.Polyline; import org.opengis.parameter.ParameterDescriptorGroup; import org.opengis.parameter.ParameterValueGroup; import org.apache.sis.feature.AbstractAttribute; @@ -27,6 +27,7 @@ import org.apache.sis.feature.AbstractOp import org.apache.sis.feature.DefaultAttributeType; import org.apache.sis.internal.feature.AttributeConvention; import org.apache.sis.internal.feature.FeatureUtilities; +import org.apache.sis.internal.feature.Geometries; import org.apache.sis.util.resources.Errors; // Branch-dependent imports @@ -38,22 +39,30 @@ import org.opengis.feature.AttributeType /** * Creates a single (Multi){@code Polyline} instance from a sequence of points or polylines stored in another property. - * This base class expects a sequence of {@link Polyline} as input, but subclass will expect other kind of geometries. + * This base class expects a sequence of {@code Point} or {@code Polyline} instances as input. * The single (Multi){@code Polyline} instance is re-computed every time this property is requested. * - * <div class="note"><b>Example:</b> + * <div class="note"><b>Examples:</b> + * <p><i>Polylines created from points:</i> + * a boat that record it's position every hour. + * The list of all positions is stored in an attribute with [0 … ∞] cardinality. + * This class will extract each position and create a line as a new attribute. + * Any change applied to the positions will be visible on the line.</p> + * + * <p><i>Polylines created from other polylines:</i> * a boat that record track every hour. * The list of all tracks is stored in an attribute with [0 … ∞] cardinality. * This class will extract each track and create a polyline as a new attribute. - * Any change applied to the tracks will be visible on the polyline. + * Any change applied to the tracks will be visible on the polyline.</p> * </div> * * @author Johann Sorel (Geomatys) + * @author Martin Desruisseaux (Geomatys) * @version 0.8 * @since 0.8 * @module */ -class GroupAsPolylineOperation extends AbstractOperation { +final class GroupAsPolylineOperation extends AbstractOperation { /** * For cross-version compatibility. */ @@ -65,27 +74,38 @@ class GroupAsPolylineOperation extends A private static final ParameterDescriptorGroup EMPTY_PARAMS = FeatureUtilities.parameters("GroupPolylines"); /** - * The type of the values computed by this operation. The name of this type presumes - * that the result will be assigned to the "geometry" attribute of the feature type. + * Name of the property to follow in order to get the geometries to add to a polyline. + * This property shall be a feature association, usually with [0 … ∞] cardinality. */ - static final AttributeType<Polyline> RESULT_TYPE = new DefaultAttributeType<>( - Collections.singletonMap(NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY), Polyline.class, 1, 1, null); + private final String association; /** - * Name of the property to follow in order to get the geometries to add to a polyline. - * This property shall be a feature association, usually with [0 … ∞] cardinality. + * The expected result type to be returned by {@link #getResult()}. */ - final String association; + private final AttributeType<?> result; /** * Creates a new operation which will look for geometries in the given feature association. * * @param identification name and other information to be given to this operation. * @param association name of the property to follow in order to get the geometries to add to a polyline. + * @param result the expected result type to be returned by {@link #getResult()}. */ - GroupAsPolylineOperation(final Map<String,?> identification, final String association) { + GroupAsPolylineOperation(final Map<String,?> identification, final String association, final AttributeType<?> result) { super(identification); this.association = association; + this.result = result; + } + + /** + * Creates the {@code result} argument for the constructor. This creation is provided in a separated method + * because the same instance will be shared by many {@code GroupAsPolylineOperation} instances. + * + * @param geometries accessor to the geometry implementation in use (Java2D, ESRI or JTS). + */ + static <G> AttributeType<? extends G> getResult(final Geometries<G> geometries) { + return new DefaultAttributeType<>(Collections.singletonMap(NAME_KEY, AttributeConvention.ENVELOPE_PROPERTY), + geometries.polylineClass, 1, 1, null); } /** @@ -100,8 +120,8 @@ class GroupAsPolylineOperation extends A * Returns the expected result type. */ @Override - public final AttributeType<Polyline> getResult() { - return RESULT_TYPE; + public final AttributeType<?> getResult() { + return result; } /** @@ -110,19 +130,9 @@ class GroupAsPolylineOperation extends A * the result will be recomputed. */ @Override + @SuppressWarnings({"rawtypes", "unchecked"}) public final Property apply(Feature feature, ParameterValueGroup parameters) { - return new Result(feature); - } - - /** - * Invoked for every geometric objects to put in a single polyline. - * - * @param addTo where to add the geometry object. - * @param geometry the point or polyline to add to {@code addTo}. - * @param isFirst whether {@code geometry} is the first object added to the given polyline. - */ - void addGeometry(final Polyline addTo, final Object geometry, final boolean isFirst) { - addTo.add((Polyline) geometry, false); + return new Result(feature, association, result); } @@ -132,8 +142,10 @@ class GroupAsPolylineOperation extends A * Note that the cache is not used when {@link #apply(Feature, ParameterValueGroup)} is invoked, * causing a new value to be computed again. The intend is to behave as if the operation has been * executed at {@code apply(…)} invocation time, even if we deferred the actual execution. + * + * @param <G> the root geometry class (implementation-dependent). */ - private final class Result extends AbstractAttribute<Polyline> { + private static final class Result<G> extends AbstractAttribute<G> { /** * For cross-version compatibility. */ @@ -145,31 +157,43 @@ class GroupAsPolylineOperation extends A private final Feature feature; /** + * Name of the property to follow in order to get the geometries to add to a polyline. + * This property shall be a feature association, usually with [0 … ∞] cardinality. + */ + private final String association; + + /** * The result, computed when first needed. */ - private transient Polyline geometry; + private transient G geometry; /** * Creates a new result for an execution on the given feature. * The actual computation is deferred to the first call of {@link #getValue()}. */ - Result(final Feature feature) { - super(RESULT_TYPE); + Result(final Feature feature, final String association, final AttributeType<G> result) { + super(result); this.feature = feature; + this.association = association; } /** - * Computes the geometry from all points of polylines found in the associated feature. + * Computes the geometry from all points or polylines found in the associated feature. */ @Override - public Polyline getValue() { + public G getValue() { if (geometry == null) { - boolean isFirst = true; - geometry = new Polyline(); - for (final Object child : (Collection<?>) feature.getPropertyValue(association)) { - addGeometry(geometry, ((Feature) child).getPropertyValue("sis:geometry"), isFirst); - isFirst = false; - } + final Iterator<?> it = ((Collection<?>) feature.getPropertyValue(association)).iterator(); + final Object geom = Geometries.mergePolylines(new Iterator<Object>() { + @Override public boolean hasNext() { + return it.hasNext(); + } + + @Override public Object next() { + return ((Feature) it.next()).getPropertyValue("sis:geometry"); + } + }); + geometry = getType().getValueClass().cast(geom); } return geometry; } @@ -178,7 +202,7 @@ class GroupAsPolylineOperation extends A * Does not allow modification of this attribute. */ @Override - public void setValue(Polyline value) { + public void setValue(G value) { throw new UnsupportedOperationException(Errors.format(Errors.Keys.UnmodifiableObject_1, Attribute.class)); } } Modified: sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Reader.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -25,7 +25,6 @@ import java.net.URISyntaxException; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.bind.JAXBException; -import com.esri.core.geometry.Point; import org.apache.sis.storage.gps.Fix; import org.apache.sis.storage.DataStoreException; import org.apache.sis.storage.DataStoreContentException; @@ -108,7 +107,7 @@ final class Reader extends StaxStreamRea * Returns {@code true} if the given namespace is a GPX namespace or is null. */ private static boolean isGPX(final String ns) { - return (ns == null) || ns.startsWith(Tags.NAMESPACE + "/GPX/"); + return (ns == null) || ns.startsWith(Tags.NAMESPACE); } /** @@ -408,9 +407,10 @@ parse: while (reader.hasNext()) { throw new DataStoreContentException(errors().getString(Errors.Keys.MandatoryAttribute_2, (lat == null) ? Attributes.LATITUDE : Attributes.LONGITUDE, tagName)); } - final Feature feature = ((Store) owner).types.wayPoint.newInstance(); + final Types types = ((Store) owner).types; + final Feature feature = types.wayPoint.newInstance(); feature.setPropertyValue("sis:identifier", index); - feature.setPropertyValue("sis:geometry", new Point(parseDouble(lon), parseDouble(lat))); + feature.setPropertyValue("sis:geometry", types.geometries.createPoint(parseDouble(lon), parseDouble(lat))); List<Link> links = null; while (true) { /* Modified: sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Store.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -17,6 +17,8 @@ package org.apache.sis.internal.storage.gpx; import java.net.URISyntaxException; +import org.opengis.util.NameFactory; +import org.opengis.util.FactoryException; import org.opengis.metadata.Metadata; import org.opengis.metadata.distribution.Format; import org.apache.sis.storage.StorageConnector; @@ -24,10 +26,14 @@ import org.apache.sis.storage.DataStoreE import org.apache.sis.storage.DataStoreContentException; import org.apache.sis.storage.ConcurrentReadException; import org.apache.sis.storage.IllegalNameException; +import org.apache.sis.internal.system.DefaultFactories; import org.apache.sis.internal.storage.xml.stream.StaxDataStore; import org.apache.sis.util.collection.BackingStoreException; import org.apache.sis.util.ArgumentChecks; import org.apache.sis.util.Version; +import org.apache.sis.setup.OptionKey; +import org.apache.sis.setup.GeometryLibrary; +import org.apache.sis.util.iso.DefaultNameFactory; import org.apache.sis.util.iso.SimpleInternationalString; import org.apache.sis.metadata.iso.citation.DefaultCitation; import org.apache.sis.metadata.iso.distribution.DefaultFormat; @@ -84,7 +90,14 @@ public final class Store extends StaxDat */ public Store(final StoreProvider provider, final StorageConnector connector) throws DataStoreException { super(provider, connector); - types = Types.DEFAULT; + final GeometryLibrary library = connector.getOption(OptionKey.GEOMETRY_LIBRARY); + if (library == null || Types.DEFAULT.geometries.library == library) { + types = Types.DEFAULT; + } else try { + types = new Types(DefaultFactories.forBuildin(NameFactory.class, DefaultNameFactory.class), null, library); + } catch (FactoryException e) { + throw new DataStoreException(e); + } } /** @@ -169,11 +182,12 @@ public final class Store extends StaxDat /** * Returns the stream of features. * + * @param parallel ignored in current implementation. * @return a stream over all features in the XML file. * @throws DataStoreException if an error occurred while creating the feature stream. */ @Override - public synchronized Stream<Feature> features() throws DataStoreException { + public synchronized Stream<Feature> features(final boolean parallel) throws DataStoreException { Reader r = reader; reader = null; if (r == null) try { Modified: sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Tags.java URL: http://svn.apache.org/viewvc/sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Tags.java?rev=1803070&r1=1803069&r2=1803070&view=diff ============================================================================== --- sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Tags.java [UTF-8] (original) +++ sis/branches/JDK9/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/storage/gpx/Tags.java [UTF-8] Wed Jul 26 16:14:09 2017 @@ -33,17 +33,22 @@ final class Tags extends Static { /** * GPX scope name used for feature type names. */ - static final String NAMESPACE = "http://www.topografix.com"; + static final String PREFIX = "gpx"; + + /** + * GPX XML namespace (common root to all versions). + */ + static final String NAMESPACE = "http://www.topografix.com/GPX/"; /** * GPX 1.0 XML namespace (v1.0). */ - static final String NAMESPACE_V10 = "http://www.topografix.com/GPX/1/0"; + static final String NAMESPACE_V10 = NAMESPACE + "1/0"; /** * GPX 1.1 XML namespace (v1.1). */ - static final String NAMESPACE_V11 = "http://www.topografix.com/GPX/1/1"; + static final String NAMESPACE_V11 = NAMESPACE + "1/1"; /** Main GPX XML tags. */ static final String GPX = "gpx"; /** A tag used a bit everywhere. */ static final String NAME = "name";