You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2016/12/20 17:22:53 UTC
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...
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(\u2026)} (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: // \ufe19
+ case Tags.DESCRIPTION: // \ufe19
+ case Tags.SOURCE: // \ufe19
+ case Tags.SYMBOL: // \ufe19
+ case Tags.TYPE: value = getElementText(); break;
+ case Tags.TIME: value = getElementAsTemporal(); break;
+ case Tags.MAGNETIC_VAR: // Fallthrough to getElementAsDouble()
+ case Tags.GEOID_HEIGHT: // \ufe19
+ case Tags.AGE_OF_GPS_DATA: // \ufe19
+ case Tags.HDOP: // \ufe19
+ case Tags.PDOP: // \ufe19
+ case Tags.VDOP: // \ufe19
+ 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: // \ufe19
+ case Tags.DESCRIPTION: // \ufe19
+ case Tags.SOURCE: // \ufe19
+ 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: // \ufe19
+ case Tags.DESCRIPTION: // \ufe19
+ case Tags.SOURCE: // \ufe19
+ 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 {
* \u2502 link \u2502 OnlineResource \u2502 gpx:linkType \u2502 [0 \u2026 \u221e] \u2502
* \u2502 sym \u2502 String \u2502 xsd:string \u2502 [0 \u2026 1] \u2502
* \u2502 type \u2502 String \u2502 xsd:string \u2502 [0 \u2026 1] \u2502
- * \u2502 fix \u2502 String \u2502 gpx:fixType \u2502 [0 \u2026 1] \u2502
+ * \u2502 fix \u2502 Fix \u2502 gpx:fixType \u2502 [0 \u2026 1] \u2502
* \u2502 sat \u2502 Integer \u2502 xsd:nonNegativeInteger \u2502 [0 \u2026 1] \u2502
* \u2502 hdop \u2502 Double \u2502 xsd:decimal \u2502 [0 \u2026 1] \u2502
* \u2502 vdop \u2502 Double \u2502 xsd:decimal \u2502 [0 \u2026 1] \u2502
@@ -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);
+ }
}