sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jso...@apache.org
Subject svn commit: r1738241 [2/3] - in /sis/branches/JDK8: core/sis-feature/src/main/java/org/apache/sis/feature/ core/sis-feature/src/main/java/org/apache/sis/internal/feature/ ide-project/NetBeans/ ide-project/NetBeans/nbproject/ storage/ storage/sis-xmlsto...
Date Fri, 08 Apr 2016 12:43:54 GMT
Added: 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=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXReader.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,734 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gpx;
+
+import org.apache.sis.internal.xml.StaxStreamReader;
+import com.esri.core.geometry.Point;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.UnsupportedTemporalTypeException;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.stream.XMLStreamException;
+import org.opengis.geometry.Envelope;
+import org.opengis.feature.Feature;
+
+import static javax.xml.stream.XMLStreamReader.*;
+import org.apache.sis.geometry.ImmutableEnvelope;
+import static org.apache.sis.internal.gpx.GPXConstants.*;
+import org.apache.sis.referencing.CommonCRS;
+
+/**
+ * Stax reader class for GPX 1.0 and 1.1 files.
+ *
+ * Usage :<br>
+ * <pre>
+ * {@code
+ * final GPXReader reader = new GPXReader();
+ * reader.setInput(gpxInput);
+ *
+ * final GPXVersion version = reader.getVersion();
+ * final Metadata metadata = reader.getMetadata();
+ *
+ * while(reader.hasNext()) {
+ *     Feature feature = reader.next();
+ * }
+ *
+ * }
+ * </pre>
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class GPXReader extends StaxStreamReader {
+
+    private MetaData metadata;
+    private Feature current;
+    private int wayPointInc = 0;
+    private int routeInc = 0;
+    private int trackInc = 0;
+    private GPXVersion version = null;
+    private String baseNamespace = GPX_NAMESPACE_V11;
+
+    /**
+     * {@inheritDoc }
+     *
+     * @param input input object
+     * @throws IOException if input failed to be opened for any IO reason
+     * @throws XMLStreamException if input is not a valid XML stream
+     */
+    @Override
+    public void setInput(final Object input) throws IOException, XMLStreamException {
+        super.setInput(input);
+
+        //search for the bound tag to generate the envelope
+        searchLoop :
+        while(reader.hasNext()){
+            final int type = reader.next();
+
+            switch (type) {
+                case START_ELEMENT:
+                    final String typeName = reader.getLocalName();
+                    if(TAG_GPX.equalsIgnoreCase(typeName)){
+
+                        String str = "1.1"; //consider 1.1 by default
+                        for(int i=0,n=reader.getAttributeCount(); i<n;i++){
+                            if(ATT_GPX_VERSION.equalsIgnoreCase(reader.getAttributeLocalName(i))){
+                                str = reader.getAttributeValue(i);
+                            }
+                        }
+
+                        try{
+                            this.version = GPXVersion.toVersion(str);
+                        }catch(NumberFormatException ex){
+                            throw new XMLStreamException(ex);
+                        }
+
+                        if(version == GPXVersion.v1_0_0){
+                            baseNamespace = GPX_NAMESPACE_V10;
+                            //we wont found a metadata tag, must read the tags here.
+                            metadata = parseMetaData100();
+                            break searchLoop;
+                        }else{
+                            baseNamespace = GPX_NAMESPACE_V11;
+                        }
+
+                    }else if(TAG_METADATA.equalsIgnoreCase(typeName)){
+                        metadata = parseMetaData110();
+                        break searchLoop;
+                    }else if(  TAG_WPT.equalsIgnoreCase(typeName)
+                            || TAG_TRK.equalsIgnoreCase(typeName)
+                            || TAG_RTE.equalsIgnoreCase(typeName)){
+                        //there is no metadata tag
+                        break searchLoop;
+                    }
+            }
+        }
+
+    }
+
+    /**
+     * Get GPX file version.
+     * This method will return a result only if called only after the input has been set.
+     *
+     * @return GPXVersion or null if input is not set.
+     */
+    public GPXVersion getVersion() {
+        return version;
+    }
+
+    /**
+     * Get GPX metadata.
+     * This method will return a result only if called only after the input has been set.
+     *
+     * @return Metadata or null if input is not set.
+     */
+    public MetaData getMetadata() {
+        return metadata;
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public void reset() throws IOException, XMLStreamException {
+        super.reset();
+        metadata = null;
+        current = null;
+        wayPointInc = 0;
+        routeInc = 0;
+        trackInc = 0;
+    }
+
+    /**
+     * Returns true if there is a next feature in the stream.
+     * 
+     * @return true if there is next feature
+     * @throws javax.xml.stream.XMLStreamException if xml parser encounter an invalid
+     *                          element or underlying stream caused an exception
+     */
+    public boolean hasNext() throws XMLStreamException {
+        findNext();
+        return current != null;
+    }
+
+    /**
+     * Get next feature in the stream.
+     * 
+     * @return GPX WayPoint, Route or track
+     * @throws javax.xml.stream.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.
+     */
+    private void findNext() throws XMLStreamException {
+        if(current != null) return;
+
+        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(TAG_WPT.equalsIgnoreCase(localName)){
+                    current = parseWayPoint(wayPointInc++);
+                    break;
+                }else if(TAG_RTE.equalsIgnoreCase(localName)){
+                    current = parseRoute(routeInc++);
+                    break;
+                }else if(TAG_TRK.equalsIgnoreCase(localName)){
+                    current = parseTrack(trackInc++);
+                    break;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Parse current metadata element.
+     * The stax reader must be placed to the start element of the metadata.
+     *
+     * @return MetaData
+     */
+    private MetaData parseMetaData100() throws XMLStreamException {
+
+        final MetaData metadata = new MetaData();
+
+        searchLoop:
+        while (reader.hasNext()) {
+            final int type = reader.next();
+
+            switch (type) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_NAME.equalsIgnoreCase(localName)){
+                        metadata.setName(reader.getElementText());
+                    }else if(TAG_DESC.equalsIgnoreCase(localName)){
+                        metadata.setDescription(reader.getElementText());
+                    }else if(TAG_AUTHOR.equalsIgnoreCase(localName)){
+                        if(metadata.getPerson()==null) metadata.setPerson(new Person());
+                        metadata.getPerson().setName(reader.getElementText());
+                    }else if(TAG_AUTHOR_EMAIL.equalsIgnoreCase(localName)){
+                        if(metadata.getPerson()==null) metadata.setPerson(new Person());
+                        metadata.getPerson().setEmail(reader.getElementText());
+                    }else if(TAG_URL.equalsIgnoreCase(localName)){
+                        try {
+                            metadata.getLinks().add(new URI(reader.getElementText()));
+                        } catch (URISyntaxException ex) {
+                            throw new XMLStreamException(ex);
+                        }
+                    }else if(TAG_URLNAME.equalsIgnoreCase(localName)){
+                        //reader.getElementText();
+                    }else if(TAG_METADATA_TIME.equalsIgnoreCase(localName)){
+                        metadata.setTime(parseTime(reader.getElementText()));
+                    }else if(TAG_METADATA_KEYWORDS.equalsIgnoreCase(localName)){
+                        metadata.setKeywords(reader.getElementText());
+                    }else if(TAG_BOUNDS.equalsIgnoreCase(localName)){
+                        metadata.setBounds(parseBound());
+                    }else if(  TAG_WPT.equalsIgnoreCase(localName)
+                            || TAG_TRK.equalsIgnoreCase(localName)
+                            || TAG_RTE.equalsIgnoreCase(localName)){
+                        //there is no more metadata tags
+                        break searchLoop;
+                    }
+                    break;
+            }
+        }
+
+        return metadata;
+    }
+
+    /**
+     * Parse current metadata element.
+     * The stax reader must be placed to the start element of the metadata.
+     *
+     * @return MetaData
+     */
+    private MetaData parseMetaData110() throws XMLStreamException {
+
+        final MetaData metadata = new MetaData();
+
+        while (reader.hasNext()) {
+            final int type = reader.next();
+
+            switch (type) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_NAME.equalsIgnoreCase(localName)){
+                        metadata.setName(reader.getElementText());
+                    }else if(TAG_DESC.equalsIgnoreCase(localName)){
+                        metadata.setDescription(reader.getElementText());
+                    }else if(TAG_AUTHOR.equalsIgnoreCase(localName)){
+                        metadata.setPerson(parsePerson());
+                    }else if(TAG_COPYRIGHT.equalsIgnoreCase(localName)){
+                        metadata.setCopyRight(parseCopyRight());
+                    }else if(TAG_LINK.equalsIgnoreCase(localName)){
+                        metadata.getLinks().add(parseLink());
+                    }else if(TAG_METADATA_TIME.equalsIgnoreCase(localName)){
+                        metadata.setTime(parseTime(reader.getElementText()));
+                    }else if(TAG_METADATA_KEYWORDS.equalsIgnoreCase(localName)){
+                        metadata.setKeywords(reader.getElementText());
+                    }else if(TAG_BOUNDS.equalsIgnoreCase(localName)){
+                        metadata.setBounds(parseBound());
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_METADATA.equalsIgnoreCase(reader.getLocalName())){
+                        //end of the metadata element
+                        return metadata;
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, relation tag without end.");
+    }
+
+    /**
+     * Parse current copyright element.
+     * The stax reader must be placed to the start element of the copyright.
+     *
+     * @return CopyRight
+     */
+    private CopyRight parseCopyRight() throws XMLStreamException {
+        
+        final CopyRight copyright = new CopyRight();
+        copyright.setAuthor(reader.getAttributeValue(null, ATT_COPYRIGHT_AUTHOR));
+
+        while (reader.hasNext()) {
+            final int type = reader.next();
+
+            switch (type) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_COPYRIGHT_YEAR.equalsIgnoreCase(localName)){
+                        copyright.setYear(Integer.valueOf(reader.getElementText()));
+                    }else if(TAG_COPYRIGHT_LICENSE.equalsIgnoreCase(localName)){
+                        try {
+                            copyright.setLicense( new URI(reader.getElementText()));
+                        } catch (URISyntaxException ex) {
+                            throw new XMLStreamException(ex);
+                        }
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_COPYRIGHT.equalsIgnoreCase(reader.getLocalName())){
+                        return copyright;
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, copyright tag without end.");
+    }
+
+    /**
+     * Parse current URI element.
+     * The stax reader must be placed to the start element.
+     *
+     * @return URI
+     */
+    private URI parseLink() throws XMLStreamException {
+
+        String text = reader.getAttributeValue(null, ATT_LINK_HREF);
+        String mime = null;
+
+        while (reader.hasNext()) {
+            final int type = reader.next();
+
+            switch (type) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_LINK_TEXT.equalsIgnoreCase(localName) && text==null){
+                        text = reader.getElementText();
+                    }else if(TAG_LINK_TYPE.equalsIgnoreCase(localName)){
+                        mime = reader.getElementText();
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_LINK.equalsIgnoreCase(reader.getLocalName())){
+                        try {
+                            //end of the link element
+                            return new URI(text);
+                        } catch (URISyntaxException ex) {
+                            throw new XMLStreamException(ex);
+                        }
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, link tag without end.");
+    }
+
+    /**
+     * Parse current Person element.
+     * The stax reader must be placed to the start element.
+     *
+     * @return Person
+     */
+    private Person parsePerson() throws XMLStreamException {
+
+        final Person person = new Person();
+
+        while (reader.hasNext()) {
+            final int type = reader.next();
+
+            switch (type) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_NAME.equalsIgnoreCase(localName)){
+                        person.setName(reader.getElementText());
+                    }else if(TAG_AUTHOR_EMAIL.equalsIgnoreCase(localName)){
+                        person.setEmail(reader.getElementText());
+                    }else if(TAG_LINK.equalsIgnoreCase(localName)){
+                        person.setLink(parseLink());
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_AUTHOR.equalsIgnoreCase(reader.getLocalName())){
+                        //end of the author element
+                        return person;
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, person tag without end.");
+    }
+
+    /**
+     * Parse current Envelope element.
+     * The stax reader must be placed to the start element.
+     *
+     * @return Envelope
+     */
+    private Envelope parseBound() throws XMLStreamException {
+        final String xmin = reader.getAttributeValue(null, ATT_BOUNDS_MINLON);
+        final String xmax = reader.getAttributeValue(null, ATT_BOUNDS_MAXLON);
+        final String ymin = reader.getAttributeValue(null, ATT_BOUNDS_MINLAT);
+        final String ymax = reader.getAttributeValue(null, ATT_BOUNDS_MAXLAT);
+
+        if(xmin == null || xmax == null || ymin == null || ymax == null){
+            throw new XMLStreamException("Error in xml file, metadata bounds not defined correctly");
+        }
+
+        toTagEnd(TAG_BOUNDS);
+
+        return new ImmutableEnvelope(new double[] {Double.parseDouble(xmin), Double.parseDouble(ymin)},
+                                     new double[] {Double.parseDouble(xmax), Double.parseDouble(ymax)},
+                                     CommonCRS.WGS84.normalizedGeographic());
+    }
+
+    /**
+     * Parse way point type feature element.
+     * The stax reader must be placed to the start element.
+     *
+     * @return Feature
+     */
+    private Feature parseWayPoint(final int index) throws XMLStreamException {
+
+        final Feature feature = TYPE_WAYPOINT.newInstance();
+        feature.setPropertyValue("index", 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.
+        final String tagName = reader.getLocalName();
+
+        List<URI> links = null;
+
+        final String lat = reader.getAttributeValue(null, ATT_WPT_LAT);
+        final String lon = reader.getAttributeValue(null, ATT_WPT_LON);
+
+        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)));
+        }
+
+        while (reader.hasNext()) {
+            final int eventType = reader.next();
+
+            switch (eventType) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_WPT_ELE.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_ELE, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_TIME.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_TIME, parseTime(reader.getElementText()));
+                    }else if(TAG_WPT_MAGVAR.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_MAGVAR, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_GEOIHEIGHT.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_GEOIHEIGHT, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_NAME.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_NAME, reader.getElementText());
+                    }else if(TAG_CMT.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_CMT,reader.getElementText());
+                    }else if(TAG_DESC.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_DESC, reader.getElementText());
+                    }else if(TAG_SRC.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_SRC, reader.getElementText());
+                    }else if(TAG_LINK.equalsIgnoreCase(localName)){
+                        if(links == null) links = new ArrayList<>();
+                        links.add(parseLink());
+                    }else if(TAG_WPT_SYM.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_SYM, reader.getElementText());
+                    }else if(TAG_TYPE.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_TYPE, reader.getElementText());
+                    }else if(TAG_WPT_FIX.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_FIX, reader.getElementText());
+                    }else if(TAG_WPT_SAT.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_SAT, Integer.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_HDOP.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_HDOP, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_PDOP.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_PDOP, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_VDOP.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_VDOP, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_AGEOFGPSDATA.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_AGEOFGPSDATA, Double.valueOf(reader.getElementText()));
+                    }else if(TAG_WPT_DGPSID.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_WPT_DGPSID, Integer.valueOf(reader.getElementText()));
+                    }else if(version == GPXVersion.v1_0_0 && TAG_URL.equalsIgnoreCase(localName)){
+                        //GPX 1.0 only
+                        if(links == null) links = new ArrayList<>();
+                        try {
+                            links.add(new URI(reader.getElementText()));
+                        } catch (URISyntaxException ex) {
+                            throw new XMLStreamException(ex);
+                        }
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(tagName.equalsIgnoreCase(reader.getLocalName())){
+                        //end of the way point element
+                        if(links!=null) feature.setPropertyValue(TAG_LINK, links);
+                        return feature;
+                    }
+                    break;
+            }
+        }
+
+        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.
+     *
+     * @return Feature
+     */
+    private Feature parseRoute(final int index) throws XMLStreamException {
+
+        final Feature feature = TYPE_ROUTE.newInstance();
+        feature.setPropertyValue("index", index);
+
+        int ptInc = 0;
+        List<URI> links = null;
+        List<Feature> wayPoints = null;
+
+        while (reader.hasNext()) {
+            final int eventType = reader.next();
+
+            switch (eventType) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_RTE_RTEPT.equalsIgnoreCase(localName)){
+                        if(wayPoints == null) wayPoints = new ArrayList<>();
+                        wayPoints.add(parseWayPoint(ptInc++));
+                    }else if(TAG_NAME.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_NAME, reader.getElementText());
+                    }else if(TAG_CMT.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_CMT, reader.getElementText());
+                    }else if(TAG_DESC.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_DESC, reader.getElementText());
+                    }else if(TAG_SRC.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_SRC, reader.getElementText());
+                    }else if(TAG_LINK.equalsIgnoreCase(localName)){
+                        if(links == null) links = new ArrayList<>();
+                        links.add(parseLink());
+                    }else if(TAG_NUMBER.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_NUMBER, Integer.valueOf(reader.getElementText()));
+                    }else if(TAG_TYPE.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_TYPE, reader.getElementText());
+                    }else if(version == GPXVersion.v1_0_0 && TAG_URL.equalsIgnoreCase(localName)){
+                        //GPX 1.0 only
+                        if(links == null) links = new ArrayList<>();
+                        try {
+                            links.add(new URI(reader.getElementText()));
+                        } catch (URISyntaxException ex) {
+                            throw new XMLStreamException(ex);
+                        }
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_RTE.equalsIgnoreCase(reader.getLocalName())){
+                        //end of the route element
+                        if(links!=null) feature.setPropertyValue(TAG_LINK, links);
+                        if(wayPoints!=null) feature.setPropertyValue(TAG_RTE_RTEPT, wayPoints);
+                        return feature;
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, "+TAG_RTE+" tag without end.");
+    }
+
+    /**
+     * Parse track segment type feature element.
+     * The stax reader must be placed to the start element.
+     *
+     * @return Feature
+     */
+    private Feature parseTrackSegment(final int index) throws XMLStreamException {
+
+        final Feature feature = TYPE_TRACK_SEGMENT.newInstance();
+        feature.setPropertyValue("index", index);
+        int ptInc = 0;
+        List<Feature> wayPoints = null;
+
+        while (reader.hasNext()) {
+            final int eventType = reader.next();
+
+            switch (eventType) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_TRK_SEG_PT.equalsIgnoreCase(localName)){
+                        if(wayPoints == null) wayPoints = new ArrayList<>();
+                        wayPoints.add(parseWayPoint(ptInc++));
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_TRK_SEG.equalsIgnoreCase(reader.getLocalName())){
+                        //end of the track segment element
+                        if(wayPoints!=null) feature.setPropertyValue(TAG_TRK_SEG_PT, wayPoints);
+                        return feature;
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, "+TAG_TRK_SEG+" tag without end.");
+    }
+
+    /**
+     * Parse track type feature element.
+     * The stax reader must be placed to the start element.
+     *
+     * @return Feature
+     */
+    private Feature parseTrack(final int index) throws XMLStreamException {
+
+        final Feature feature = TYPE_TRACK.newInstance();
+        feature.setPropertyValue("index", index);
+        int segInc = 0;
+        List<URI> links = null;
+        List<Feature> segments = null;
+
+        while (reader.hasNext()) {
+            final int eventType = reader.next();
+
+            switch (eventType) {
+                case START_ELEMENT:
+                    final String localName = reader.getLocalName();
+                    if(TAG_TRK_SEG.equalsIgnoreCase(localName)){
+                        if(segments == null) segments = new ArrayList<>();
+                        segments.add(parseTrackSegment(segInc++));
+                    }else if(TAG_NAME.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_NAME, reader.getElementText());
+                    }else if(TAG_CMT.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_CMT, reader.getElementText());
+                    }else if(TAG_DESC.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_DESC, reader.getElementText());
+                    }else if(TAG_SRC.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_SRC, reader.getElementText());
+                    }else if(TAG_LINK.equalsIgnoreCase(localName)){
+                        if(links == null) links = new ArrayList<>();
+                        links.add(parseLink());
+                    }else if(TAG_NUMBER.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_NUMBER, Integer.valueOf(reader.getElementText()));
+                    }else if(TAG_TYPE.equalsIgnoreCase(localName)){
+                        feature.setPropertyValue(TAG_TYPE, reader.getElementText());
+                    }else if(version == GPXVersion.v1_0_0 && TAG_URL.equalsIgnoreCase(localName)){
+                        //GPX 1.0 only
+                        if(links == null) links = new ArrayList<>();
+                        try {
+                            links.add(new URI(reader.getElementText()));
+                        } catch (URISyntaxException ex) {
+                            throw new XMLStreamException(ex);
+                        }
+                    }
+                    break;
+                case END_ELEMENT:
+                    if(TAG_TRK.equalsIgnoreCase(reader.getLocalName())){
+                        //end of the track element
+                        if(links!=null) feature.setPropertyValue(TAG_LINK, links);
+                        if(segments!=null) feature.setPropertyValue(TAG_TRK_SEG, segments);
+                        return feature;
+                    }
+                    break;
+            }
+        }
+
+        throw new XMLStreamException("Error in xml file, "+TAG_TRK+" tag without end.");
+    }
+
+    /**
+     * Parse date or date time from string.
+     * The method support only ISO 8601 Date and DateTime formats.
+     *
+     * @param dateStr date in ISO date or data time format
+     * @return Temporal
+     */
+    private static Temporal parseTime(String dateStr) {
+        try{
+            final DateTimeFormatter format = DateTimeFormatter.ISO_DATE;
+            final TemporalAccessor accessor = format.parse(dateStr);
+            return LocalDate.from(accessor);
+        }catch(UnsupportedTemporalTypeException ex){
+            final DateTimeFormatter format = DateTimeFormatter.ISO_DATE_TIME;
+            final TemporalAccessor accessor = format.parse(dateStr);
+            return LocalDateTime.from(accessor);
+        }
+    }
+    
+}

