sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1775325 [1/2] - in /sis/branches/JDK8: core/sis-utility/src/main/java/org/apache/sis/internal/util/ ide-project/NetBeans/nbproject/ storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/ storage/sis-xmlstore/src/main/java/org/apac...
Date Tue, 20 Dec 2016 17:22:53 GMT
Author: desruisseaux
Date: Tue Dec 20 17:22:53 2016
New Revision: 1775325

URL: http://svn.apache.org/viewvc?rev=1775325&view=rev
Log:
Replaced sequence of "if ... else if" by switch statement.
Replace Iterator pattern by Spliterator.

Modified:
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java
    sis/branches/JDK8/ide-project/NetBeans/nbproject/genfiles.properties
    sis/branches/JDK8/ide-project/NetBeans/nbproject/project.xml
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXWriter100.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Link.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Types.java
    sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java
    sis/branches/JDK8/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/gpx/GPXReaderTest.java
    sis/branches/JDK8/storage/sis-xmlstore/src/test/java/org/apache/sis/internal/gpx/GPXWriterTest.java

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java [UTF-8] Tue Dec 20 17:22:53 2016
@@ -55,6 +55,7 @@ import static org.apache.sis.util.Argume
  *
  * @see Collections#checkedList(List, Class)
  */
+@SuppressWarnings("CloneableClassWithoutClone")         // ArrayList.clone() is sufficient.
 public final class CheckedArrayList<E> extends ArrayList<E> implements CheckedContainer<E> {
     /**
      * Serial version UID for compatibility with different versions.
@@ -69,7 +70,7 @@ public final class CheckedArrayList<E> e
     /**
      * Constructs a list of the specified type.
      *
-     * @param type The element type (can not be null).
+     * @param type  the element type (can not be null).
      */
     public CheckedArrayList(final Class<E> type) {
         super();
@@ -80,8 +81,8 @@ public final class CheckedArrayList<E> e
     /**
      * Constructs a list of the specified type and initial capacity.
      *
-     * @param type The element type (should not be null).
-     * @param capacity The initial capacity.
+     * @param type      the element type (should not be null).
+     * @param capacity  the initial capacity.
      */
     public CheckedArrayList(final Class<E> type, final int capacity) {
         super(capacity);
@@ -92,10 +93,10 @@ public final class CheckedArrayList<E> e
     /**
      * Returns the given collection as a {@code CheckedArrayList} instance of the given element type.
      *
-     * @param  <E>        The element type.
-     * @param  collection The collection or {@code null}.
-     * @param  type       The element type.
-     * @return The given collection as a {@code CheckedArrayList}, or {@code null} if the given collection was null.
+     * @param  <E>         the element type.
+     * @param  collection  the collection or {@code null}.
+     * @param  type        the element type.
+     * @return the given collection as a {@code CheckedArrayList}, or {@code null} if the given collection was null.
      * @throws ClassCastException if an element is not of the expected type.
      *
      * @since 0.5
@@ -109,7 +110,7 @@ public final class CheckedArrayList<E> e
             return (CheckedArrayList<E>) collection;
         } else {
             final CheckedArrayList<E> list = new CheckedArrayList<>(type, collection.size());
-            list.addAll((Collection) collection); // addAll will perform the type checks.
+            list.addAll((Collection) collection);               // addAll will perform the type checks.
             return list;
         }
     }
@@ -138,10 +139,10 @@ public final class CheckedArrayList<E> e
      *       stack trace depth, so the first element on the stack trace is the public {@code add(E)} method.</li>
      * </ul>
      *
-     * @param  collection   The collection in which the user attempted to add an invalid element.
-     * @param  element      The element that the user attempted to add (may be {@code null}).
-     * @param  expectedType The type of elements that the collection expected.
-     * @return The message to give to the exception to be thrown, or {@code null} if no message shall be thrown.
+     * @param  collection    the collection in which the user attempted to add an invalid element.
+     * @param  element       the element that the user attempted to add (may be {@code null}).
+     * @param  expectedType  the type of elements that the collection expected.
+     * @return the message to give to the exception to be thrown, or {@code null} if no message shall be thrown.
      *
      * @see <a href="https://issues.apache.org/jira/browse/SIS-139">SIS-139</a>
      * @see <a href="https://issues.apache.org/jira/browse/SIS-157">SIS-157</a>
@@ -168,10 +169,9 @@ public final class CheckedArrayList<E> e
     }
 
     /**
-     * Ensures that the given element is non-null and assignable to the type
-     * specified at construction time.
+     * Ensures that the given element is non-null and assignable to the type specified at construction time.
      *
-     * @param  element the object to check, or {@code null}.
+     * @param  element  the object to check, or {@code null}.
      * @return {@code true} if the instance is valid, {@code false} if it shall be ignored.
      * @throws NullPointerException if the given element is {@code null}.
      * @throws ClassCastException if the given element is not of the expected type.
@@ -199,8 +199,8 @@ public final class CheckedArrayList<E> e
     /**
      * Ensures that all elements of the given collection can be added to this list.
      *
-     * @param  collection the collection to check, or {@code null}.
-     * @return The potentially filtered collection of elements to add.
+     * @param  collection  the collection to check, or {@code null}.
+     * @return the potentially filtered collection of elements to add.
      * @throws NullPointerException if an element is {@code null}.
      * @throws ClassCastException if an element is not of the expected type.
      */
@@ -225,21 +225,22 @@ public final class CheckedArrayList<E> e
      * In particular {@link #toArray()} returns directly the internal array, because this is the method to be
      * invoked by {@code ArrayList.addAll(…)} (this is actually the only important method in this wrapper).
      *
-     * @param <E> The type or list elements.
+     * @param  <E>  the type or list elements.
      */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")
     private static final class Mediator<E> extends AbstractList<E> {
         private final E[] array;
         Mediator(final E[] array)           {this.array = array;}
         @Override public int size()         {return array.length;}
         @Override public E   get(int index) {return array[index];}
-        @Override public E[] toArray()      {return array;} // See class javadoc.
+        @Override public E[] toArray()      {return array;}                 // See class javadoc.
     }
 
     /**
      * Replaces the element at the specified position in this list with the specified element.
      *
-     * @param  index   index of element to replace.
-     * @param  element element to be stored at the specified position.
+     * @param  index    index of element to replace.
+     * @param  element  element to be stored at the specified position.
      * @return the element previously at the specified position.
      * @throws IndexOutOfBoundsException if index out of range.
      * @throws NullPointerException if the given element is {@code null}.
@@ -256,7 +257,7 @@ public final class CheckedArrayList<E> e
     /**
      * Appends the specified element to the end of this list.
      *
-     * @param  element element to be appended to this list.
+     * @param  element  element to be appended to this list.
      * @return always {@code true}.
      * @throws NullPointerException if the given element is {@code null}.
      * @throws ClassCastException if the given element is not of the expected type.
@@ -272,8 +273,8 @@ public final class CheckedArrayList<E> e
     /**
      * Inserts the specified element at the specified position in this list.
      *
-     * @param  index index at which the specified element is to be inserted.
-     * @param  element element to be inserted.
+     * @param  index  index at which the specified element is to be inserted.
+     * @param  element  element to be inserted.
      * @throws IndexOutOfBoundsException if index out of range.
      * @throws NullPointerException if the given element is {@code null}.
      * @throws ClassCastException if the given element is not of the expected type.
@@ -289,7 +290,7 @@ public final class CheckedArrayList<E> e
      * Appends all of the elements in the specified collection to the end of this list,
      * in the order that they are returned by the specified Collection's Iterator.
      *
-     * @param  collection the elements to be inserted into this list.
+     * @param  collection  the elements to be inserted into this list.
      * @return {@code true} if this list changed as a result of the call.
      * @throws NullPointerException if an element is {@code null}.
      * @throws ClassCastException if an element is not of the expected type.
@@ -303,8 +304,8 @@ public final class CheckedArrayList<E> e
      * Inserts all of the elements in the specified collection into this list,
      * starting at the specified position.
      *
-     * @param  index index at which to insert first element fromm the specified collection.
-     * @param  collection elements to be inserted into this list.
+     * @param  index  index at which to insert first element fromm the specified collection.
+     * @param  collection  elements to be inserted into this list.
      * @return {@code true} if this list changed as a result of the call.
      * @throws NullPointerException if an element is {@code null}.
      * @throws ClassCastException if an element is not of the expected type.
@@ -320,9 +321,9 @@ public final class CheckedArrayList<E> e
      * <p><b>Limitation:</b> current implementation checks only the type.
      * It does not prevent the insertion of {@code null} values.</p>
      *
-     * @param  fromIndex Index of the first element.
-     * @param  toIndex   Index after the last element.
-     * @return The sublist in the given index range.
+     * @param  fromIndex  index of the first element.
+     * @param  toIndex    index after the last element.
+     * @return the sublist in the given index range.
      */
     @Override
     public List<E> subList(final int fromIndex, final int toIndex) {

Modified: sis/branches/JDK8/ide-project/NetBeans/nbproject/genfiles.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/ide-project/NetBeans/nbproject/genfiles.properties?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/ide-project/NetBeans/nbproject/genfiles.properties [ISO-8859-1] (original)
+++ sis/branches/JDK8/ide-project/NetBeans/nbproject/genfiles.properties [ISO-8859-1] Tue Dec 20 17:22:53 2016
@@ -3,6 +3,6 @@
 build.xml.data.CRC32=58e6b21c
 build.xml.script.CRC32=462eaba0
 build.xml.stylesheet.CRC32=28e38971@1.53.1.46
-nbproject/build-impl.xml.data.CRC32=7573d217
+nbproject/build-impl.xml.data.CRC32=124371e8
 nbproject/build-impl.xml.script.CRC32=a1659f70
 nbproject/build-impl.xml.stylesheet.CRC32=830a3534@1.80.1.48

Modified: sis/branches/JDK8/ide-project/NetBeans/nbproject/project.xml
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/ide-project/NetBeans/nbproject/project.xml?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/ide-project/NetBeans/nbproject/project.xml (original)
+++ sis/branches/JDK8/ide-project/NetBeans/nbproject/project.xml Tue Dec 20 17:22:53 2016
@@ -92,6 +92,7 @@
             <word>namespaces</word>
             <word>orthodromic</word>
             <word>parsable</word>
+            <word>spliterator</word>
             <word>timezone</word>
             <word>Unicode</word>
             <word>uninstall</word>

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java [UTF-8] Tue Dec 20 17:22:53 2016
@@ -21,39 +21,42 @@ import java.util.ArrayList;
 import java.util.Objects;
 import java.io.IOException;
 import java.io.EOFException;
-import java.net.URI;
 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.StorageConnector;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.internal.xml.StaxStreamReader;
+import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Version;
 
 // Branch-dependent imports
-import org.opengis.feature.Feature;
+import java.util.function.Consumer;
 import java.time.format.DateTimeParseException;
+import org.opengis.feature.Feature;
 
 
 /**
  * Reader for GPX 1.0 and 1.1 files.
- * This reader is itself an iterator over all features found in the XML file.
+ * This reader is itself a spliterator over all features found in the XML file.
  * Usage:
  *
  * {@preformat java
- *     final Reader   reader   = new Reader(dataStore, connector);
- *     final Version  version  = reader.getVersion();
- *     final Metadata metadata = reader.getMetadata();
- *     while (reader.hasNext()) {
- *         Feature feature = reader.next();
+ *     Consumer<Feature> consumer = ...;
+ *     try (Reader reader = new Reader(dataStore, connector)) {
+ *         final Version  version  = reader.initialize(true);
+ *         final Metadata metadata = reader.getMetadata();
+ *         reader.forEachRemaining(consumer);
  *     }
  * }
  *
  * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
  * @since   0.8
  * @version 0.8
  * @module
@@ -71,29 +74,11 @@ public class GPXReader extends StaxStrea
     private String namespace;
 
     /**
-     * Version of the GPX file, or {@code null} if unspecified.
-     * Can be {@link GPXStore#V1_0} or {@link GPXStore#V1_1}.
-     */
-    private Version version;
-
-    /**
-     * Convenience flag set to {@code true} if the {@link #version} field is {@link GPXStore#V1_0},
-     * or {@code false} if the version is {@link GPXStore#V1_1}.
-     */
-    private boolean isLegacy;
-
-    /**
      * The metadata (ISO 19115 compatible), or {@code null} if none.
      */
     private Metadata metadata;
 
     /**
-     * The feature to be returned by {@link #next()}.
-     * This field is updated during iteration.
-     */
-    private Feature current;
-
-    /**
      * Identifier of the last "way point" feature instance created.
      * We use sequential numbers starting from 1.
      */
@@ -112,13 +97,8 @@ public class GPXReader extends StaxStrea
     private int trackId;
 
     /**
-     * {@code true} if we reached the end of {@code <gpx>} element.
-     */
-    private boolean finished;
-
-    /**
      * Creates a new GPX reader from the given file, URL, stream or reader object.
-     * The {@link #initialize()} method must be invoked after this constructor.
+     * The {@link #initialize(boolean)} method must be invoked after this constructor.
      *
      * @param  owner      the data store for which this reader is created.
      * @param  connector  information about the storage (URL, stream, <i>etc</i>).
@@ -133,32 +113,52 @@ public class GPXReader extends StaxStrea
     }
 
     /**
+     * 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/");
+    }
+
+    /**
+     * Returns {@code true} if the current position of the given reader is the closing {@code </gpx>} tag.
+     * The reader event should be {@link #END_DOCUMENT} before to invoke this method.
+     */
+    private boolean isEndGPX(final XMLStreamReader reader) {
+        return Tags.GPX.equals(reader.getLocalName()) && Objects.equals(namespace, reader.getNamespaceURI());
+    }
+
+    /**
      * Reads the metadata. This method should be invoked exactly once after construction.
      * This work is performed outside the constructor for allowing {@link #close()} method
-     * invocation no matter if this {@code initialize()} method fails.
+     * invocation no matter if this {@code initialize(boolean)} method fails.
      *
-     * @throws DataStoreContentException if the root element is not the expected one.
+     * @param  readMetadata  if {@code false}, skip the reading of metadata elements.
+     * @return the GPX file version, or {@code null} if no version information was found.
+     * @throws DataStoreException if the root element is not the expected one.
      * @throws XMLStreamException if an error occurred while reading the XML file.
-     * @throws URISyntaxException if an error occurred while parsing URI in GPX 1.0 metadata.
      * @throws JAXBException if an error occurred while parsing GPX 1.1 metadata.
      * @throws ClassCastException if an object unmarshalled by JAXB was not of the expected type.
+     * @throws URISyntaxException if an error occurred while parsing URI in GPX 1.0 metadata.
      * @throws DateTimeParseException if a text can not be parsed as a date.
      * @throws EOFException if the file seems to be truncated.
      */
-    public void initialize() throws DataStoreException, XMLStreamException, JAXBException, URISyntaxException, EOFException {
+    public Version initialize(final boolean readMetadata) throws DataStoreException,
+            XMLStreamException, JAXBException, URISyntaxException, EOFException
+    {
         /*
          * Skip comments, characters, entity declarations, etc. until we find the root element.
          * If that root is anything other than <gpx>, we consider that this is not a GPX file.
          */
-        moveToRootElement(GPXReader::isNamespaceGPX, Tags.GPX);
+        moveToRootElement(GPXReader::isGPX, Tags.GPX);
         /*
          * If a version attribute is found on the <gpx> element, use that value for detecting the GPX version.
-         * If the version is not found, we leave the field to null (we do not assume any version). If a version
-         * is specified, we require major.minor version 1.0 or 1.1 but accept any bug-fix versions (e.g. 1.1.x).
+         * If a version is specified, we require major.minor version 1.0 or 1.1 but accept any bug-fix versions
+         * (e.g. 1.1.x). If no version attribute was found, try to infer the version from the namespace URL.
          */
         final XMLStreamReader reader = getReader();
         namespace = reader.getNamespaceURI();
         String ver = reader.getAttributeValue(null, Attributes.VERSION);
+        Version version = null;
         if (ver != null) {
             version = new Version(ver);
             final int c = version.compareTo(GPXStore.V1_0, 2);
@@ -166,7 +166,11 @@ public class GPXReader extends StaxStrea
                 throw new DataStoreContentException(errors().getString(
                         Errors.Keys.UnsupportedFormatVersion_2, owner.getFormatName(), version));
             }
-            isLegacy = (c == 0);
+        } else if (namespace != null) {
+            switch (namespace) {
+                case Tags.NAMESPACE_V10: version = GPXStore.V1_0; break;
+                case Tags.NAMESPACE_V11: version = GPXStore.V1_1; break;
+            }
         }
         /*
          * Read metadata immediately, from current position until the beginning of way points, tracks or routes.
@@ -178,35 +182,46 @@ public class GPXReader extends StaxStrea
          *   - In GPX 1.1, they are declared in a <metadata> sub-element and their structure is a little bit
          *     more elaborated than what it was in the previous version. We will use JAXB for parsing them.
          */
-        while (reader.hasNext()) {
+parse:  while (reader.hasNext()) {
             switch (reader.next()) {
                 case START_ELEMENT: {
                     /*
                      * GPX 1.0 and 1.1 metadata should not be mixed. However the following code will work even
                      * if GPX 1.0 metadata like <name> or <author> appear after the GPX 1.1 <metadata> element.
                      */
-                    if (isNamespaceGPX(reader.getNamespaceURI())) {
-                        switch (reader.getLocalName()) {
-                            case Tags.GPX: {
-                                throw new DataStoreContentException(errors().getString(
-                                        Errors.Keys.NestedElementNotAllowed_1, Tags.GPX));
+                    if (isGPX(reader.getNamespaceURI())) {
+                        final String name = reader.getLocalName();
+                        if (readMetadata) {
+                            switch (name) {
+                                // GPX 1.1 metadata
+                                case Tags.METADATA:     metadata = unmarshal(Metadata.class); break;
+
+                                // GPX 1.0 metadata
+                                case Tags.NAME:         metadata().name        = getElementText();        break;
+                                case Tags.DESCRIPTION:  metadata().description = getElementText();        break;
+                                case Tags.AUTHOR:       author()  .name        = getElementText();        break;
+                                case Tags.EMAIL:        author()  .email       = getElementText();        break;
+                                case Tags.URL:          link()    .uri         = getElementAsURI();       break;
+                                case Tags.URL_NAME:     link()    .text        = getElementText();        break;
+                                case Tags.TIME:         metadata().time        = getElementAsDate();      break;
+                                case Tags.KEYWORDS:     metadata().keywords    = getElementAsList();      break;
+                                case Tags.BOUNDS:       metadata().bounds      = unmarshal(Bounds.class); break;
+                                case Tags.WAY_POINT:    // stop metadata parsing.
+                                case Tags.TRACKS:
+                                case Tags.ROUTES:       break parse;
+                                case Tags.GPX:          throw new DataStoreContentException(nestedElement(Tags.GPX));
+                            }
+                        } else {
+                            /*
+                             * If the caller asked to skip metadata, just look for the end of metadata elements.
+                             */
+                            switch (name) {
+                                case Tags.METADATA:  skipUntilEnd(reader.getName()); break;
+                                case Tags.WAY_POINT:
+                                case Tags.TRACKS:
+                                case Tags.ROUTES:    break parse;
+                                case Tags.GPX:       throw new DataStoreContentException(nestedElement(Tags.GPX));
                             }
-                            // GPX 1.1 metadata
-                            case Tags.METADATA:     metadata = unmarshal(Metadata.class); break;
-
-                            // GPX 1.0 metadata
-                            case Tags.NAME:         metadata().name        = getElementText();   break;
-                            case Tags.DESCRIPTION:  metadata().description = getElementText();   break;
-                            case Tags.AUTHOR:       author()  .name        = getElementText();   break;
-                            case Tags.EMAIL:        author()  .email       = getElementText();   break;
-                            case Tags.URL:          link()    .uri         = getElementAsURI();  break;
-                            case Tags.URL_NAME:     link()    .text        = getElementText();   break;
-                            case Tags.TIME:         metadata().time        = getElementAsDate(); break;
-                            case Tags.KEYWORDS:     metadata().keywords    = getElementAsList(); break;
-                            case Tags.BOUNDS:       metadata().bounds      = parseBound(reader); break;
-                            case Tags.WAY_POINT:    // stop metadata parsing.
-                            case Tags.TRACKS:
-                            case Tags.ROUTES:       return;
                         }
                     }
                     break;
@@ -218,28 +233,13 @@ public class GPXReader extends StaxStrea
                      * the enclosing <gpx> tag to check.
                      */
                     if (isEndGPX(reader)) {
-                        finished = true;
-                        return;
+                        break parse;
                     }
                     break;
                 }
             }
         }
-    }
-
-    /**
-     * Returns {@code true} if the given namespace is a GPX namespace or is null.
-     */
-    private static boolean isNamespaceGPX(final String ns) {
-        return (ns == null) || ns.startsWith(Tags.NAMESPACE + "/GPX/");
-    }
-
-    /**
-     * Returns {@code true} if the current position of the given reader is the closing {@code </gpx>} tag.
-     * The reader event should be {@link #END_DOCUMENT} before to invoke this method.
-     */
-    private boolean isEndGPX(final XMLStreamReader reader) {
-        return Tags.GPX.equals(reader.getLocalName()) && Objects.equals(namespace, reader.getNamespaceURI());
+        return version;
     }
 
     /**
@@ -282,401 +282,342 @@ public class GPXReader extends StaxStrea
     }
 
     /**
-     * Returns the GPX file version, or {@code null} if unknown. If a {@code version} attribute
-     * was found on the root {@code <gpx>} element, then that version is returned.
-     * Otherwise this method tries to infer the version from the namespace URL.
+     * Adds the given element to the given list if non null, or do nothing otherwise.
      *
-     * @return GPX file version, or {@code null} if unknown.
-     */
-    public Version getVersion() {
-        if (version == null && namespace != null) {
-            switch (namespace) {
-                case Tags.NAMESPACE_V10: return GPXStore.V1_0;
-                case Tags.NAMESPACE_V11: return GPXStore.V1_1;
+     * @param  links    the list where to add the element, or {@code null} if not yet created.
+     * @param  element  the element to add, or {@code null} if none.
+     * @return the list where the element has been added.
+     */
+    private static List<Link> addIfNonNull(List<Link> links, final Link element) {
+        if (element != null) {
+            if (links == null) {
+                links = new ArrayList<>(3);
             }
+            links.add(element);
         }
-        return version;
+        return links;
     }
 
     /**
      * Returns the metadata (ISO 19115 compatible), or {@code null} if none.
+     * This method can return a non-null value only if {@code initialize(true)}
+     * has been invoked before this method.
+     *
+     * @return the metadata, or {@code null} if none.
      *
-     * @return the metadata or {@code null} if none.
+     * @see #initialize(boolean)
      */
     public Metadata getMetadata() {
         return metadata;
     }
 
     /**
-     * Returns true if there is a next feature in the stream.
+     * Performs the given action on the next feature instance, or returns {@code null} if there is no more
+     * feature to parse.
      *
-     * @return true if there is next feature
-     * @throws XMLStreamException if xml parser encounter an invalid element
-     *         or underlying stream caused an exception
+     * @param  action  the action to perform on the next feature instances.
+     * @return {@code true} if a feature has been found, or {@code false} if we reached the end of GPX file.
+     * @throws BackingStoreException if an error occurred while parsing the next feature instance.
+     *         The cause may be {@link DataStoreException}, {@link IOException}, {@link URISyntaxException}
+     *         or various {@link RuntimeException}.
      */
-    public boolean hasNext() throws XMLStreamException {
-        if (finished) return false;
-        findNext();
-        return current != null;
+    @Override
+    public boolean tryAdvance(final Consumer<? super Feature> action) throws BackingStoreException {
+        try {
+            return parse(action, false);
+        } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
+            throw new BackingStoreException(canNotReadFile(), e);
+        }
     }
 
     /**
-     * Get next feature in the stream.
+     * Performs the given action for each remaining element until all elements have been processed.
      *
-     * @return GPX WayPoint, Route or track
-     * @throws XMLStreamException if xml parser encounter an invalid element
-     *         or underlying stream caused an exception
-     */
-    public Feature next() throws XMLStreamException {
-        findNext();
-        final Feature ele = current;
-        current = null;
-        return ele;
-    }
-
-    /**
-     * Search for the next feature in the stax stream.
-     * This method will set the current local property if there is one.
+     * @param  action  the action to perform on the remaining feature instances.
+     * @throws BackingStoreException if an error occurred while parsing the next feature instance.
+     *         The cause may be {@link DataStoreException}, {@link IOException}, {@link URISyntaxException}
+     *         or various {@link RuntimeException}.
      */
-    private void findNext() throws XMLStreamException {
-        final XMLStreamReader reader = getReader();
-        boolean first = true;
-        while ( first || (current == null && reader.hasNext()) ) {
-            final int type;
-            if (first) {
-                type = reader.getEventType();
-                first = false;
-            } else {
-                type = reader.next();
-            }
-            if (type == START_ELEMENT) {
-                final String localName = reader.getLocalName();
-                if (Tags.WAY_POINT.equalsIgnoreCase(localName)) {
-                    current = parseWayPoint(++wayPointId);
-                    break;
-                } else if (Tags.ROUTES.equalsIgnoreCase(localName)) {
-                    current = parseRoute(++routeId);
-                    break;
-                } else if (Tags.TRACKS.equalsIgnoreCase(localName)) {
-                    current = parseTrack(++trackId);
-                    break;
-                }
-            }
+    @Override
+    public void forEachRemaining(final Consumer<? super Feature> action) throws BackingStoreException {
+        try {
+            parse(action, true);
+        } catch (Exception e) {                 // Many possible exceptions including unchecked ones.
+            throw new BackingStoreException(canNotReadFile(), e);
         }
     }
 
     /**
-     * Parse current URI element.
-     * The stax reader must be placed to the start element.
+     * Implementation of {@link #tryAdvance(Consumer)} and {@link #forEachRemaining(Consumer)}.
+     *
+     * @param  action  the action to perform on the remaining feature instances.
+     * @param  all     whether to perform the action on all remaining instances or only the next one.
+     * @return {@code false} if this method as detected the end of {@code <gpx>} element or the end of document.
+     * @throws DataStoreException if the file contains invalid elements.
+     * @throws XMLStreamException if an error occurred while reading the XML file.
+     * @throws URISyntaxException if an error occurred while parsing GPX 1.0 URI.
+     * @throws JAXBException if an error occurred while parsing GPX 1.1 link.
+     * @throws ClassCastException if an object unmarshalled by JAXB was not of the expected type.
+     * @throws NumberFormatException if a text can not be parsed as an integer or a floating point number.
+     * @throws DateTimeParseException if a text can not be parsed as a date.
+     * @throws EOFException if the file seems to be truncated.
      */
-    private Link parseLink() throws XMLStreamException {
+    @SuppressWarnings("fallthrough")
+    private boolean parse(final Consumer<? super Feature> action, final boolean all)
+            throws DataStoreException, XMLStreamException, JAXBException, URISyntaxException, EOFException
+    {
         final XMLStreamReader reader = getReader();
-        String text = reader.getAttributeValue(null, Attributes.HREF);
-        String mime = null;
-
-        while (reader.hasNext()) {
-            switch (reader.next()) {
+        for (int type = reader.getEventType(); ; type = reader.next()) {
+            /*
+             * We do not need to check 'reader.hasNext()' in above loop
+             * since this check is done by the END_DOCUMENT case below.
+             */
+            switch (type) {
                 case START_ELEMENT: {
-                    final String localName = reader.getLocalName();
-                    if (Tags.TEXT.equalsIgnoreCase(localName) && text==null) {
-                        text = reader.getElementText();
-                    } else if (Tags.TYPE.equalsIgnoreCase(localName)) {
-                        mime = reader.getElementText();
-                    }
-                    break;
-                }
-                case END_ELEMENT: {
-                    if (Tags.LINK.equalsIgnoreCase(reader.getLocalName())) {
-                        try {
-                            // End of the link element
-                            return new Link(new URI(text));
-                        } catch (URISyntaxException ex) {
-                            throw new XMLStreamException(ex);
-                        }
-                    }
-                    break;
+                    final Feature f;
+                    switch (isGPX(reader.getNamespaceURI()) ? reader.getLocalName() : "") {
+                        case Tags.WAY_POINT: f = parseWayPoint(reader, ++wayPointId); break;
+                        case Tags.ROUTES:    f = parseRoute   (reader, ++routeId);    break;
+                        case Tags.TRACKS:    f = parseTrack   (reader, ++trackId);    break;
+                        case Tags.GPX:       throw new DataStoreContentException(nestedElement(Tags.GPX));
+                        default:             skipUntilEnd(reader.getName()); continue;
+                    }
+                    action.accept(f);
+                    if (all) continue;
+                    reader.next();                                      // Skip the END_ELEMENT
+                    return true;
                 }
+                case END_ELEMENT:  if (!isEndGPX(reader)) continue;     // else fallthrough
+                case END_DOCUMENT: return false;
             }
         }
-        throw new XMLStreamException("Error in xml file, link tag without end.");
     }
 
     /**
-     * Parse current Envelope element.
-     * The stax reader must be placed to the start element.
+     * Parses a {@code <wpt>}, {@code <rtept>} or {@code <trkpt>} element.
+     * The STAX reader {@linkplain XMLStreamReader#getEventType() current event} must be a {@link #START_ELEMENT}.
      */
-    private Bounds parseBound(final XMLStreamReader reader) throws XMLStreamException, EOFException {
-        final String xmin = reader.getAttributeValue(null, Attributes.MIN_X);
-        final String xmax = reader.getAttributeValue(null, Attributes.MAX_X);
-        final String ymin = reader.getAttributeValue(null, Attributes.MIN_Y);
-        final String ymax = reader.getAttributeValue(null, Attributes.MAX_Y);
-
-        if (xmin == null || xmax == null || ymin == null || ymax == null) {
-            throw new XMLStreamException("Error in xml file, metadata bounds not defined correctly");
-        }
-
-        skipUntilEnd(reader.getName());
-        final Bounds bounds = new Bounds();
-        bounds.westBoundLongitude = Double.parseDouble(xmin);
-        bounds.eastBoundLongitude = Double.parseDouble(xmax);
-        bounds.southBoundLatitude = Double.parseDouble(ymin);
-        bounds.northBoundLatitude = Double.parseDouble(ymax);
-        return bounds;
-    }
-
-    /**
-     * Parse way point type feature element.
-     * The stax reader must be placed to the start element.
-     */
-    private Feature parseWayPoint(final int index) throws XMLStreamException {
-        final XMLStreamReader reader = getReader();
-        final Feature feature = types.wayPoint.newInstance();
-        feature.setPropertyValue("@identifier", index);
-
-        //way points might be located in different tag names : wpt, rtept and trkpt
-        //we kind the current tag name to know when we reach the end.
+    private Feature parseWayPoint(final XMLStreamReader reader, final int index)
+            throws DataStoreException, XMLStreamException, JAXBException, URISyntaxException, EOFException
+    {
+        /*
+         * Way points might be located in different tag elements: <wpt>, <rtept> and <trkpt>.
+         * We have to keep the current tag name in order to know when we reached the end.
+         * We are lenient about namespace since we do not allow nested way points.
+         */
         final String tagName = reader.getLocalName();
-
-        List<Link> links = null;
-
         final String lat = reader.getAttributeValue(null, Attributes.LATITUDE);
         final String lon = reader.getAttributeValue(null, Attributes.LONGITUDE);
-
         if (lat == null || lon == null) {
-            throw new XMLStreamException("Error in xml file, way point lat/lon not defined correctly");
-        } else{
-            feature.setPropertyValue("@geometry", new Point(Double.parseDouble(lon), Double.parseDouble(lat)));
+            throw new XMLStreamException(errors().getString(Errors.Keys.MandatoryAttribute_2,
+                    (lat == null) ? Attributes.LATITUDE : Attributes.LONGITUDE, tagName));
         }
-
-        while (reader.hasNext()) {
+        final Feature feature = types.wayPoint.newInstance();
+        feature.setPropertyValue("@identifier", index);
+        feature.setPropertyValue("@geometry", new Point(Double.parseDouble(lon), Double.parseDouble(lat)));
+        List<Link> links = null;
+        while (true) {
+            /*
+             * We do not need to check 'reader.hasNext()' in above loop
+             * since this check is done by the END_DOCUMENT case below.
+             */
             switch (reader.next()) {
                 case START_ELEMENT: {
-                    final String localName = reader.getLocalName();
-                    if (Tags.ELEVATION.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.ELEVATION, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.TIME.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.TIME, getElementAsTemporal());
-                    } else if (Tags.MAGNETIC_VAR.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.MAGNETIC_VAR, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.GEOID_HEIGHT.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.GEOID_HEIGHT, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.NAME.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.NAME, reader.getElementText());
-                    } else if (Tags.COMMENT.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.COMMENT,reader.getElementText());
-                    } else if (Tags.DESCRIPTION.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.DESCRIPTION, reader.getElementText());
-                    } else if (Tags.SOURCE.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.SOURCE, reader.getElementText());
-                    } else if (Tags.LINK.equalsIgnoreCase(localName)) {
-                        if (links == null) links = new ArrayList<>();
-                        links.add(parseLink());
-                    } else if (Tags.SYMBOL.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.SYMBOL, reader.getElementText());
-                    } else if (Tags.TYPE.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.TYPE, reader.getElementText());
-                    } else if (Tags.FIX.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.FIX, reader.getElementText());
-                    } else if (Tags.SATELITTES.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.SATELITTES, Integer.valueOf(reader.getElementText()));
-                    } else if (Tags.HDOP.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.HDOP, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.PDOP.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.PDOP, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.VDOP.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.VDOP, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.AGE_OF_GPS_DATA.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.AGE_OF_GPS_DATA, Double.valueOf(reader.getElementText()));
-                    } else if (Tags.DGPS_ID.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.DGPS_ID, Integer.valueOf(reader.getElementText()));
-                    } else if (isLegacy && Tags.URL.equalsIgnoreCase(localName)) {
-                        // GPX 1.0 only
-                        if (links == null) links = new ArrayList<>();
-                        try {
-                            links.add(new Link(new URI(reader.getElementText())));
-                        } catch (URISyntaxException ex) {
-                            throw new XMLStreamException(ex);
+                    final Object value;
+                    final String name = reader.getLocalName();
+                    switch (isGPX(reader.getNamespaceURI()) ? name : "") {
+                        case Tags.NAME:             // Fallthrough to getElementText()
+                        case Tags.COMMENT:          // ︙
+                        case Tags.DESCRIPTION:      // ︙
+                        case Tags.SOURCE:           // ︙
+                        case Tags.SYMBOL:           // ︙
+                        case Tags.TYPE:             value = getElementText(); break;
+                        case Tags.TIME:             value = getElementAsTemporal(); break;
+                        case Tags.MAGNETIC_VAR:     // Fallthrough to getElementAsDouble()
+                        case Tags.GEOID_HEIGHT:     // ︙
+                        case Tags.AGE_OF_GPS_DATA:  // ︙
+                        case Tags.HDOP:             // ︙
+                        case Tags.PDOP:             // ︙
+                        case Tags.VDOP:             // ︙
+                        case Tags.ELEVATION:        value = getElementAsDouble(); break;
+                        case Tags.SATELITTES:       // Fallthrough to getElementAsInteger()
+                        case Tags.DGPS_ID:          value = getElementAsInteger(); break;
+                        case Tags.FIX:              value = Fix.fromGPX(getElementText()); break;
+                        case Tags.LINK:             links = addIfNonNull(links, unmarshal(Link.class)); continue;
+                        case Tags.URL:              links = addIfNonNull(links, Link.valueOf(getElementAsURI())); continue;
+                        default: {
+                            if (name.equals(tagName)) {
+                                throw new DataStoreContentException(nestedElement(name));
+                            }
+                            continue;
                         }
                     }
+                    feature.setPropertyValue(name, value);
                     break;
                 }
                 case END_ELEMENT: {
-                    if (tagName.equalsIgnoreCase(reader.getLocalName())) {
-                        // End of the way point element
-                        if (links!=null) feature.setPropertyValue(Tags.LINK, links);
+                    if (tagName.equals(reader.getLocalName()) && isGPX(reader.getNamespaceURI())) {
+                        if (links != null) feature.setPropertyValue(Tags.LINK, links);
                         return feature;
                     }
                     break;
                 }
+                case END_DOCUMENT: {
+                    throw new EOFException(endOfFile());
+                }
             }
         }
-        throw new XMLStreamException("Error in xml file, "+tagName+" tag without end.");
     }
 
     /**
-     * Parse route type feature element.
-     * The stax reader must be placed to the start element.
+     * Parses a {@code <rte>} element. The STAX reader {@linkplain XMLStreamReader#getEventType() current event}
+     * must be a {@link #START_ELEMENT} and the name of that start element must be {@link Tags#ROUTES}.
      */
-    private Feature parseRoute(final int index) throws XMLStreamException {
-        final XMLStreamReader reader = getReader();
+    private Feature parseRoute(final XMLStreamReader reader, final int index)
+            throws DataStoreException, XMLStreamException, JAXBException, URISyntaxException, EOFException
+    {
         final Feature feature = types.route.newInstance();
         feature.setPropertyValue("@identifier", index);
-
-        int ptInc = 0;
-        List<Link> links = null;
         List<Feature> wayPoints = null;
-
-        while (reader.hasNext()) {
+        List<Link> links = null;
+        while (true) {
+            /*
+             * We do not need to check 'reader.hasNext()' in above loop
+             * since this check is done by the END_DOCUMENT case below.
+             */
             switch (reader.next()) {
                 case START_ELEMENT: {
-                    final String localName = reader.getLocalName();
-                    if (Tags.ROUTE_POINTS.equalsIgnoreCase(localName)) {
-                        if (wayPoints == null) wayPoints = new ArrayList<>();
-                        wayPoints.add(parseWayPoint(++ptInc));
-                    } else if (Tags.NAME.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.NAME, reader.getElementText());
-                    } else if (Tags.COMMENT.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.COMMENT, reader.getElementText());
-                    } else if (Tags.DESCRIPTION.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.DESCRIPTION, reader.getElementText());
-                    } else if (Tags.SOURCE.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.SOURCE, reader.getElementText());
-                    } else if (Tags.LINK.equalsIgnoreCase(localName)) {
-                        if (links == null) links = new ArrayList<>();
-                        links.add(parseLink());
-                    } else if (Tags.NUMBER.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.NUMBER, Integer.valueOf(reader.getElementText()));
-                    } else if (Tags.TYPE.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.TYPE, reader.getElementText());
-                    } else if (isLegacy && Tags.URL.equalsIgnoreCase(localName)) {
-                        //GPX 1.0 only
-                        if (links == null) links = new ArrayList<>();
-                        try {
-                            links.add(new Link(new URI(reader.getElementText())));
-                        } catch (URISyntaxException ex) {
-                            throw new XMLStreamException(ex);
+                    final Object value;
+                    final String name = reader.getLocalName();
+                    switch (isGPX(reader.getNamespaceURI()) ? name : "") {
+                        default: continue;
+                        case Tags.NAME:        // Fallthrough to getElementText()
+                        case Tags.COMMENT:     // ︙
+                        case Tags.DESCRIPTION: // ︙
+                        case Tags.SOURCE:      // ︙
+                        case Tags.TYPE:        value = getElementText(); break;
+                        case Tags.NUMBER:      value = getElementAsInteger(); break;
+                        case Tags.LINK:        links = addIfNonNull(links, unmarshal(Link.class)); continue;
+                        case Tags.URL:         links = addIfNonNull(links, Link.valueOf(getElementAsURI())); continue;
+                        case Tags.ROUTES:      throw new DataStoreContentException(nestedElement(name));
+                        case Tags.ROUTE_POINTS: {
+                            if (wayPoints == null) wayPoints = new ArrayList<>(8);
+                            wayPoints.add(parseWayPoint(reader, wayPoints.size() + 1));
+                            continue;
                         }
                     }
+                    feature.setPropertyValue(name, value);
                     break;
                 }
                 case END_ELEMENT: {
-                    if (Tags.ROUTES.equalsIgnoreCase(reader.getLocalName())) {
-                        // End of the route element
-                        if (links!=null) feature.setPropertyValue(Tags.LINK, links);
-                        if (wayPoints!=null) feature.setPropertyValue(Tags.ROUTE_POINTS, wayPoints);
+                    if (Tags.ROUTES.equals(reader.getLocalName()) && isGPX(reader.getNamespaceURI())) {
+                        if (wayPoints != null) feature.setPropertyValue(Tags.ROUTE_POINTS, wayPoints);
+                        if (links     != null) feature.setPropertyValue(Tags.LINK, links);
                         return feature;
                     }
                     break;
                 }
+                case END_DOCUMENT: {
+                    throw new EOFException(endOfFile());
+                }
             }
         }
-        throw new XMLStreamException("Error in xml file, "+Tags.ROUTES+" tag without end.");
     }
 
     /**
-     * Parse track segment type feature element.
-     * The stax reader must be placed to the start element.
+     * Parses a {@code <trkseg>} element. The STAX reader {@linkplain XMLStreamReader#getEventType() current event}
+     * must be a {@link #START_ELEMENT} and the name of that start element must be {@link Tags#TRACK_SEGMENTS}.
      */
-    private Feature parseTrackSegment(final int index) throws XMLStreamException {
-        final XMLStreamReader reader = getReader();
+    private Feature parseTrackSegment(final XMLStreamReader reader, final int index)
+            throws DataStoreException, XMLStreamException, JAXBException, URISyntaxException, EOFException
+    {
         final Feature feature = types.trackSegment.newInstance();
         feature.setPropertyValue("@identifier", index);
-        int ptInc = 0;
         List<Feature> wayPoints = null;
-
-        while (reader.hasNext()) {
+        while (true) {
+            /*
+             * We do not need to check 'reader.hasNext()' in above loop
+             * since this check is done by the END_DOCUMENT case below.
+             */
             switch (reader.next()) {
                 case START_ELEMENT: {
-                    final String localName = reader.getLocalName();
-                    if (Tags.TRACK_POINTS.equalsIgnoreCase(localName)) {
-                        if (wayPoints == null) wayPoints = new ArrayList<>();
-                        wayPoints.add(parseWayPoint(++ptInc));
+                    final String name = reader.getLocalName();
+                    switch (isGPX(reader.getNamespaceURI()) ? name : "") {
+                        default: continue;
+                        case Tags.TRACK_POINTS: {
+                            if (wayPoints == null) wayPoints = new ArrayList<>(8);
+                            wayPoints.add(parseWayPoint(reader, wayPoints.size() + 1));
+                            continue;
+                        }
+                        case Tags.TRACK_SEGMENTS: throw new DataStoreContentException(nestedElement(name));
                     }
-                    break;
                 }
                 case END_ELEMENT: {
-                    if (Tags.TRACK_SEGMENTS.equalsIgnoreCase(reader.getLocalName())) {
-                        // End of the track segment element
-                        if (wayPoints!=null) feature.setPropertyValue(Tags.TRACK_POINTS, wayPoints);
+                    if (Tags.TRACK_SEGMENTS.equals(reader.getLocalName()) && isGPX(reader.getNamespaceURI())) {
+                        if (wayPoints != null) feature.setPropertyValue(Tags.TRACK_POINTS, wayPoints);
                         return feature;
                     }
                     break;
                 }
+                case END_DOCUMENT: {
+                    throw new EOFException(endOfFile());
+                }
             }
         }
-        throw new XMLStreamException("Error in xml file, "+Tags.TRACK_SEGMENTS+" tag without end.");
     }
 
     /**
-     * Parse track type feature element.
-     * The stax reader must be placed to the start element.
+     * Parses a {@code <trk>} element. The STAX reader {@linkplain XMLStreamReader#getEventType() current event}
+     * must be a {@link #START_ELEMENT} and the name of that start element must be {@link Tags#TRACKS}.
      */
-    private Feature parseTrack(final int index) throws XMLStreamException {
-        final XMLStreamReader reader = getReader();
+    private Feature parseTrack(final XMLStreamReader reader, final int index)
+            throws DataStoreException, XMLStreamException, JAXBException, URISyntaxException, EOFException
+    {
         final Feature feature = types.track.newInstance();
         feature.setPropertyValue("@identifier", index);
-        int segInc = 0;
-        List<Link> links = null;
         List<Feature> segments = null;
-
-        while (reader.hasNext()) {
+        List<Link> links = null;
+        while (true) {
+            /*
+             * We do not need to check 'reader.hasNext()' in above loop
+             * since this check is done by the END_DOCUMENT case below.
+             */
             switch (reader.next()) {
                 case START_ELEMENT: {
-                    final String localName = reader.getLocalName();
-                    if (Tags.TRACK_SEGMENTS.equalsIgnoreCase(localName)) {
-                        if (segments == null) segments = new ArrayList<>();
-                        segments.add(parseTrackSegment(segInc++));
-                    } else if (Tags.NAME.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.NAME, reader.getElementText());
-                    } else if (Tags.COMMENT.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.COMMENT, reader.getElementText());
-                    } else if (Tags.DESCRIPTION.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.DESCRIPTION, reader.getElementText());
-                    } else if (Tags.SOURCE.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.SOURCE, reader.getElementText());
-                    } else if (Tags.LINK.equalsIgnoreCase(localName)) {
-                        if (links == null) links = new ArrayList<>();
-                        links.add(parseLink());
-                    } else if (Tags.NUMBER.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.NUMBER, Integer.valueOf(reader.getElementText()));
-                    } else if (Tags.TYPE.equalsIgnoreCase(localName)) {
-                        feature.setPropertyValue(Tags.TYPE, reader.getElementText());
-                    } else if (isLegacy && Tags.URL.equalsIgnoreCase(localName)) {
-                        // GPX 1.0 only
-                        if (links == null) links = new ArrayList<>();
-                        try {
-                            links.add(new Link(new URI(reader.getElementText())));
-                        } catch (URISyntaxException ex) {
-                            throw new XMLStreamException(ex);
+                    final Object value;
+                    final String name = reader.getLocalName();
+                    switch (isGPX(reader.getNamespaceURI()) ? name : "") {
+                        default: continue;
+                        case Tags.NAME:         // Fallthrough to getElementText()
+                        case Tags.COMMENT:      // ︙
+                        case Tags.DESCRIPTION:  // ︙
+                        case Tags.SOURCE:       // ︙
+                        case Tags.TYPE:         value = getElementText(); break;
+                        case Tags.NUMBER:       value = getElementAsInteger(); break;
+                        case Tags.LINK:         links = addIfNonNull(links, unmarshal(Link.class)); continue;
+                        case Tags.URL:          links = addIfNonNull(links, Link.valueOf(getElementAsURI())); continue;
+                        case Tags.TRACKS:       throw new DataStoreContentException(nestedElement(name));
+                        case Tags.TRACK_SEGMENTS: {
+                            if (segments == null) segments = new ArrayList<>(8);
+                            segments.add(parseTrackSegment(reader, segments.size() + 1));
+                            continue;
                         }
                     }
+                    feature.setPropertyValue(name, value);
                     break;
                 }
                 case END_ELEMENT: {
-                    if (Tags.TRACKS.equalsIgnoreCase(reader.getLocalName())) {
-                        // End of the track element
-                        if (links!=null) feature.setPropertyValue(Tags.LINK, links);
-                        if (segments!=null) feature.setPropertyValue(Tags.TRACK_SEGMENTS, segments);
+                    if (Tags.TRACKS.equalsIgnoreCase(reader.getLocalName()) && isGPX(reader.getNamespaceURI())) {
+                        if (segments != null) feature.setPropertyValue(Tags.TRACK_SEGMENTS, segments);
+                        if (links    != null) feature.setPropertyValue(Tags.LINK, links);
                         return feature;
                     }
                     break;
                 }
+                case END_DOCUMENT: {
+                    throw new EOFException(endOfFile());
+                }
             }
         }
-        throw new XMLStreamException("Error in xml file, "+Tags.TRACKS+" tag without end.");
-    }
-
-    /**
-     * Closes the input stream and releases any resources used by this XML reader.
-     * This reader can not be used anymore after this method has been invoked.
-     *
-     * @throws IOException if an error occurred while closing the input stream.
-     * @throws XMLStreamException if an error occurred while releasing XML reader/writer resources.
-     */
-    @Override
-    public void close() throws IOException, XMLStreamException {
-        super.close();
-        current  = null;
-        finished = true;
     }
 }

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXWriter100.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXWriter100.java?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXWriter100.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXWriter100.java [UTF-8] Tue Dec 20 17:22:53 2016
@@ -32,9 +32,10 @@ import java.util.stream.Collectors;
 import javax.xml.stream.XMLStreamException;
 import javax.xml.stream.XMLStreamWriter;
 import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.apache.sis.internal.xml.StaxStreamWriter;
+import org.apache.sis.storage.gps.Fix;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.util.collection.BackingStoreException;
+import org.apache.sis.internal.xml.StaxStreamWriter;
 
 import static org.apache.sis.util.ArgumentChecks.*;
 
@@ -269,7 +270,7 @@ public class GPXWriter100 extends StaxSt
         writeLinkURIs((Collection<Link>)    feature.getPropertyValue(Tags.LINK));
         writeProperty(Tags.SYMBOL,          feature.getProperty(Tags.SYMBOL));
         writeProperty(Tags.TYPE,            feature.getProperty(Tags.TYPE));
-        writeProperty(Tags.FIX,             feature.getProperty(Tags.FIX));
+        writeFix((Fix)                      feature.getPropertyValue(Tags.FIX));
         writeProperty(Tags.SATELITTES,      feature.getProperty(Tags.SATELITTES));
         writeProperty(Tags.HDOP,            feature.getProperty(Tags.HDOP));
         writeProperty(Tags.VDOP,            feature.getProperty(Tags.VDOP));
@@ -406,7 +407,7 @@ public class GPXWriter100 extends StaxSt
      * @param prop can be null
      * @throws XMLStreamException if underlying xml stax writer encounter an error
      */
-    protected void writeProperty(final String tagName,final Property prop) throws XMLStreamException {
+    protected void writeProperty(final String tagName, final Property prop) throws XMLStreamException {
         if (prop == null) return;
 
         Object val = prop.getValue();
@@ -417,6 +418,10 @@ public class GPXWriter100 extends StaxSt
         writeSimpleTag(namespace, tagName, val);
     }
 
+    private void writeFix(final Fix fix) throws XMLStreamException {
+        writeSimpleTag(namespace, Tags.FIX, (fix != null) ? fix.toGPX() : null);
+    }
+
     /**
      * Convert temporal object to it's most appropriate ISO-8601 string representation.
      *

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Link.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Link.java?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Link.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Link.java [UTF-8] Tue Dec 20 17:22:53 2016
@@ -88,6 +88,16 @@ public final class Link implements Onlin
     }
 
     /**
+     * Creates a new instance initialized to the given URI.
+     *
+     * @param  uri  the URI, or {@code null}.
+     * @return the link, or {@code null} if the given URI was null.
+     */
+    static Link valueOf(final URI uri) {
+        return (uri != null) ? new Link(uri) : null;
+    }
+
+    /**
      * Invoked by JAXB after unmarshalling. If the {@linkplain #uri} is not set but the {@link #text} looks
      * like a URI, uses that text. The intend is to handle link that should have been defined like below:
      *

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Types.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Types.java?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Types.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Types.java [UTF-8] Tue Dec 20 17:22:53 2016
@@ -25,6 +25,7 @@ import org.opengis.util.LocalName;
 import org.opengis.util.NameFactory;
 import org.opengis.util.FactoryException;
 import org.opengis.metadata.citation.OnlineResource;
+import org.apache.sis.storage.gps.Fix;
 import org.apache.sis.referencing.CommonCRS;
 import org.apache.sis.feature.AbstractIdentifiedType;
 import org.apache.sis.feature.FeatureOperations;
@@ -129,7 +130,7 @@ final class Types extends Static {
          * │ link          │ OnlineResource │ gpx:linkType           │   [0 … ∞]   │
          * │ sym           │ String         │ xsd:string             │   [0 … 1]   │
          * │ type          │ String         │ xsd:string             │   [0 … 1]   │
-         * │ fix           │ String         │ gpx:fixType            │   [0 … 1]   │
+         * │ fix           │ Fix            │ gpx:fixType            │   [0 … 1]   │
          * │ sat           │ Integer        │ xsd:nonNegativeInteger │   [0 … 1]   │
          * │ hdop          │ Double         │ xsd:decimal            │   [0 … 1]   │
          * │ vdop          │ Double         │ xsd:decimal            │   [0 … 1]   │
@@ -155,7 +156,7 @@ final class Types extends Static {
         builder.addAttribute(OnlineResource.class).setName(Tags.LINK).setMaximumOccurs(Integer.MAX_VALUE);
         builder.addAttribute(String        .class).setName(Tags.SYMBOL);
         builder.addAttribute(String        .class).setName(Tags.TYPE);
-        builder.addAttribute(String        .class).setName(Tags.FIX);
+        builder.addAttribute(Fix           .class).setName(Tags.FIX);
         builder.addAttribute(Integer       .class).setName(Tags.SATELITTES);
         builder.addAttribute(Double        .class).setName(Tags.HDOP);
         builder.addAttribute(Double        .class).setName(Tags.VDOP);

Modified: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java?rev=1775325&r1=1775324&r2=1775325&view=diff
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java [UTF-8] (original)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java [UTF-8] Tue Dec 20 17:22:53 2016
@@ -43,40 +43,60 @@ import org.xml.sax.InputSource;
 import org.w3c.dom.Node;
 import org.apache.sis.xml.XML;
 import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.storage.DataStoreException;
 import org.apache.sis.storage.StorageConnector;
 import org.apache.sis.storage.DataStoreContentException;
 import org.apache.sis.storage.UnsupportedStorageException;
+import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.Classes;
 
 // Branch-dependent imports
+import java.util.Spliterator;
+import java.util.function.Consumer;
 import java.time.temporal.Temporal;
 import java.time.format.DateTimeParseException;
+import org.opengis.feature.Feature;
 
 
 /**
  * Base class of Apache SIS readers of XML files using STAX parser.
+ * This class is itself an spliterator over all {@code Feature} instances found in the XML file,
+ * with the following restrictions:
+ *
+ * <ul>
+ *   <li>{@link #tryAdvance(Consumer)} shall returns the features in the order they are declared in the XML file.</li>
+ *   <li>{@code tryAdvance(Consumer)} shall not return {@code null} value.</li>
+ *   <li>Modifications of the XML file are not allowed while an iteration is in progress.</li>
+ *   <li>A {@code StaxStreamReader} instance can iterate over the features only once;
+ *       if a new iteration is wanted, a new {@code StaxStreamReader} instance must be created.</li>
+ * </ul>
+ *
  * This is a helper class for {@link org.apache.sis.storage.DataStore} implementations.
- * Readers for a given specification should extend this class and provide appropriate read methods.
+ * Readers for a given specification should extend this class and provide the following methods:
  *
  * <p>Example:</p>
  * {@preformat java
  *     public class UserObjectReader extends StaxStreamReader {
- *         public UserObject read() throws XMLStreamException {
- *             // Actual STAX read operations.
- *             return userObject;
+ *         public boolean tryAdvance(Consumer<? super Feature> action) throws BackingStoreException {
+ *             if (endOfFile) {
+ *                 return false;
+ *             }
+ *             Feature f = ...;         // Actual STAX read operations.
+ *             action.accept(f);
+ *             return true;
  *         }
  *     }
  * }
  *
- * And should be used like below:
+ * And can be used like below:
  *
  * {@preformat java
- *     UserObject obj;
+ *     Consumer<Feature> consumer = ...;
  *     try (UserObjectReader reader = new UserObjectReader(input)) {
- *         obj = instance.read();
+ *         reader.forEachRemaining(consumer);
  *     }
  * }
  *
@@ -90,7 +110,7 @@ import java.time.format.DateTimeParseExc
  * @version 0.8
  * @module
  */
-public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants {
+public abstract class StaxStreamReader extends StaxStreamIO implements XMLStreamConstants, Spliterator<Feature> {
     /**
      * The XML stream reader.
      */
@@ -138,6 +158,62 @@ public abstract class StaxStreamReader e
     }
 
     /**
+     * Returns the characteristics of the iteration over feature instances.
+     * The iteration is assumed {@link #ORDERED} in the declaration order in the XML 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 XML file while an iteration is in progress).
+     *
+     * @return characteristics of iteration over the features in the XML file.
+     */
+    @Override
+    public int characteristics() {
+        return ORDERED | NONNULL | IMMUTABLE;
+    }
+
+    /**
+     * Performs the given action on the next feature instance, or returns {@code null} if there is no more
+     * feature to parse.
+     *
+     * @param  action  the action to perform on the next feature instances.
+     * @return {@code true} if a feature has been found, or {@code false} if we reached the end of XML file.
+     * @throws BackingStoreException if an error occurred while parsing the next feature instance.
+     *         The cause may be {@link DataStoreException}, {@link IOException}, {@link URISyntaxException}
+     *         or various {@link RuntimeException} among others.
+     */
+    @Override
+    public abstract boolean tryAdvance(Consumer<? super Feature> action) throws BackingStoreException;
+
+    /**
+     * Returns {@code null} by default since non-binary XML files are hard to split.
+     *
+     * @return {@code null}.
+     */
+    @Override
+    public Spliterator<Feature> trySplit() {
+        return null;
+    }
+
+    /**
+     * Returns the sentinel value meaning that the number of elements is too expensive to compute.
+     *
+     * @return {@link Long#MAX_VALUE}.
+     */
+    @Override
+    public long estimateSize() {
+        return Long.MAX_VALUE;
+    }
+
+
+
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    ////////                                                                                ////////
+    ////////                Convenience methods for subclass implementations                ////////
+    ////////                                                                                ////////
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+    /**
      * Convenience method invoking {@link StaxDataStore#inputFactory()}.
      */
     private XMLInputFactory factory() {
@@ -212,7 +288,7 @@ public abstract class StaxStreamReader e
         final XMLStreamReader reader = getReader();
         if (!reader.isStartElement()) {
             do if (!reader.hasNext()) {
-                throw new EOFException(errors().getString(Errors.Keys.UnexpectedEndOfFile_1, inputName));
+                throw new EOFException(endOfFile());
             } while (reader.next() != START_ELEMENT);
         }
         if (!isNamespace.test(reader.getNamespaceURI()) || !localName.equals(reader.getLocalName())) {
@@ -224,6 +300,7 @@ public abstract class StaxStreamReader e
     /**
      * Skips all remaining elements until we reach the end of the given tag.
      * Nested tags of the same name, if any, are also skipped.
+     * After this method invocation, the current event is {@link #END_DOCUMENT}.
      *
      * @param  tagName name of the tag to close.
      * @throws EOFException if end tag could not be found.
@@ -234,13 +311,13 @@ public abstract class StaxStreamReader e
         int nested = 0;
         while (reader.hasNext()) {
             switch (reader.next()) {
-                case XMLStreamReader.START_ELEMENT: {
+                case START_ELEMENT: {
                     if (tagName.equals(reader.getName())) {
                         nested++;
                     }
                     break;
                 }
-                case XMLStreamReader.END_ELEMENT: {
+                case END_ELEMENT: {
                     if (tagName.equals(reader.getName())) {
                         if (--nested < 0) return;
                     }
@@ -248,7 +325,7 @@ public abstract class StaxStreamReader e
                 }
             }
         }
-        throw new EOFException(errors().getString(Errors.Keys.UnexpectedEndOfFile_1, inputName));
+        throw new EOFException(endOfFile());
     }
 
     /**
@@ -283,6 +360,32 @@ public abstract class StaxStreamReader e
     }
 
     /**
+     * Returns the current value of {@link XMLStreamReader#getElementText()} as an integer,
+     * or {@code null} if that value is null or empty.
+     *
+     * @return the current text element as an integer, or {@code null} if empty.
+     * @throws XMLStreamException if a text element can not be returned.
+     * @throws NumberFormatException if the text can not be parsed as an integer.
+     */
+    protected final Integer getElementAsInteger() throws XMLStreamException {
+        final String text = getElementText();
+        return (text != null) ? Integer.valueOf(text) : null;
+    }
+
+    /**
+     * Returns the current value of {@link XMLStreamReader#getElementText()} as a floating point number,
+     * or {@code null} if that value is null or empty.
+     *
+     * @return the current text element as a floating point number, or {@code null} if empty.
+     * @throws XMLStreamException if a text element can not be returned.
+     * @throws NumberFormatException if the text can not be parsed as a floating point number.
+     */
+    protected final Double getElementAsDouble() throws XMLStreamException {
+        final String text = getElementText();
+        return (text != null) ? Numerics.valueOf(Double.parseDouble(text)) : null;
+    }
+
+    /**
      * Returns the current value of {@link XMLStreamReader#getElementText()} as a date,
      * or {@code null} if that value is null or empty.
      *
@@ -368,4 +471,34 @@ public abstract class StaxStreamReader e
         }
         super.close();
     }
+
+    /**
+     * Returns an error message for {@link EOFException}.
+     * This a convenience method for a frequently-used error.
+     *
+     * @return a localized error message for end of file error.
+     */
+    protected final String endOfFile() {
+        return errors().getString(Errors.Keys.UnexpectedEndOfFile_1, inputName);
+    }
+
+    /**
+     * Returns an error message for {@link BackingStoreException}.
+     * This a convenience method for {@link #tryAdvance(Consumer)} implementations.
+     *
+     * @return a localized error message for a file that can not be parsed.
+     */
+    protected final String canNotReadFile() {
+        return errors().getString(Errors.Keys.CanNotParseFile_2, owner.getFormatName(), inputName);
+    }
+
+    /**
+     * Returns an error message saying that nested elements are not allowed.
+     *
+     * @param  name  the name of the nested element found.
+     * @return a localized error message for forbidden nested element.
+     */
+    protected final String nestedElement(final String name) {
+        return errors().getString(Errors.Keys.NestedElementNotAllowed_1, name);
+    }
 }



Mime
View raw message