Style fixing SAX importer contribution

This commit is contained in:
Rodrigo Damazio
2010-08-12 20:22:54 -03:00
parent 6d42803dd8
commit f01c382807
2 changed files with 98 additions and 130 deletions
@@ -42,7 +42,7 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Imports GPX XML files to the my tracks provider
* Imports GPX XML files to the my tracks provider.
*
* @author Leif Hendrik Wilden
* @author Steffen Horlacher
@@ -53,15 +53,15 @@ public class GpxImporter extends DefaultHandler {
* Different date formats used in GPX files
*/
static final SimpleDateFormat DATE_FORMAT1 = new SimpleDateFormat(
"yyyy-MM-dd'T'hh:mm:ssZ");
"yyyy-MM-dd'T'hh:mm:ssZ");
static final SimpleDateFormat DATE_FORMAT2 = new SimpleDateFormat(
"yyyy-MM-dd'T'hh:mm:ss'Z'");
"yyyy-MM-dd'T'hh:mm:ss'Z'");
static final SimpleDateFormat DATE_FORMAT3 = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSSZ");
"yyyy-MM-dd'T'HH:mm:ss.SSSZ");
static final SimpleTimeZone UTC_TIMEZONE = new SimpleTimeZone(0, "UTC");
/**
* GPX-XML tag names and attributes
* GPX-XML tag names and attributes.
*/
private static final String TAG_TRACK = "trk";
private static final String TAG_TRACK_POINT = "trkpt";
@@ -72,31 +72,31 @@ public class GpxImporter extends DefaultHandler {
private static final String ATT_LAT = "lat";
private static final String ATT_LON = "lon";
final private MyTracksProviderUtils providerUtils;
private final MyTracksProviderUtils providerUtils;
/**
* List of track ids written in the database. Only contains successfully
* written tracks.
*/
final private List<Long> tracksWritten;
private final List<Long> tracksWritten;
/**
* Contains the current elements content
* Contains the current elements content.
*/
private StringBuilder content;
/**
* Currently reading location
* Currently reading location.
*/
private Location location;
/**
* Previous location, required for calculations
* Previous location, required for calculations.
*/
private Location lastLocation;
/**
* Currently reading track
* Currently reading track.
*/
private Track track;
@@ -106,7 +106,7 @@ public class GpxImporter extends DefaultHandler {
private TripStatisticsBuilder statsBuilder;
/**
* Number of locations already processed
* Number of locations already processed.
*/
private int numberOfLocations;
@@ -118,17 +118,17 @@ public class GpxImporter extends DefaultHandler {
/**
* Flag to indicate if we in a track xml element some sub elements like name
* may be used in other parts of the gpx file - ignore them
* may be used in other parts of the gpx file - ignore them.
*/
private boolean isInTrackElement;
/**
* Counter to find out which child level of track we are processing
* Counter to find out which child level of track we are processing.
*/
private int trackChildDepth;
/**
* SAX-Locator to get current line information
* SAX-Locator to get current line information.
*/
private Locator locator;
@@ -136,22 +136,16 @@ public class GpxImporter extends DefaultHandler {
* Reads GPS tracks from a GPX file and writes tracks and their coordinates to
* the database.
*
* @param tracks
* a list of tracks
* @param is
* a input steam with gpx-xml data
* @param tracks a list of tracks
* @param is a input steam with gpx-xml data
* @return long[] array of track ids written in the database
* @throws SAXException
* a parsing error
* @throws ParserConfigurationException
* internal error
* @throws IOException
* a file reading problem
* @throws SAXException a parsing error
* @throws ParserConfigurationException internal error
* @throws IOException a file reading problem
*/
public static long[] importGPXFile(final InputStream is,
final MyTracksProviderUtils providerUtils)
throws ParserConfigurationException, SAXException, IOException {
final MyTracksProviderUtils providerUtils)
throws ParserConfigurationException, SAXException, IOException {
SAXParserFactory factory = SAXParserFactory.newInstance();
GpxImporter handler = new GpxImporter(providerUtils);
SAXParser parser = factory.newSAXParser();
@@ -184,13 +178,11 @@ public class GpxImporter extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
Attributes attributes) throws SAXException {
// reset element content
content.setLength(0);
if (localName.equalsIgnoreCase(TAG_TRACK)) {
// test if we are already in a track element - abort in this case
if (isInTrackElement) {
String msg = createErrorMessage("Invalid GPX-XML detected");
@@ -203,7 +195,6 @@ public class GpxImporter extends DefaultHandler {
// process this element only as sub-elements of track
} else if (isInTrackElement) {
trackChildDepth++;
if (localName.equalsIgnoreCase(TAG_TRACK_POINT)) {
onTrackPointElementStart(attributes);
@@ -213,8 +204,7 @@ public class GpxImporter extends DefaultHandler {
@Override
public void endElement(String uri, String localName, String name)
throws SAXException {
throws SAXException {
if (localName.equalsIgnoreCase(TAG_TRACK)) {
onTrackElementEnd();
isInTrackElement = false;
@@ -222,7 +212,6 @@ public class GpxImporter extends DefaultHandler {
// process these elements only as sub-elements of track
} else if (isInTrackElement) {
if (localName.equalsIgnoreCase(TAG_TRACK_POINT)) {
onTrackPointElementEnd();
} else if (localName.equalsIgnoreCase(TAG_ALTITUDE)) {
@@ -257,7 +246,6 @@ public class GpxImporter extends DefaultHandler {
* updated with missing values later.
*/
private void onTrackElementStart() {
track = new Track();
numberOfLocations = 0;
@@ -268,10 +256,9 @@ public class GpxImporter extends DefaultHandler {
}
/**
* Reads trackpoint attributes and assigns them to the current location
* Reads trackpoint attributes and assigns them to the current location.
*
* @param attributes
* xml attributes
* @param attributes xml attributes
*/
private void onTrackPointElementStart(Attributes attributes) {
location = createLocationFromAttributes(attributes);
@@ -305,20 +292,17 @@ public class GpxImporter extends DefaultHandler {
}
/**
* Track point finished, write in database
* Track point finished, write in database.
*
* @throws SAXException
* - thrown if track point is invalid
* @throws SAXException - thrown if track point is invalid
*/
private void onTrackPointElementEnd() throws SAXException {
if (MyTracksUtils.isValidLocation(location)) {
statsBuilder.addLocation(location, location.getTime());
// insert in db
Uri trackPointIdUri = providerUtils.insertTrackPoint(location, track
.getId());
Uri trackPointIdUri = providerUtils.insertTrackPoint(location,
track.getId());
// set start and stop id for track
long trackPointId = Long.parseLong(trackPointIdUri.getLastPathSegment());
@@ -327,6 +311,7 @@ public class GpxImporter extends DefaultHandler {
if (lastLocation == null) {
track.setStartId(trackPointId);
}
// location has no setId method
// updating stop id on track every time...
track.setStopId(trackPointId);
@@ -334,7 +319,6 @@ public class GpxImporter extends DefaultHandler {
lastLocation = location;
numberOfLocations++;
} else {
// invalid location - abort import
String msg = createErrorMessage("Invalid location detected: " + location);
throw new SAXException(msg);
@@ -342,12 +326,10 @@ public class GpxImporter extends DefaultHandler {
}
/**
* Track finished - update in database
* Track finished - update in database.
*/
private void onTrackElementEnd() {
if (lastLocation != null) {
// Calculate statistics for the imported track and update
statsBuilder.pauseAt(lastLocation.getTime());
track.setNumberOfPoints(numberOfLocations);
@@ -357,9 +339,7 @@ public class GpxImporter extends DefaultHandler {
isCurrentTrackRollbackable = false;
lastLocation = null;
statsBuilder = null;
} else {
// track contains no track points makes not really
// sense to import it as we have no location
// information -> roll back
@@ -370,17 +350,14 @@ public class GpxImporter extends DefaultHandler {
/**
* Setting time and doing additional calculations as this is the last value
* required. Also sets the start time for track and statistics as there is no
* start time in the track root element
* start time in the track root element.
*
* @throws SAXException
* on parsing errors
* @throws SAXException on parsing errors
*/
private void onTimeElementEnd() throws SAXException {
long time = parseTimeForAllFormats(content.toString().trim());
if (location != null) {
// check for negative time change
if (lastLocation != null) {
long timeDifference = time - lastLocation.getTime();
@@ -393,8 +370,8 @@ public class GpxImporter extends DefaultHandler {
location.setTime(time);
// initialize start time with time of first track point
if (statsBuilder == null) {
statsBuilder = new TripStatisticsBuilder();
statsBuilder.resumeAt(time);
statsBuilder = new TripStatisticsBuilder();
statsBuilder.resumeAt(time);
}
// We don't have a speed and bearing in GPX, make something up from
@@ -418,7 +395,6 @@ public class GpxImporter extends DefaultHandler {
String altitude = content.toString().trim();
location.setAltitude(Double.parseDouble(altitude));
}
}
/**
@@ -433,7 +409,7 @@ public class GpxImporter extends DefaultHandler {
}
/**
* Get all track ids of the tracks created by this importer run
* Get all track ids of the tracks created by this importer run.
*
* @return array of track ids
*/
@@ -447,37 +423,28 @@ public class GpxImporter extends DefaultHandler {
}
/**
* Parse time trying different formats used in GPX files
* Parse time trying different formats used in GPX files.
*
* @param timeContents
* string with time information
* @param timeContents string with time information
* @return time as long
* @throws SAXException
* on time parsing errors
* @throws SAXException on time parsing errors
*/
private long parseTimeForAllFormats(String timeContents) throws SAXException {
long time = -1;
// 1st try with time zone at end a la "+0000"
time = parseTime(timeContents, DATE_FORMAT1);
if(time > -1) {
return time;
}
if (time > -1) { return time; }
// if that fails, try with a literal "Z" at the end
// (this is not according to xml standard, but some gpx files are like
// that):
time = parseTime(timeContents, DATE_FORMAT2);
if(time > -1) {
return time;
}
if (time > -1) { return time; }
// some gpx timestamps have 3 additional digits at the end.
time = parseTime(timeContents, DATE_FORMAT3);
if(time > -1) {
return time;
}
if (time > -1) { return time; }
// everything failed - abort the import
String msg = createErrorMessage("Invalid time format: " + timeContents);
@@ -496,10 +463,9 @@ public class GpxImporter extends DefaultHandler {
}
/**
* Builds an parsing error message with current line information
* Builds a parsing error message with current line information.
*
* @param details
* details about the error, will be appended
* @param details details about the error, will be appended
* @return error message string with current line information
*/
private String createErrorMessage(String details) {
@@ -512,5 +478,4 @@ public class GpxImporter extends DefaultHandler {
msg.append(details);
return msg.toString();
}
}
@@ -1,3 +1,18 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed 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 com.google.android.apps.mytracks.io;
import com.google.android.apps.mytracks.content.MyTracksProviderUtils;
@@ -46,32 +61,31 @@ public class GpxImporterTest extends AndroidTestCase {
// TODO use real files from different sources with more track points
private static final String VALID_TEST_GPX = "<gpx><trk><name><![CDATA["
+ TRACK_NAME + "]]></name><desc><![CDATA[" + TRACK_DESC
+ "]]></desc><trkseg>" + "<trkpt lat=\"" + TRACK_LAT_1 + "\" lon=\""
+ TRACK_LON_1 + "\"><ele>" + TRACK_ELE_1 + "</ele><time>"
+ TRACK_TIME_1 + "</time></trkpt> +" + "<trkpt lat=\"" + TRACK_LAT_2
+ "\" lon=\"" + TRACK_LON_2 + "\"><ele>" + TRACK_ELE_2
+ "</ele><time>" + TRACK_TIME_2 + "</time></trkpt>"
+ "</trkseg></trk></gpx>";
+ TRACK_NAME + "]]></name><desc><![CDATA[" + TRACK_DESC
+ "]]></desc><trkseg>" + "<trkpt lat=\"" + TRACK_LAT_1 + "\" lon=\""
+ TRACK_LON_1 + "\"><ele>" + TRACK_ELE_1 + "</ele><time>" + TRACK_TIME_1
+ "</time></trkpt> +" + "<trkpt lat=\"" + TRACK_LAT_2 + "\" lon=\""
+ TRACK_LON_2 + "\"><ele>" + TRACK_ELE_2 + "</ele><time>" + TRACK_TIME_2
+ "</time></trkpt>" + "</trkseg></trk></gpx>";
// invalid xml
private static final String INVALID_XML_TEST_GPX = VALID_TEST_GPX
.substring(0, VALID_TEST_GPX.length() - 50);
private static final String INVALID_XML_TEST_GPX = VALID_TEST_GPX.substring(
0, VALID_TEST_GPX.length() - 50);
private static final String INVALID_LOCATION_TEST_GPX = VALID_TEST_GPX
.replaceAll(TRACK_LAT_1, "1000.0");
.replaceAll(TRACK_LAT_1, "1000.0");
private static final String INVALID_TIME_TEST_GPX = VALID_TEST_GPX
.replaceAll(TRACK_TIME_1, "invalid");
.replaceAll(TRACK_TIME_1, "invalid");
private static final long TRACK_ID = 1;
private static final long TRACK_POINT_ID_1 = 1;
private static final long TRACK_POINT_ID_2 = 1;
private static final Uri TRACK_ID_URI = ContentUris.appendId(
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID).build();
TracksColumns.CONTENT_URI.buildUpon(), TRACK_ID).build();
private static final Uri TRACK_POINT_ID_URI_1 = ContentUris.appendId(
TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID_1).build();
TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID_1).build();
private static final Uri TRACK_POINT_ID_URI_2 = ContentUris.appendId(
TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID_2).build();
TrackPointsColumns.CONTENT_URI.buildUpon(), TRACK_POINT_ID_2).build();
private MyTracksProviderUtils providerUtils;
@@ -82,8 +96,8 @@ public class GpxImporterTest extends AndroidTestCase {
protected void setUp() throws Exception {
super.setUp();
providerUtils = AndroidMock.createMock(MyTracksProviderUtils.class);
oldProviderUtilsFactory = TestingProviderUtilsFactory
.installWithInstance(providerUtils);
oldProviderUtilsFactory =
TestingProviderUtilsFactory.installWithInstance(providerUtils);
}
@Override
@@ -96,22 +110,21 @@ public class GpxImporterTest extends AndroidTestCase {
* Test import success
*/
public void testImportSuccess() throws Exception {
Capture<Track> trackParam = new Capture<Track>();
Capture<Location> locParam1 = new MyLocationCaptue();
Capture<Location> locParam2 = new MyLocationCaptue();
AndroidMock.expect(
providerUtils.insertTrack(AndroidMock.capture(trackParam)))
.andReturn(TRACK_ID_URI);
providerUtils.insertTrack(AndroidMock.capture(trackParam))).andReturn(
TRACK_ID_URI);
AndroidMock.expect(
providerUtils.insertTrackPoint(AndroidMock.capture(locParam1),
AndroidMock.anyLong())).andReturn(TRACK_POINT_ID_URI_1);
providerUtils.insertTrackPoint(AndroidMock.capture(locParam1),
AndroidMock.anyLong())).andReturn(TRACK_POINT_ID_URI_1);
AndroidMock.expect(
providerUtils.insertTrackPoint(AndroidMock.capture(locParam2),
AndroidMock.anyLong())).andReturn(TRACK_POINT_ID_URI_2);
providerUtils.insertTrackPoint(AndroidMock.capture(locParam2),
AndroidMock.anyLong())).andReturn(TRACK_POINT_ID_URI_2);
providerUtils.updateTrack(AndroidMock.capture(trackParam));
@@ -128,8 +141,8 @@ public class GpxImporterTest extends AndroidTestCase {
Track track = trackParam.getValue();
assertEquals(TRACK_NAME, track.getName());
assertEquals(TRACK_DESC, track.getDescription());
assertEquals(format.parse(TRACK_TIME_1).getTime(),
track.getStatistics().getStartTime());
assertEquals(format.parse(TRACK_TIME_1).getTime(), track.getStatistics()
.getStartTime());
assertNotSame(-1, track.getStartId());
assertNotSame(-1, track.getStopId());
@@ -151,47 +164,41 @@ public class GpxImporterTest extends AndroidTestCase {
* Test with invalid location - track should be deleted
*/
public void testImportLocationFailure() throws ParserConfigurationException,
SAXException, IOException {
SAXException, IOException {
testInvalidXML(INVALID_LOCATION_TEST_GPX);
}
/**
* Test with invalid time - track should be deleted
*/
public void testImportTimeFailure() throws ParserConfigurationException,
SAXException, IOException {
SAXException, IOException {
testInvalidXML(INVALID_TIME_TEST_GPX);
}
/**
* Test with invalid xml - track should be deleted
*/
public void testImportXMLFailure() throws ParserConfigurationException,
SAXException, IOException {
SAXException, IOException {
testInvalidXML(INVALID_XML_TEST_GPX);
}
private void testInvalidXML(String xml) throws ParserConfigurationException,
IOException {
IOException {
AndroidMock.expect(
providerUtils.insertTrack((Track) AndroidMock.anyObject())).andReturn(
TRACK_ID_URI);
AndroidMock.expect(
providerUtils.insertTrack((Track) AndroidMock.anyObject()))
.andReturn(TRACK_ID_URI);
AndroidMock.expect(
providerUtils.insertTrackPoint((Location) AndroidMock.anyObject(),
AndroidMock.anyLong())).andStubReturn(TRACK_POINT_ID_URI_1);
providerUtils.insertTrackPoint((Location) AndroidMock.anyObject(),
AndroidMock.anyLong())).andStubReturn(TRACK_POINT_ID_URI_1);
providerUtils.deleteTrack(TRACK_ID);
AndroidMock.replay(providerUtils);
InputStream is = new ByteArrayInputStream(xml.getBytes());
try {
GpxImporter.importGPXFile(is, providerUtils);
} catch (SAXException e) {
@@ -199,24 +206,20 @@ public class GpxImporterTest extends AndroidTestCase {
}
AndroidMock.verify();
}
/**
* Workaround because of capture bug 2617107 in easymock:
*
* http://sourceforge.net/tracker/?func=detail&aid=2617107&group_id=82958&atid=567837
* http://sourceforge.net
* /tracker/?func=detail&aid=2617107&group_id=82958&atid=567837
*/
@SuppressWarnings("serial")
class MyLocationCaptue extends Capture<Location> {
@Override
public void setValue(Location value) {
if (!hasCaptured()) {
super.setValue(value);
}
}
}
}