Added: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXVersion.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXVersion.java?rev=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXVersion.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GPXVersion.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gpx;
+
+import org.apache.sis.util.ArgumentChecks;
+
+/**
+ * GPX versions enumeration
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public enum GPXVersion {
+    /**
+     * GPX 1.0
+     */
+    v1_0_0,
+    /**
+     * GPX 1.1
+     */
+    v1_1_0;
+
+    /**
+     * Convert code to GPXVersion enum.
+     *
+     * @param code gpx version as string
+     * @return enumeration
+     * @throws NumberFormatException if version is not formatted as expected
+     */
+    public static GPXVersion toVersion(String code) throws NumberFormatException {
+        ArgumentChecks.ensureNonNull("code", code);
+        switch (code.trim()) {
+            case "1.0":
+                return v1_0_0;
+            case "1.1":
+                return v1_1_0;
+            default:
+                throw new NumberFormatException("Invalid version number " + code);
+        }
+    }
+}

Added: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPointsAsPolylineOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPointsAsPolylineOperation.java?rev=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPointsAsPolylineOperation.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPointsAsPolylineOperation.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,171 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gpx;
+
+import com.esri.core.geometry.Point;
+import com.esri.core.geometry.Polyline;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.MultiValuedPropertyException;
+import org.opengis.feature.Property;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.util.GenericName;
+
+/**
+ * A calculated attribute that define a Polyline geometry calculated
+ * from other attributes of the feature.
+ *
+ * For example : a boat that record it's position every hour.
+ * each record is available in a 0-N associate attribute.
+ * This class while extract each position and create a line as a new attribute.
+ * Any change applied to the positions will be visible on the line.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class GroupPointsAsPolylineOperation extends AbstractOperation {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -5169104838093353092L;
+    
+    private static final AttributeType<Polyline> TYPE = new DefaultAttributeType<>(
+            Collections.singletonMap(NAME_KEY, "Polyline"),Polyline.class,1,1,null);
+
+    /**
+     * An empty parameter value group for this operation.
+     */
+    private static final ParameterDescriptorGroup EMPTY_PARAMS =
+            new DefaultParameterDescriptorGroup(Collections.singletonMap("name", "noargs"), 0, 1);
+
+    private final GenericName[] path;
+
+    /**
+     *
+     * @param name operation name
+     * @param attributePath names of the properties to group
+     */
+    public GroupPointsAsPolylineOperation(GenericName name, GenericName ... attributePath) {
+        this(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),attributePath);
+    }
+
+    /**
+     *
+     * @param identification operation identification parameters
+     * @param attributePath names of the properties to group
+     */
+    public GroupPointsAsPolylineOperation(Map<String, ?> identification, GenericName ... attributePath) {
+        super(identification);
+        this.path = attributePath;
+    }
+
+    /**
+     * Returns an empty parameter descriptor group.
+     * @return Empty parameter descriptor
+     */
+    @Override
+    public ParameterDescriptorGroup getParameters() {
+        return EMPTY_PARAMS;
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public IdentifiedType getResult() {
+        return TYPE;
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Property apply(Feature feature, ParameterValueGroup parameters) {
+        return new GeomAtt(feature);
+    }
+
+    private boolean explore(final Feature att, final int depth, Polyline geom, boolean first) {
+        if(depth == path.length-1){
+            //we are on the field that hold the geometry points
+            for (final Object propVal : asCollection(att, path[depth])) {
+                if(first){
+                    geom.startPath(((Point)propVal));
+                    first = false;
+                }else{
+                    geom.lineTo(((Point)propVal));
+                }
+            }
+        }else{
+            //explore children
+            int d = depth+1;
+            for (final Object prop : asCollection(att,path[depth])) {
+                final Feature child = (Feature) prop;
+                first = explore(child, d, geom,first);
+            }
+        }
+        return first;
+    }
+
+    private static Collection asCollection(Feature att, GenericName property) {
+        final Object value = att.getPropertyValue(property.toString());
+        if(value == null) return Collections.EMPTY_LIST;
+        if(value instanceof Collection) return (Collection) value;
+        return Collections.singletonList(value);
+    }
+
+
+    /**
+     * Operation attribute.
+     * Value is calculated each time it is accessed.
+     */
+    private final class GeomAtt extends AbstractAttribute<Polyline> {
+
+        private static final long serialVersionUID = -8872834506769732436L;
+
+        private final Feature feature;
+
+        public GeomAtt(final Feature feature) {
+            super(TYPE);
+            this.feature = feature;
+        }
+
+        @Override
+        public Polyline getValue() throws MultiValuedPropertyException {
+            final Polyline geom = new Polyline();
+            explore(feature,0,geom,true);
+            return geom;
+        }
+
+        @Override
+        public void setValue(Polyline value) {
+            throw new UnsupportedOperationException("Operation attribute can not be set.");
+        }
+
+    }
+
+}

Added: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPolylinesOperation.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPolylinesOperation.java?rev=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPolylinesOperation.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/GroupPolylinesOperation.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gpx;
+
+import com.esri.core.geometry.Polyline;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.sis.feature.AbstractAttribute;
+import org.apache.sis.feature.AbstractOperation;
+import org.apache.sis.feature.DefaultAttributeType;
+import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
+import org.opengis.feature.AttributeType;
+import org.opengis.feature.Feature;
+import org.opengis.feature.IdentifiedType;
+import org.opengis.feature.MultiValuedPropertyException;
+import org.opengis.feature.Property;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.parameter.ParameterValueGroup;
+import org.opengis.util.GenericName;
+
+/**
+ * A calculated attribute that define a MultiLineString geometry calculated
+ * from other attributes of the feature.
+ *
+ * For example : a boat that record tracks every hour.
+ * each record is available in a 0-N complex attribute.
+ * This class while extract each track and create a Polyline as a new attribute.
+ * Any change applied to the tracks will be visible on the Polyline.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public final class GroupPolylinesOperation extends AbstractOperation {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 7898989085371304159L;
+
+    private static final AttributeType<Polyline> TYPE = new DefaultAttributeType<>(
+            Collections.singletonMap(NAME_KEY, "MultiLineString"),Polyline.class,1,1,null);
+
+    /**
+     * An empty parameter value group for this operation.
+     */
+    private static final ParameterDescriptorGroup EMPTY_PARAMS =
+            new DefaultParameterDescriptorGroup(Collections.singletonMap("name", "noargs"), 0, 1);
+
+    private final GenericName[] path;
+
+    /**
+     * 
+     * @param name operation name
+     * @param attributePath names of the properties to group
+     */
+    public GroupPolylinesOperation(GenericName name, GenericName ... attributePath) {
+        this(Collections.singletonMap(DefaultAttributeType.NAME_KEY, name),attributePath);
+    }
+
+    /**
+     *
+     * @param identification operation identification parameters
+     * @param attributePath names of the properties to group
+     */
+    public GroupPolylinesOperation(Map<String, ?> identification, GenericName ... attributePath) {
+        super(identification);
+        this.path = attributePath;
+    }
+
+    /**
+     * Returns an empty parameter descriptor group.
+     */
+    @Override
+    public ParameterDescriptorGroup getParameters() {
+        return EMPTY_PARAMS;
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public IdentifiedType getResult() {
+        return TYPE;
+    }
+
+    /**
+     * {@inheritDoc }
+     */
+    @Override
+    public Property apply(Feature feature, ParameterValueGroup parameters) {
+        return new GeomAtt(feature);
+    }
+
+    private void explore(final Feature att, final int depth, final Polyline geom) {
+        if(depth == path.length-1){
+            //we are on the field that hold the geometry
+            for (final Object propVal : asCollection(att, path[depth])) {
+                geom.add((Polyline)propVal, false);
+            }
+        }else{
+            //explore childs
+            int d = depth+1;
+            for (final Object prop : asCollection(att,path[depth])) {
+                final Feature child = (Feature) prop;
+                explore(child, d, geom);
+            }
+        }
+    }
+
+    private static Collection asCollection(Feature att, GenericName property) {
+        final Object value = att.getPropertyValue(property.toString());
+        if(value == null) return Collections.EMPTY_LIST;
+        if(value instanceof Collection) return (Collection) value;
+        return Collections.singletonList(value);
+    }
+
+    /**
+     * Operation attribute.
+     * Value is calculated each time it is accessed.
+     */
+    private final class GeomAtt extends AbstractAttribute<Polyline> {
+
+        private static final long serialVersionUID = 2246647654324667404L;
+
+        private final Feature feature;
+
+        public GeomAtt(final Feature feature) {
+            super(TYPE);
+            this.feature = feature;
+        }
+
+        @Override
+        public Polyline getValue() throws MultiValuedPropertyException {
+            final Polyline geom = new Polyline();
+            explore(feature,0,geom);
+            return geom;
+        }
+
+        @Override
+        public void setValue(Polyline value) {
+            throw new UnsupportedOperationException("Operation attribute can not be set.");
+        }
+
+    }
+
+}

Added: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/MetaData.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/MetaData.java?rev=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/MetaData.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/MetaData.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gpx;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.sis.io.TableAppender;
+
+import org.opengis.geometry.Envelope;
+
+/**
+ * Metadata object as defined in GPX.
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class MetaData {
+
+    private String name;
+    private String description;
+    private Person person;
+    private CopyRight copyRight;
+    private final List<URI> links = new ArrayList<>();
+    private Temporal time;
+    private String keywords;
+    private Envelope bounds;
+
+    /**
+     * Returns the dataset name.
+     *
+     * @return name, may be null
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set dataset name.
+     *
+     * @param name, can be null
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the dataset description.
+     *
+     * @return description, may be null
+     */
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * Set dataset description.
+     *
+     * @param description, can be null
+     */
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    /**
+     * Returns dataset author person.
+     *
+     * @return Person, may be null
+     */
+    public Person getPerson() {
+        return person;
+    }
+
+    /**
+     * Set dataset author person.
+     *
+     * @param person, can be null
+     */
+    public void setPerson(Person person) {
+        this.person = person;
+    }
+
+    /**
+     * Returns the dataset copyright
+     *
+     * @return copyright, may be null
+     */
+    public CopyRight getCopyRight() {
+        return copyRight;
+    }
+
+    /**
+     * Set dataset copyright.
+     *
+     * @param copyRight, can be null
+     */
+    public void setCopyRight(CopyRight copyRight) {
+        this.copyRight = copyRight;
+    }
+
+    /**
+     * Returns the dataset links
+     * The returned list is modifiable.
+     *
+     * @return list of links, never null
+     */
+    public List<URI> getLinks() {
+        return links;
+    }
+
+    /**
+     * Returns the dataset creation time.
+     *
+     * @return Temporal, may be null
+     */
+    public Temporal getTime() {
+        return time;
+    }
+
+    /**
+     * Set dataset creation time.
+     *
+     * @param time, can be null
+     */
+    public void setTime(Temporal time) {
+        this.time = time;
+    }
+
+    /**
+     * Returns dataset keywords.
+     *
+     * @return keywords, may be null
+     */
+    public String getKeywords() {
+        return keywords;
+    }
+
+    /**
+     * Set dataset keywords.
+     *
+     * @param keywords, can be null
+     */
+    public void setKeywords(String keywords) {
+        this.keywords = keywords;
+    }
+
+    /**
+     * Returns the dataset bounding box.
+     *
+     * @return Envelope, may be null
+     */
+    public Envelope getBounds() {
+        return bounds;
+    }
+
+    /**
+     * Set dataset bounding box.
+     *
+     * @param bounds, can be null
+     */
+    public void setBounds(Envelope bounds) {
+        this.bounds = bounds;
+    }
+
+    @Override
+    public String toString() {
+        final StringWriter writer = new StringWriter();
+        final TableAppender tablewriter = new TableAppender(writer);
+
+        tablewriter.appendHorizontalSeparator();
+        tablewriter.append("GPX-Metadata\t \n");
+        tablewriter.appendHorizontalSeparator();
+
+        tablewriter.append("Name\t"+getName()+"\n");
+        tablewriter.append("Desc\t"+getDescription()+"\n");
+        tablewriter.append("Time\t"+getTime()+"\n");
+        tablewriter.append("Keywords\t"+getKeywords()+"\n");
+        tablewriter.append("Bounds\t"+getBounds()+"\n");
+
+        final Person person = getPerson();
+        if(person != null){
+            tablewriter.append("Person - Name\t"+person.getName()+"\n");
+            tablewriter.append("Person - EMail\t"+person.getEmail()+"\n");
+            tablewriter.append("Person - Link\t"+person.getLink()+"\n");
+        }else{
+            tablewriter.append("Person\t"+person+"\n");
+        }
+
+        final CopyRight copyright = getCopyRight();
+        if(copyright != null){
+            tablewriter.append("CopyRight - Author\t"+copyright.getAuthor()+"\n");
+            tablewriter.append("CopyRight - Year\t"+copyright.getYear()+"\n");
+            tablewriter.append("CopyRight - License\t"+copyright.getLicense()+"\n");
+        }else{
+            tablewriter.append("CopyRight\t"+copyright+"\n");
+        }
+
+        tablewriter.append("Links\t");
+        final List<URI> links = getLinks();
+        if(links.isEmpty()){
+            tablewriter.append("None\n");
+        }else{
+            tablewriter.append("\n");
+            for(final URI uri : getLinks()){
+                tablewriter.append("\t"+uri+"\n");
+            }
+        }
+
+        tablewriter.appendHorizontalSeparator();
+
+        try {
+            tablewriter.flush();
+            writer.flush();
+        } catch (IOException ex) {
+            //will never happen is this case
+            ex.printStackTrace();
+        }
+
+        return writer.getBuffer().toString();
+    }
+
+}

Added: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Person.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Person.java?rev=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Person.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/gpx/Person.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.gpx;
+
+import java.net.URI;
+
+/**
+ * Person object as defined in GPX.
+ * 
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public class Person {
+
+    private String name;
+    private String email;
+    private URI link;
+
+    /**
+     * Returns the person name.
+     *
+     * @return name, may be null
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set person name.
+     *
+     * @param name, can be null
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns the person email.
+     *
+     * @return email, may be null
+     */
+    public String getEmail() {
+        return email;
+    }
+
+    /**
+     * Set person email.
+     *
+     * @param email, can be null
+     */
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    /**
+     * Returns the person information link.
+     *
+     * @return uri, may be null
+     */
+    public URI getLink() {
+        return link;
+    }
+
+    /**
+     * Set person information link.
+     *
+     * @param link, can be null
+     */
+    public void setLink(URI link) {
+        this.link = link;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("Person(");
+        sb.append(name).append(',').append(email).append(',').append(link);
+        sb.append(')');
+        return sb.toString();
+    }
+
+}

Added: sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/AbstractConfigurable.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/AbstractConfigurable.java?rev=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/AbstractConfigurable.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/AbstractConfigurable.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.xml;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author Guilhem Legal (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public abstract class AbstractConfigurable {
+
+    /**
+     * Map of optional properties to configure the underlying object.
+     *
+     */
+    protected final Map<String, Object> properties = new HashMap<>();
+
+    /**
+     * 
+     * @param key property key
+     * @return value, may be null
+     */
+    public Object getProperty(final String key) {
+        return properties.get(key);
+    }
+    
+    /**
+     * @return the properties
+     */
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+}

Added: 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=1738241&view=auto
==============================================================================
--- sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java (added)
+++ sis/branches/JDK8/storage/sis-xmlstore/src/main/java/org/apache/sis/internal/xml/StaxStreamReader.java Fri Apr  8 12:43:53 2016
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.internal.xml;
+
+import java.io.*;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.Result;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.dom.DOMSource;
+import org.w3c.dom.Node;
+
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+
+import static javax.xml.stream.XMLStreamReader.*;
+import javax.xml.stream.util.StreamReaderDelegate;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.transform.stax.StAXSource;
+import org.w3c.dom.Document;
+import org.xml.sax.SAXException;
+
+/**
+ * An abstract class for all stax parser.<br>
+ * Readers for a given specification should extend this class and
+ * provide appropriate read methods.<br>
+ * <br>
+ * Example : <br>
+ * <pre>
+ * {@code
+ * public class UserReader extends StaxStreamReader{
+ *
+ *   public User read() throws XMLStreamException{
+ *      //casual stax reading operations
+ *      return user;
+ *   }
+ * }
+ * }
+ * </pre>
+ * And should be used like :<br>
+ * <pre>
+ * {@code
+ * final UserReader instance = new UserReader();
+ * try{
+ *     instance.setInput(stream);
+ *     user = instance.read();
+ * }finally{
+ *     instance.dispose();
+ * }
+ * }
+ * </pre>
+ *
+ * @author Johann Sorel (Geomatys)
+ * @since   0.7
+ * @version 0.7
+ * @module
+ */
+public abstract class StaxStreamReader extends AbstractConfigurable implements AutoCloseable {
+
+    /**
+     * Wrapped xml stream reader
+     */
+    protected XMLStreamReader reader;
+    
+    /**
+     * Store the input stream if it was generated by the parser itself.
+     * It will closed on the dispose method or when a new input is set.
+     */
+    private InputStream sourceStream;
+
+    /**
+     * close potentiel previous stream and cache if there are some.
+     * This way the reader can be reused for a different input later.
+     * The underlying stax reader will be closed.
+     * 
+     * @throws java.io.IOException if previous source stream caused an exception on close
+     * @throws javax.xml.stream.XMLStreamException if previous stax reader caused an exception on close
+     */
+    public void reset() throws IOException, XMLStreamException {
+        if(sourceStream != null){
+            sourceStream.close();
+            sourceStream = null;
+        }
+        if(reader != null){
+            reader.close();
+            reader = null;
+        }
+    }
+
+    /**
+     * Release potentiel locks or opened stream.
+     * Must be called when the reader is not needed anymore.
+     * It should not be used after this method has been called.
+     */
+    @Override
+    public void close() throws Exception {
+        reset();
+    }
+    
+    /**
+     * Set the input for this reader.<br>
+     * Handle types are :<br>
+     * - java.io.File<br>
+     * - java.io.Reader<br>
+     * - java.io.InputStream<br>
+     * - java.net.URL<br>
+     * - java.net.URI<br>
+     * - javax.xml.stream.XMLStreamReader<br>
+     * - javax.xml.transform.Source<br>
+     * 
+     * @param input input object
+     * @throws IOException if input failed to be opened for any IO reason
+     * @throws XMLStreamException if input is not a valid XML stream
+     */
+    public void setInput(Object input) throws IOException, XMLStreamException {
+        reset();
+
+        if(input instanceof XMLStreamReader){
+            reader = (XMLStreamReader) input;
+            return;
+        }
+
+        if(input instanceof File){
+            sourceStream = new FileInputStream((File)input);
+            input = sourceStream;
+        }else if(input instanceof Path){
+            sourceStream = Files.newInputStream((Path)input, StandardOpenOption.READ);
+            input = sourceStream;
+        }else if(input instanceof URL){
+            sourceStream = ((URL)input).openStream();
+            input = sourceStream;
+        }else if(input instanceof URI){
+            sourceStream = ((URI)input).toURL().openStream();
+            input = sourceStream;
+        }else if(input instanceof String){
+            input = new StringReader((String) input);
+        }
+
+        reader = toReader(input);
+    }
+
+    /**
+     * Iterator on the reader until it reachs the end of the given tag name.
+     * @param tagName tag name to search
+     * @throws XMLStreamException if end tag could not be found
+     *                      or if there is error processing the xml stream
+     */
+    protected void toTagEnd(final String tagName) throws XMLStreamException {
+        while (reader.hasNext()) {
+            if(END_ELEMENT == reader.next() &&
+               tagName.equalsIgnoreCase(reader.getLocalName()))
+               return;
+        }
+        throw new XMLStreamException("Error in xml file, Could not find end of tag "+tagName+" .");
+    }
+
+    /**
+     * Creates a new XMLStreamReader.
+     * @param input input to convert to stax stream
+     * @return XMLStreamReader
+     * @throws XMLStreamException if the input is not handled
+     */
+    private static final XMLStreamReader toReader(final Object input)
+            throws XMLStreamException {
+        final XMLInputFactory XMLfactory = XMLInputFactory.newInstance();
+        XMLfactory.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE);
+        XMLfactory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.FALSE);
+
+        if (input instanceof InputStream) {
+            return XMLfactory.createXMLStreamReader((InputStream) input);
+        } else if (input instanceof Source) {
+            return XMLfactory.createXMLStreamReader((Source) input);
+        } else if (input instanceof Node) {
+            
+            /* here we can think that we can use a DOMSource and pass it directly to the
+             * method XMLfactory.createXMLStreamReader(Source) but it lead to a NPE
+             * during the geometry unmarshall.
+             */
+            try {
+                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+                Result outputTarget = new StreamResult(outputStream);
+                Transformer t = TransformerFactory.newInstance().newTransformer();
+                t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+                t.transform(new DOMSource((Node) input), outputTarget);
+                return XMLfactory.createXMLStreamReader(new ByteArrayInputStream(outputStream.toByteArray()));
+            } catch (TransformerException ex) {
+                throw new XMLStreamException(ex);
+            }
+        } else if (input instanceof Reader) {
+            return XMLfactory.createXMLStreamReader((Reader) input);
+        } else {
+            throw new XMLStreamException("Input type is not supported : " + input);
+        }
+    }
+
+    /**
+     * <p>XML language provides two notations for boolean type :
+     * "true" can be written "1" and "0" significates "false".
+     * This method considers all this values as CharSequences and return its boolean value.</p>
+     *
+     * @param candidate The String to parse
+     * @return true if bool is equal to "true" or "1".
+     */
+    protected static boolean parseBoolean(final String candidate) {
+        if (candidate.length() == 1) {
+            return !candidate.equals("0");
+        }
+        return Boolean.parseBoolean(candidate);
+    }
+
+    /**
+     * <p>This method reads doubles with coma separated.</p>
+     * 
+     * @param candidate Can not be null.
+     * @return parsed double value
+     */
+    protected static double parseDouble(final String candidate) {
+        return Double.parseDouble(candidate.replace(',', '.'));
+    }
+
+    /**
+     * Iterator on the reader until it reach the end of the given tag name.
+     * Return the read elements as dom.
+     *
+     * @param tagName tag
+     * @return Document read elements as a dom document
+     * @throws javax.xml.stream.XMLStreamException if conversion to dom failed
+     */
+    protected Document readAsDom(final String tagName) throws XMLStreamException {
+        
+        final XMLStreamReader limitedReader = new StreamReaderDelegate(reader){
+            boolean finished = false;
+
+            @Override
+            public boolean hasNext() throws XMLStreamException {
+                if(finished) return false;
+                return super.hasNext();
+            }
+
+            @Override
+            public int next() throws XMLStreamException {
+                int t = super.next();
+                finished = END_ELEMENT == t && tagName.equalsIgnoreCase(reader.getLocalName());
+                return t;
+            }
+
+        };
+
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setNamespaceAware(false);
+
+        final TransformerFactory trsFactory = TransformerFactory.newInstance();
+        try {
+            final DocumentBuilder builder = factory.newDocumentBuilder();
+            final Transformer idTransform = trsFactory.newTransformer();
+            final Source input = new StAXSource(limitedReader);
+            final ByteArrayOutputStream out = new ByteArrayOutputStream();
+            final Result output = new StreamResult(out);
+            idTransform.transform(input, output);
+            final Document doc = builder.parse(new ByteArrayInputStream(out.toByteArray()));
+            return doc;
+        } catch (TransformerConfigurationException e) {
+            throw new XMLStreamException(e.getMessage());
+        } catch (TransformerFactoryConfigurationError e) {
+            throw new XMLStreamException(e.getMessage());
+        } catch (IOException e) {
+            throw new XMLStreamException(e.getMessage());
+        } catch (TransformerException e) {
+            throw new XMLStreamException(e.getMessage());
+        } catch (SAXException e) {
+            throw new XMLStreamException(e.getMessage());
+        } catch (ParserConfigurationException e) {
+            throw new XMLStreamException(e.getMessage());
+        }
+
+    }
+
+}



Mime
View raw message