From 74d552d1878243ccc58777a92378804438bffeb7 Mon Sep 17 00:00:00 2001 From: Andreas Schildbach Date: Mon, 15 Jun 2015 11:18:52 +0200 Subject: [PATCH] Migrate all HTTP calls to use OkHttp rather than URLConnection. --- enabler/build.gradle | 2 + .../schildbach/pte/AbstractEfaProvider.java | 1023 ++++---- .../schildbach/pte/AbstractHafasProvider.java | 2139 +++++++++-------- .../pte/AbstractNavitiaProvider.java | 20 +- .../schildbach/pte/AbstractTsiProvider.java | 10 +- .../src/de/schildbach/pte/HslProvider.java | 603 +++-- .../src/de/schildbach/pte/InvgProvider.java | 4 +- .../src/de/schildbach/pte/SeptaProvider.java | 4 +- .../src/de/schildbach/pte/VrsProvider.java | 12 +- .../pte/exception/AbstractHttpException.java | 31 +- .../pte/exception/BlockedException.java | 7 +- .../pte/exception/InternalErrorException.java | 7 +- .../pte/exception/NotFoundException.java | 7 +- .../UnexpectedRedirectException.java | 8 +- .../de/schildbach/pte/util/HttpClient.java | 296 +-- .../schildbach/pte/util/HttpClientTest.java | 24 +- 16 files changed, 2135 insertions(+), 2062 deletions(-) diff --git a/enabler/build.gradle b/enabler/build.gradle index d0d5e982..aed61448 100644 --- a/enabler/build.gradle +++ b/enabler/build.gradle @@ -2,6 +2,8 @@ apply plugin: 'java' apply plugin: 'eclipse' dependencies { + compile 'com.squareup.okhttp3:okhttp:3.4.1' + compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' compile 'com.google.guava:guava:18.0' compile 'org.slf4j:slf4j-api:1.7.12' compile 'com.google.code.findbugs:jsr305:3.0.0' diff --git a/enabler/src/de/schildbach/pte/AbstractEfaProvider.java b/enabler/src/de/schildbach/pte/AbstractEfaProvider.java index 6a49906c..caef613a 100644 --- a/enabler/src/de/schildbach/pte/AbstractEfaProvider.java +++ b/enabler/src/de/schildbach/pte/AbstractEfaProvider.java @@ -83,6 +83,9 @@ import de.schildbach.pte.util.HttpClient; import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.XmlPullUtil; +import okhttp3.HttpUrl; +import okhttp3.ResponseBody; + /** * @author Andreas Schildbach */ @@ -244,10 +247,10 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final StringBuilder parameters = stopfinderRequestParameters(constraint, "JSON"); final CharSequence page; if (httpPost) - page = httpClient.get(uri.toString(), parameters.substring(1), "application/x-www-form-urlencoded", - Charsets.UTF_8); + page = httpClient.get(HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", Charsets.UTF_8); else - page = httpClient.get(uri.append(parameters).toString(), Charsets.UTF_8); + page = httpClient.get(HttpUrl.parse(uri.append(parameters).toString()), Charsets.UTF_8); final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); try { @@ -354,126 +357,128 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { protected SuggestLocationsResult xmlStopfinderRequest(final Location constraint) throws IOException { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML"); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterItdRequest(pp); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + final List locations = new ArrayList(); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterItdRequest(pp); + XmlPullUtil.enter(pp, "itdStopFinderRequest"); - final List locations = new ArrayList(); + processItdOdv(pp, "sf", new ProcessItdOdvCallback() { + @Override + public void location(final String nameState, final Location location, final int matchQuality) { + locations.add(new SuggestedLocation(location, matchQuality)); + } + }); - XmlPullUtil.enter(pp, "itdStopFinderRequest"); + XmlPullUtil.skipExit(pp, "itdStopFinderRequest"); - processItdOdv(pp, "sf", new ProcessItdOdvCallback() { - @Override - public void location(final String nameState, final Location location, final int matchQuality) { - locations.add(new SuggestedLocation(location, matchQuality)); + result.set(new SuggestLocationsResult(header, locations)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - }); + } + }; - XmlPullUtil.skipExit(pp, "itdStopFinderRequest"); + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); - return new SuggestLocationsResult(header, locations); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } protected SuggestLocationsResult mobileStopfinderRequest(final Location constraint) throws IOException { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); final StringBuilder parameters = stopfinderRequestParameters(constraint, "XML"); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterEfa(pp); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + final List locations = new ArrayList(); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterEfa(pp); + XmlPullUtil.require(pp, "sf"); + if (!pp.isEmptyElementTag()) { + XmlPullUtil.enter(pp, "sf"); - final List locations = new ArrayList(); + while (XmlPullUtil.test(pp, "p")) { + XmlPullUtil.enter(pp, "p"); - XmlPullUtil.require(pp, "sf"); - if (!pp.isEmptyElementTag()) { - XmlPullUtil.enter(pp, "sf"); + final String name = normalizeLocationName(XmlPullUtil.valueTag(pp, "n")); + final String u = XmlPullUtil.valueTag(pp, "u"); + if (!"sf".equals(u)) + throw new RuntimeException("unknown usage: " + u); + final String ty = XmlPullUtil.valueTag(pp, "ty"); + final LocationType type; + if ("stop".equals(ty)) + type = LocationType.STATION; + else if ("poi".equals(ty)) + type = LocationType.POI; + else if ("loc".equals(ty)) + type = LocationType.COORD; + else if ("street".equals(ty)) + type = LocationType.ADDRESS; + else if ("singlehouse".equals(ty)) + type = LocationType.ADDRESS; + else + throw new RuntimeException("unknown type: " + ty); - while (XmlPullUtil.test(pp, "p")) { - XmlPullUtil.enter(pp, "p"); + XmlPullUtil.enter(pp, "r"); - final String name = normalizeLocationName(XmlPullUtil.valueTag(pp, "n")); - final String u = XmlPullUtil.valueTag(pp, "u"); - if (!"sf".equals(u)) - throw new RuntimeException("unknown usage: " + u); - final String ty = XmlPullUtil.valueTag(pp, "ty"); - final LocationType type; - if ("stop".equals(ty)) - type = LocationType.STATION; - else if ("poi".equals(ty)) - type = LocationType.POI; - else if ("loc".equals(ty)) - type = LocationType.COORD; - else if ("street".equals(ty)) - type = LocationType.ADDRESS; - else if ("singlehouse".equals(ty)) - type = LocationType.ADDRESS; - else - throw new RuntimeException("unknown type: " + ty); + final String id = XmlPullUtil.valueTag(pp, "id"); + XmlPullUtil.optValueTag(pp, "gid", null); + XmlPullUtil.valueTag(pp, "stateless"); + XmlPullUtil.valueTag(pp, "omc"); + final String place = normalizeLocationName(XmlPullUtil.optValueTag(pp, "pc", null)); + XmlPullUtil.valueTag(pp, "pid"); + final Point coord = parseCoord(XmlPullUtil.optValueTag(pp, "c", null)); - XmlPullUtil.enter(pp, "r"); + XmlPullUtil.skipExit(pp, "r"); - final String id = XmlPullUtil.valueTag(pp, "id"); - XmlPullUtil.optValueTag(pp, "gid", null); - XmlPullUtil.valueTag(pp, "stateless"); - XmlPullUtil.valueTag(pp, "omc"); - final String place = normalizeLocationName(XmlPullUtil.optValueTag(pp, "pc", null)); - XmlPullUtil.valueTag(pp, "pid"); - final Point coord = parseCoord(XmlPullUtil.optValueTag(pp, "c", null)); + final String qal = XmlPullUtil.optValueTag(pp, "qal", null); + final int quality = qal != null ? Integer.parseInt(qal) : 0; - XmlPullUtil.skipExit(pp, "r"); + XmlPullUtil.skipExit(pp, "p"); - final String qal = XmlPullUtil.optValueTag(pp, "qal", null); - final int quality = qal != null ? Integer.parseInt(qal) : 0; + final Location location = new Location(type, type == LocationType.STATION ? id : null, + coord, place, name); + final SuggestedLocation locationAndQuality = new SuggestedLocation(location, quality); + locations.add(locationAndQuality); + } - XmlPullUtil.skipExit(pp, "p"); + XmlPullUtil.skipExit(pp, "sf"); + } else { + XmlPullUtil.next(pp); + } - final Location location = new Location(type, type == LocationType.STATION ? id : null, coord, place, - name); - final SuggestedLocation locationAndQuality = new SuggestedLocation(location, quality); - locations.add(locationAndQuality); + result.set(new SuggestLocationsResult(header, locations)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - - XmlPullUtil.skipExit(pp, "sf"); - } else { - XmlPullUtil.next(pp); } + }; - return new SuggestLocationsResult(header, locations); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); + + return result.get(); } private StringBuilder xmlCoordRequestParameters(final EnumSet types, final int lat, final int lon, @@ -507,149 +512,151 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final int maxDistance, final int maxStations) throws IOException { final StringBuilder uri = new StringBuilder(coordEndpoint); final StringBuilder parameters = xmlCoordRequestParameters(types, lat, lon, maxDistance, maxStations); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterItdRequest(pp); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + XmlPullUtil.enter(pp, "itdCoordInfoRequest"); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterItdRequest(pp); + XmlPullUtil.enter(pp, "itdCoordInfo"); - XmlPullUtil.enter(pp, "itdCoordInfoRequest"); + XmlPullUtil.enter(pp, "coordInfoRequest"); + XmlPullUtil.skipExit(pp, "coordInfoRequest"); - XmlPullUtil.enter(pp, "itdCoordInfo"); + final List locations = new ArrayList(); - XmlPullUtil.enter(pp, "coordInfoRequest"); - XmlPullUtil.skipExit(pp, "coordInfoRequest"); + if (XmlPullUtil.test(pp, "coordInfoItemList")) { + XmlPullUtil.enter(pp, "coordInfoItemList"); - final List locations = new ArrayList(); + while (XmlPullUtil.test(pp, "coordInfoItem")) { + final String type = XmlPullUtil.attr(pp, "type"); + final LocationType locationType; + if ("STOP".equals(type)) + locationType = LocationType.STATION; + else if ("POI_POINT".equals(type)) + locationType = LocationType.POI; + else + throw new IllegalStateException("unknown type: " + type); - if (XmlPullUtil.test(pp, "coordInfoItemList")) { - XmlPullUtil.enter(pp, "coordInfoItemList"); + String id = XmlPullUtil.optAttr(pp, "stateless", null); + if (id == null) + id = XmlPullUtil.attr(pp, "id"); - while (XmlPullUtil.test(pp, "coordInfoItem")) { - final String type = XmlPullUtil.attr(pp, "type"); - final LocationType locationType; - if ("STOP".equals(type)) - locationType = LocationType.STATION; - else if ("POI_POINT".equals(type)) - locationType = LocationType.POI; - else - throw new IllegalStateException("unknown type: " + type); + final String name = normalizeLocationName(XmlPullUtil.optAttr(pp, "name", null)); + final String place = normalizeLocationName(XmlPullUtil.optAttr(pp, "locality", null)); - String id = XmlPullUtil.optAttr(pp, "stateless", null); - if (id == null) - id = XmlPullUtil.attr(pp, "id"); + XmlPullUtil.enter(pp, "coordInfoItem"); - final String name = normalizeLocationName(XmlPullUtil.optAttr(pp, "name", null)); - final String place = normalizeLocationName(XmlPullUtil.optAttr(pp, "locality", null)); + // FIXME this is always only one coordinate + final Point coord = processItdPathCoordinates(pp).get(0); - XmlPullUtil.enter(pp, "coordInfoItem"); + XmlPullUtil.skipExit(pp, "coordInfoItem"); - // FIXME this is always only one coordinate - final Point coord = processItdPathCoordinates(pp).get(0); + if (name != null) + locations.add(new Location(locationType, id, coord, place, name)); + } - XmlPullUtil.skipExit(pp, "coordInfoItem"); + XmlPullUtil.skipExit(pp, "coordInfoItemList"); + } - if (name != null) - locations.add(new Location(locationType, id, coord, place, name)); + result.set(new NearbyLocationsResult(header, locations)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - - XmlPullUtil.skipExit(pp, "coordInfoItemList"); } + }; - return new NearbyLocationsResult(header, locations); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); + + return result.get(); } protected NearbyLocationsResult mobileCoordRequest(final EnumSet types, final int lat, final int lon, final int maxDistance, final int maxStations) throws IOException { final StringBuilder uri = new StringBuilder(coordEndpoint); final StringBuilder parameters = xmlCoordRequestParameters(types, lat, lon, maxDistance, maxStations); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterEfa(pp); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + XmlPullUtil.enter(pp, "ci"); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterEfa(pp); + XmlPullUtil.enter(pp, "request"); + XmlPullUtil.skipExit(pp, "request"); - XmlPullUtil.enter(pp, "ci"); + final List stations = new ArrayList(); - XmlPullUtil.enter(pp, "request"); - XmlPullUtil.skipExit(pp, "request"); + if (XmlPullUtil.test(pp, "pis")) { + XmlPullUtil.enter(pp, "pis"); - final List stations = new ArrayList(); + while (XmlPullUtil.test(pp, "pi")) { + XmlPullUtil.enter(pp, "pi"); - if (XmlPullUtil.test(pp, "pis")) { - XmlPullUtil.enter(pp, "pis"); + final String name = normalizeLocationName(XmlPullUtil.optValueTag(pp, "de", null)); + final String type = XmlPullUtil.valueTag(pp, "ty"); + final LocationType locationType; + if ("STOP".equals(type)) + locationType = LocationType.STATION; + else if ("POI_POINT".equals(type)) + locationType = LocationType.POI; + else + throw new IllegalStateException("unknown type: " + type); - while (XmlPullUtil.test(pp, "pi")) { - XmlPullUtil.enter(pp, "pi"); + final String id = XmlPullUtil.valueTag(pp, "id"); + XmlPullUtil.valueTag(pp, "omc"); + XmlPullUtil.optValueTag(pp, "pid", null); + final String place = normalizeLocationName(XmlPullUtil.valueTag(pp, "locality")); + XmlPullUtil.valueTag(pp, "layer"); + XmlPullUtil.valueTag(pp, "gisID"); + XmlPullUtil.valueTag(pp, "ds"); + XmlPullUtil.valueTag(pp, "stateless"); + final Point coord = parseCoord(XmlPullUtil.valueTag(pp, "c")); - final String name = normalizeLocationName(XmlPullUtil.optValueTag(pp, "de", null)); - final String type = XmlPullUtil.valueTag(pp, "ty"); - final LocationType locationType; - if ("STOP".equals(type)) - locationType = LocationType.STATION; - else if ("POI_POINT".equals(type)) - locationType = LocationType.POI; - else - throw new IllegalStateException("unknown type: " + type); + final Location location; + if (name != null) + location = new Location(locationType, id, coord, place, name); + else + location = new Location(locationType, id, coord, null, place); + stations.add(location); - final String id = XmlPullUtil.valueTag(pp, "id"); - XmlPullUtil.valueTag(pp, "omc"); - XmlPullUtil.optValueTag(pp, "pid", null); - final String place = normalizeLocationName(XmlPullUtil.valueTag(pp, "locality")); - XmlPullUtil.valueTag(pp, "layer"); - XmlPullUtil.valueTag(pp, "gisID"); - XmlPullUtil.valueTag(pp, "ds"); - XmlPullUtil.valueTag(pp, "stateless"); - final Point coord = parseCoord(XmlPullUtil.valueTag(pp, "c")); + XmlPullUtil.skipExit(pp, "pi"); + } - final Location location; - if (name != null) - location = new Location(locationType, id, coord, place, name); - else - location = new Location(locationType, id, coord, null, place); - stations.add(location); + XmlPullUtil.skipExit(pp, "pis"); + } - XmlPullUtil.skipExit(pp, "pi"); + XmlPullUtil.skipExit(pp, "ci"); + + result.set(new NearbyLocationsResult(header, stations)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - - XmlPullUtil.skipExit(pp, "pis"); } + }; - XmlPullUtil.skipExit(pp, "ci"); + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); - return new NearbyLocationsResult(header, stations); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } @Override @@ -856,57 +863,60 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { parameters.append("&mergeDep=1"); parameters.append("&useAllStops=1"); parameters.append("&mode=direct"); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterItdRequest(pp); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + XmlPullUtil.enter(pp, "itdDepartureMonitorRequest"); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterItdRequest(pp); + final AtomicReference ownStation = new AtomicReference(); + final List stations = new ArrayList(); - XmlPullUtil.enter(pp, "itdDepartureMonitorRequest"); + final String nameState = processItdOdv(pp, "dm", new ProcessItdOdvCallback() { + @Override + public void location(final String nameState, final Location location, final int matchQuality) { + if (location.type == LocationType.STATION) { + if ("identified".equals(nameState)) + ownStation.set(location); + else if ("assigned".equals(nameState)) + stations.add(location); + } else { + throw new IllegalStateException("cannot handle: " + location.type); + } + } + }); - final AtomicReference ownStation = new AtomicReference(); - final List stations = new ArrayList(); - - final String nameState = processItdOdv(pp, "dm", new ProcessItdOdvCallback() { - @Override - public void location(final String nameState, final Location location, final int matchQuality) { - if (location.type == LocationType.STATION) { - if ("identified".equals(nameState)) - ownStation.set(location); - else if ("assigned".equals(nameState)) - stations.add(location); - } else { - throw new IllegalStateException("cannot handle: " + location.type); + if ("notidentified".equals(nameState)) { + result.set(new NearbyLocationsResult(header, NearbyLocationsResult.Status.INVALID_ID)); + return; } + + if (ownStation.get() != null && !stations.contains(ownStation)) + stations.add(ownStation.get()); + + if (maxLocations == 0 || maxLocations >= stations.size()) + result.set(new NearbyLocationsResult(header, stations)); + else + result.set(new NearbyLocationsResult(header, stations.subList(0, maxLocations))); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - }); + } + }; - if ("notidentified".equals(nameState)) - return new NearbyLocationsResult(header, NearbyLocationsResult.Status.INVALID_ID); + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); - if (ownStation.get() != null && !stations.contains(ownStation)) - stations.add(ownStation.get()); - - if (maxLocations == 0 || maxLocations >= stations.size()) - return new NearbyLocationsResult(header, stations); - else - return new NearbyLocationsResult(header, stations.subList(0, maxLocations)); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } private static final Pattern P_LINE_RE = Pattern.compile("RE ?\\d+"); @@ -1442,243 +1452,253 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final int maxDepartures, final boolean equivs) throws IOException { final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); final StringBuilder parameters = xsltDepartureMonitorRequestParameters(stationId, time, maxDepartures, equivs); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterItdRequest(pp); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + final QueryDeparturesResult r = new QueryDeparturesResult(header); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterItdRequest(pp); + XmlPullUtil.enter(pp, "itdDepartureMonitorRequest"); - final QueryDeparturesResult result = new QueryDeparturesResult(header); + XmlPullUtil.optSkip(pp, "itdMessage"); - XmlPullUtil.enter(pp, "itdDepartureMonitorRequest"); + final String nameState = processItdOdv(pp, "dm", new ProcessItdOdvCallback() { + @Override + public void location(final String nameState, final Location location, final int matchQuality) { + if (location.type == LocationType.STATION) + if (findStationDepartures(r.stationDepartures, location.id) == null) + r.stationDepartures.add(new StationDepartures(location, new LinkedList(), + new LinkedList())); + } + }); - XmlPullUtil.optSkip(pp, "itdMessage"); - - final String nameState = processItdOdv(pp, "dm", new ProcessItdOdvCallback() { - @Override - public void location(final String nameState, final Location location, final int matchQuality) { - if (location.type == LocationType.STATION) - if (findStationDepartures(result.stationDepartures, location.id) == null) - result.stationDepartures.add(new StationDepartures(location, new LinkedList(), - new LinkedList())); - } - }); - - if ("notidentified".equals(nameState) || "list".equals(nameState)) - return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION); - - XmlPullUtil.optSkip(pp, "itdDateTime"); - - XmlPullUtil.optSkip(pp, "itdDMDateTime"); - - XmlPullUtil.optSkip(pp, "itdDateRange"); - - XmlPullUtil.optSkip(pp, "itdTripOptions"); - - XmlPullUtil.optSkip(pp, "itdMessage"); - - final Calendar plannedDepartureTime = new GregorianCalendar(timeZone); - final Calendar predictedDepartureTime = new GregorianCalendar(timeZone); - - XmlPullUtil.require(pp, "itdServingLines"); - if (!pp.isEmptyElementTag()) { - XmlPullUtil.enter(pp, "itdServingLines"); - while (XmlPullUtil.test(pp, "itdServingLine")) { - final String assignedStopId = XmlPullUtil.optAttr(pp, "assignedStopID", null); - final String destinationName = normalizeLocationName(XmlPullUtil.optAttr(pp, "direction", null)); - final String destinationIdStr = XmlPullUtil.optAttr(pp, "destID", null); - final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null; - final Location destination; - if (destinationId != null || destinationName != null) - destination = new Location(destinationId != null ? LocationType.STATION : LocationType.ANY, - destinationId, null, destinationName); - else - destination = null; - - final LineDestination line = new LineDestination(processItdServingLine(pp), destination); - - StationDepartures assignedStationDepartures; - if (assignedStopId == null) - assignedStationDepartures = result.stationDepartures.get(0); - else - assignedStationDepartures = findStationDepartures(result.stationDepartures, assignedStopId); - - if (assignedStationDepartures == null) - assignedStationDepartures = new StationDepartures( - new Location(LocationType.STATION, assignedStopId), new LinkedList(), - new LinkedList()); - - final List assignedStationDeparturesLines = checkNotNull( - assignedStationDepartures.lines); - if (!assignedStationDeparturesLines.contains(line)) - assignedStationDeparturesLines.add(line); - } - XmlPullUtil.skipExit(pp, "itdServingLines"); - } else { - XmlPullUtil.next(pp); - } - - XmlPullUtil.require(pp, "itdDepartureList"); - if (!pp.isEmptyElementTag()) { - XmlPullUtil.enter(pp, "itdDepartureList"); - while (XmlPullUtil.test(pp, "itdDeparture")) { - final String assignedStopId = XmlPullUtil.attr(pp, "stopID"); - - StationDepartures assignedStationDepartures = findStationDepartures(result.stationDepartures, - assignedStopId); - if (assignedStationDepartures == null) { - final Point coord = processCoordAttr(pp); - - // final String name = normalizeLocationName(XmlPullUtil.attr(pp, "nameWO")); - - assignedStationDepartures = new StationDepartures( - new Location(LocationType.STATION, assignedStopId, coord), new LinkedList(), - new LinkedList()); + if ("notidentified".equals(nameState) || "list".equals(nameState)) { + result.set(new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION)); + return; } - final Position position = parsePosition(XmlPullUtil.optAttr(pp, "platformName", null)); + XmlPullUtil.optSkip(pp, "itdDateTime"); - XmlPullUtil.enter(pp, "itdDeparture"); + XmlPullUtil.optSkip(pp, "itdDMDateTime"); - XmlPullUtil.require(pp, "itdDateTime"); - plannedDepartureTime.clear(); - processItdDateTime(pp, plannedDepartureTime); + XmlPullUtil.optSkip(pp, "itdDateRange"); - predictedDepartureTime.clear(); - if (XmlPullUtil.test(pp, "itdRTDateTime")) - processItdDateTime(pp, predictedDepartureTime); + XmlPullUtil.optSkip(pp, "itdTripOptions"); - if (XmlPullUtil.test(pp, "itdFrequencyInfo")) + XmlPullUtil.optSkip(pp, "itdMessage"); + + final Calendar plannedDepartureTime = new GregorianCalendar(timeZone); + final Calendar predictedDepartureTime = new GregorianCalendar(timeZone); + + XmlPullUtil.require(pp, "itdServingLines"); + if (!pp.isEmptyElementTag()) { + XmlPullUtil.enter(pp, "itdServingLines"); + while (XmlPullUtil.test(pp, "itdServingLine")) { + final String assignedStopId = XmlPullUtil.optAttr(pp, "assignedStopID", null); + final String destinationName = normalizeLocationName( + XmlPullUtil.optAttr(pp, "direction", null)); + final String destinationIdStr = XmlPullUtil.optAttr(pp, "destID", null); + final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null; + final Location destination; + if (destinationId != null || destinationName != null) + destination = new Location( + destinationId != null ? LocationType.STATION : LocationType.ANY, destinationId, + null, destinationName); + else + destination = null; + + final LineDestination line = new LineDestination(processItdServingLine(pp), destination); + + StationDepartures assignedStationDepartures; + if (assignedStopId == null) + assignedStationDepartures = r.stationDepartures.get(0); + else + assignedStationDepartures = findStationDepartures(r.stationDepartures, assignedStopId); + + if (assignedStationDepartures == null) + assignedStationDepartures = new StationDepartures( + new Location(LocationType.STATION, assignedStopId), new LinkedList(), + new LinkedList()); + + final List assignedStationDeparturesLines = checkNotNull( + assignedStationDepartures.lines); + if (!assignedStationDeparturesLines.contains(line)) + assignedStationDeparturesLines.add(line); + } + XmlPullUtil.skipExit(pp, "itdServingLines"); + } else { XmlPullUtil.next(pp); + } - XmlPullUtil.require(pp, "itdServingLine"); - final boolean isRealtime = XmlPullUtil.attr(pp, "realtime").equals("1"); - final String destinationName = normalizeLocationName(XmlPullUtil.optAttr(pp, "direction", null)); - final String destinationIdStr = XmlPullUtil.optAttr(pp, "destID", null); - final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null; - final Location destination; - if (destinationId != null || destinationName != null) - destination = new Location(destinationId != null ? LocationType.STATION : LocationType.ANY, - destinationId, null, destinationName); - else - destination = null; - final Line line = processItdServingLine(pp); + XmlPullUtil.require(pp, "itdDepartureList"); + if (!pp.isEmptyElementTag()) { + XmlPullUtil.enter(pp, "itdDepartureList"); + while (XmlPullUtil.test(pp, "itdDeparture")) { + final String assignedStopId = XmlPullUtil.attr(pp, "stopID"); - if (isRealtime && !predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY)) - predictedDepartureTime.setTimeInMillis(plannedDepartureTime.getTimeInMillis()); + StationDepartures assignedStationDepartures = findStationDepartures(r.stationDepartures, + assignedStopId); + if (assignedStationDepartures == null) { + final Point coord = processCoordAttr(pp); - XmlPullUtil.skipExit(pp, "itdDeparture"); + // final String name = normalizeLocationName(XmlPullUtil.attr(pp, "nameWO")); - final Departure departure = new Departure(plannedDepartureTime.getTime(), - predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) ? predictedDepartureTime.getTime() - : null, - line, position, destination, null, null); - assignedStationDepartures.departures.add(departure); + assignedStationDepartures = new StationDepartures( + new Location(LocationType.STATION, assignedStopId, coord), + new LinkedList(), new LinkedList()); + } + + final Position position = parsePosition(XmlPullUtil.optAttr(pp, "platformName", null)); + + XmlPullUtil.enter(pp, "itdDeparture"); + + XmlPullUtil.require(pp, "itdDateTime"); + plannedDepartureTime.clear(); + processItdDateTime(pp, plannedDepartureTime); + + predictedDepartureTime.clear(); + if (XmlPullUtil.test(pp, "itdRTDateTime")) + processItdDateTime(pp, predictedDepartureTime); + + if (XmlPullUtil.test(pp, "itdFrequencyInfo")) + XmlPullUtil.next(pp); + + XmlPullUtil.require(pp, "itdServingLine"); + final boolean isRealtime = XmlPullUtil.attr(pp, "realtime").equals("1"); + final String destinationName = normalizeLocationName( + XmlPullUtil.optAttr(pp, "direction", null)); + final String destinationIdStr = XmlPullUtil.optAttr(pp, "destID", null); + final String destinationId = !"-1".equals(destinationIdStr) ? destinationIdStr : null; + final Location destination; + if (destinationId != null || destinationName != null) + destination = new Location( + destinationId != null ? LocationType.STATION : LocationType.ANY, destinationId, + null, destinationName); + else + destination = null; + final Line line = processItdServingLine(pp); + + if (isRealtime && !predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY)) + predictedDepartureTime.setTimeInMillis(plannedDepartureTime.getTimeInMillis()); + + XmlPullUtil.skipExit(pp, "itdDeparture"); + + final Departure departure = new Departure(plannedDepartureTime.getTime(), + predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) + ? predictedDepartureTime.getTime() : null, + line, position, destination, null, null); + assignedStationDepartures.departures.add(departure); + } + + XmlPullUtil.skipExit(pp, "itdDepartureList"); + } else { + XmlPullUtil.next(pp); + } + + result.set(r); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - - XmlPullUtil.skipExit(pp, "itdDepartureList"); - } else { - XmlPullUtil.next(pp); } + }; - return result; - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); + + return result.get(); } protected QueryDeparturesResult queryDeparturesMobile(final String stationId, final @Nullable Date time, final int maxDepartures, final boolean equivs) throws IOException { final StringBuilder uri = new StringBuilder(departureMonitorEndpoint); final StringBuilder parameters = xsltDepartureMonitorRequestParameters(stationId, time, maxDepartures, equivs); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.byteStream(), null); // Read encoding from XML declaration + final ResultHeader header = enterEfa(pp); + final QueryDeparturesResult r = new QueryDeparturesResult(header); - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpReferer); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpReferer); - firstChars = HttpClient.peekFirstChars(is); + XmlPullUtil.require(pp, "dps"); + if (!pp.isEmptyElementTag()) { + XmlPullUtil.enter(pp, "dps"); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - final ResultHeader header = enterEfa(pp); - final QueryDeparturesResult result = new QueryDeparturesResult(header); + final Calendar plannedDepartureTime = new GregorianCalendar(timeZone); + final Calendar predictedDepartureTime = new GregorianCalendar(timeZone); - XmlPullUtil.require(pp, "dps"); - if (!pp.isEmptyElementTag()) { - XmlPullUtil.enter(pp, "dps"); + while (XmlPullUtil.test(pp, "dp")) { + XmlPullUtil.enter(pp, "dp"); - final Calendar plannedDepartureTime = new GregorianCalendar(timeZone); - final Calendar predictedDepartureTime = new GregorianCalendar(timeZone); + // misc + /* final String stationName = */normalizeLocationName(XmlPullUtil.valueTag(pp, "n")); + /* final boolean isRealtime = */XmlPullUtil.valueTag(pp, "realtime").equals("1"); - while (XmlPullUtil.test(pp, "dp")) { - XmlPullUtil.enter(pp, "dp"); + XmlPullUtil.optSkip(pp, "dt"); - // misc - /* final String stationName = */normalizeLocationName(XmlPullUtil.valueTag(pp, "n")); - /* final boolean isRealtime = */XmlPullUtil.valueTag(pp, "realtime").equals("1"); + // time + parseMobileSt(pp, plannedDepartureTime, predictedDepartureTime); - XmlPullUtil.optSkip(pp, "dt"); + final LineDestination lineDestination = parseMobileM(pp, true); - // time - parseMobileSt(pp, plannedDepartureTime, predictedDepartureTime); + XmlPullUtil.enter(pp, "r"); + final String assignedId = XmlPullUtil.valueTag(pp, "id"); + XmlPullUtil.valueTag(pp, "a"); + final Position position = parsePosition(XmlPullUtil.optValueTag(pp, "pl", null)); + XmlPullUtil.skipExit(pp, "r"); - final LineDestination lineDestination = parseMobileM(pp, true); + /* final Point positionCoordinate = */parseCoord(XmlPullUtil.optValueTag(pp, "c", null)); - XmlPullUtil.enter(pp, "r"); - final String assignedId = XmlPullUtil.valueTag(pp, "id"); - XmlPullUtil.valueTag(pp, "a"); - final Position position = parsePosition(XmlPullUtil.optValueTag(pp, "pl", null)); - XmlPullUtil.skipExit(pp, "r"); + // TODO messages - /* final Point positionCoordinate = */parseCoord(XmlPullUtil.optValueTag(pp, "c", null)); + StationDepartures stationDepartures = findStationDepartures(r.stationDepartures, + assignedId); + if (stationDepartures == null) { + stationDepartures = new StationDepartures( + new Location(LocationType.STATION, assignedId), + new ArrayList(maxDepartures), null); + r.stationDepartures.add(stationDepartures); + } - // TODO messages + stationDepartures.departures.add(new Departure(plannedDepartureTime.getTime(), + predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) + ? predictedDepartureTime.getTime() : null, + lineDestination.line, position, lineDestination.destination, null, null)); - StationDepartures stationDepartures = findStationDepartures(result.stationDepartures, assignedId); - if (stationDepartures == null) { - stationDepartures = new StationDepartures(new Location(LocationType.STATION, assignedId), - new ArrayList(maxDepartures), null); - result.stationDepartures.add(stationDepartures); + XmlPullUtil.skipExit(pp, "dp"); + } + + XmlPullUtil.skipExit(pp, "dps"); + + result.set(r); + } else { + result.set(new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION)); } - - stationDepartures.departures.add(new Departure( - plannedDepartureTime.getTime(), predictedDepartureTime.isSet(Calendar.HOUR_OF_DAY) - ? predictedDepartureTime.getTime() : null, - lineDestination.line, position, lineDestination.destination, null, null)); - - XmlPullUtil.skipExit(pp, "dp"); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - - XmlPullUtil.skipExit(pp, "dps"); - - return result; - } else { - return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION); } - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + }; + + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpReferer); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, httpReferer); + + return result.get(); } private static final Pattern P_MOBILE_M_SYMBOL = Pattern.compile("([^\\s]*)\\s+([^\\s]*)"); @@ -2032,27 +2052,29 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(tripEndpoint); final String parameters = xsltTripRequestParameters(from, via, to, date, dep, products, optimize, walkSpeed, accessibility, options); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + result.set(queryTrips(uri.toString(), body.byteStream())); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } catch (final RuntimeException x) { + throw new RuntimeException("uncategorized problem while processing " + uri, x); + } + } + }; - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpRefererTrip); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpRefererTrip); - firstChars = HttpClient.peekFirstChars(is); + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpRefererTrip); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, + httpRefererTrip); - return queryTrips(uri.toString(), is); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } catch (final RuntimeException x) { - throw new RuntimeException("uncategorized problem while processing " + uri, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } protected QueryTripsResult queryTripsMobile(final Location from, final @Nullable Location via, final Location to, @@ -2062,27 +2084,29 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(tripEndpoint); final String parameters = xsltTripRequestParameters(from, via, to, date, dep, products, optimize, walkSpeed, accessibility, options); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + result.set(queryTripsMobile(uri.toString(), from, via, to, body.byteStream())); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } catch (final RuntimeException x) { + throw new RuntimeException("uncategorized problem while processing " + uri, x); + } + } + }; - try { - if (httpPost) - is = httpClient.getInputStream(uri.toString(), parameters.substring(1), - "application/x-www-form-urlencoded", null, httpRefererTrip); - else - is = httpClient.getInputStream(uri.append(parameters).toString(), null, httpRefererTrip); - firstChars = HttpClient.peekFirstChars(is); + if (httpPost) + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), parameters.substring(1), + "application/x-www-form-urlencoded", null, httpRefererTrip); + else + httpClient.getInputStream(callback, HttpUrl.parse(uri.append(parameters).toString()), null, + httpRefererTrip); - return queryTripsMobile(uri.toString(), from, via, to, is); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } catch (final RuntimeException x) { - throw new RuntimeException("uncategorized problem while processing " + uri, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } @Override @@ -2091,23 +2115,24 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final String commandUri = context.context; final StringBuilder uri = new StringBuilder(commandUri); uri.append("&command=").append(later ? "tripNext" : "tripPrev"); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + result.set(queryTrips(uri.toString(), body.byteStream())); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } catch (final RuntimeException x) { + throw new RuntimeException("uncategorized problem while processing " + uri, x); + } + } + }; - try { - is = httpClient.getInputStream(uri.toString(), null, httpRefererTrip); - firstChars = HttpClient.peekFirstChars(is); + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), null, httpRefererTrip); - return queryTrips(uri.toString(), is); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } catch (final RuntimeException x) { - throw new RuntimeException("uncategorized problem while processing " + uri, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } protected QueryTripsResult queryMoreTripsMobile(final QueryTripsContext contextObj, final boolean later) @@ -2116,30 +2141,30 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { final String commandUri = context.context; final StringBuilder uri = new StringBuilder(commandUri); uri.append("&command=").append(later ? "tripNext" : "tripPrev"); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + result.set(queryTripsMobile(uri.toString(), null, null, null, body.byteStream())); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } catch (final RuntimeException x) { + throw new RuntimeException("uncategorized problem while processing " + uri, x); + } + } + }; - try { - is = httpClient.getInputStream(uri.toString(), null, httpRefererTrip); - firstChars = HttpClient.peekFirstChars(is); - is.mark(512); + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString()), null, httpRefererTrip); - return queryTripsMobile(uri.toString(), null, null, null, is); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } catch (final RuntimeException x) { - throw new RuntimeException("uncategorized problem while processing " + uri, x); - } finally { - if (is != null) - is.close(); - } + return result.get(); } private QueryTripsResult queryTrips(final String uri, final InputStream is) throws XmlPullParserException, IOException { final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); + pp.setInput(is, null); // Read encoding from XML declaration final ResultHeader header = enterItdRequest(pp); final Object context = header.context; @@ -2659,7 +2684,7 @@ public abstract class AbstractEfaProvider extends AbstractNetworkProvider { private QueryTripsResult queryTripsMobile(final String uri, final Location from, final @Nullable Location via, final Location to, final InputStream is) throws XmlPullParserException, IOException { final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); + pp.setInput(is, null); // Read encoding from XML declaration final ResultHeader header = enterEfa(pp); final Calendar plannedTimeCal = new GregorianCalendar(timeZone); diff --git a/enabler/src/de/schildbach/pte/AbstractHafasProvider.java b/enabler/src/de/schildbach/pte/AbstractHafasProvider.java index 1c391ecd..4adb11cd 100644 --- a/enabler/src/de/schildbach/pte/AbstractHafasProvider.java +++ b/enabler/src/de/schildbach/pte/AbstractHafasProvider.java @@ -27,7 +27,6 @@ import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Calendar; @@ -44,8 +43,10 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; import javax.annotation.Nullable; @@ -89,6 +90,9 @@ import de.schildbach.pte.util.ParserUtils; import de.schildbach.pte.util.StringReplaceReader; import de.schildbach.pte.util.XmlPullUtil; +import okhttp3.HttpUrl; +import okhttp3.ResponseBody; + /** * @author Andreas Schildbach */ @@ -448,7 +452,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { private static final Pattern P_AJAX_GET_STOPS_ID = Pattern.compile(".*?@L=0*(\\d+)@.*?"); protected final SuggestLocationsResult jsonGetStops(final String uri) throws IOException { - final CharSequence page = httpClient.get(uri, jsonGetStopsEncoding); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), jsonGetStopsEncoding); final Matcher mJson = P_AJAX_GET_STOPS_JSON.matcher(page); if (mJson.matches()) { @@ -565,230 +569,240 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { protected final QueryDeparturesResult xmlStationBoard(final String uri, final String stationId) throws IOException { final String normalizedStationId = normalizeStationId(stationId); + final AtomicReference result = new AtomicReference(); - StringReplaceReader reader = null; - String firstChars = null; + httpClient.getInputStream(new HttpClient.Callback() { - try { - // work around unparsable XML - final InputStream is = httpClient.getInputStream(uri); - firstChars = HttpClient.peekFirstChars(is); - reader = new StringReplaceReader(new InputStreamReader(is, Charsets.ISO_8859_1), " & ", " & "); - reader.replace("", " "); - reader.replace("", " "); - reader.replace("", " "); - reader.replace("", " "); - reader.replace("", " "); - reader.replace("", " "); - reader.replace("
", " "); - reader.replace(" ->", " →"); // right arrow - reader.replace(" <-", " ←"); // left arrow - reader.replace(" <> ", " ↔ "); // left-right arrow - addCustomReplaces(reader); + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + StringReplaceReader reader = null; + String firstChars = null; - final XmlPullParserFactory factory = XmlPullParserFactory - .newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); - final XmlPullParser pp = factory.newPullParser(); - pp.setInput(reader); + // work around unparsable XML + reader = new StringReplaceReader(body.charStream(), " & ", " & "); + reader.replace("", " "); + reader.replace("", " "); + reader.replace("", " "); + reader.replace("", " "); + reader.replace("", " "); + reader.replace("", " "); + reader.replace("
", " "); + reader.replace(" ->", " →"); // right arrow + reader.replace(" <-", " ←"); // left arrow + reader.replace(" <> ", " ↔ "); // left-right arrow + addCustomReplaces(reader); - pp.nextTag(); + try { + final XmlPullParserFactory factory = XmlPullParserFactory + .newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); + final XmlPullParser pp = factory.newPullParser(); + pp.setInput(reader); - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); - final QueryDeparturesResult result = new QueryDeparturesResult(header); + pp.nextTag(); - if (XmlPullUtil.test(pp, "Err")) { - final String code = XmlPullUtil.attr(pp, "code"); - final String text = XmlPullUtil.attr(pp, "text"); + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); + final QueryDeparturesResult r = new QueryDeparturesResult(header); - if (code.equals("H730")) // Your input is not valid - return new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION); - if (code.equals("H890")) { - result.stationDepartures - .add(new StationDepartures(new Location(LocationType.STATION, normalizedStationId), - Collections. emptyList(), null)); - return result; - } - throw new IllegalArgumentException("unknown error " + code + ", " + text); - } + if (XmlPullUtil.test(pp, "Err")) { + final String code = XmlPullUtil.attr(pp, "code"); + final String text = XmlPullUtil.attr(pp, "text"); - String[] stationPlaceAndName = null; + if (code.equals("H730")) { + result.set(new QueryDeparturesResult(header, QueryDeparturesResult.Status.INVALID_STATION)); + return; + } + if (code.equals("H890")) { + r.stationDepartures + .add(new StationDepartures(new Location(LocationType.STATION, normalizedStationId), + Collections. emptyList(), null)); + result.set(r); + return; + } + throw new IllegalArgumentException("unknown error " + code + ", " + text); + } - if (stationBoardHasStationTable) - XmlPullUtil.enter(pp, "StationTable"); - else - checkState(!XmlPullUtil.test(pp, "StationTable")); + String[] stationPlaceAndName = null; - if (stationBoardHasLocation) { - XmlPullUtil.require(pp, "St"); - - final String evaId = XmlPullUtil.attr(pp, "evaId"); - if (evaId != null) { - if (!evaId.equals(normalizedStationId)) - throw new IllegalStateException("stationId: " + normalizedStationId + ", evaId: " + evaId); - - final String name = XmlPullUtil.attr(pp, "name"); - if (name != null) - stationPlaceAndName = splitStationName(name.trim()); - } - XmlPullUtil.requireSkip(pp, "St"); - } else { - checkState(!XmlPullUtil.test(pp, "St")); - } - - while (XmlPullUtil.test(pp, "Journey")) { - final String fpTime = XmlPullUtil.attr(pp, "fpTime"); - final String fpDate = XmlPullUtil.attr(pp, "fpDate"); - final String delay = XmlPullUtil.attr(pp, "delay"); - final String eDelay = XmlPullUtil.optAttr(pp, "e_delay", null); - final String platform = XmlPullUtil.optAttr(pp, "platform", null); - // TODO newpl - final String targetLoc = XmlPullUtil.optAttr(pp, "targetLoc", null); - // TODO hafasname - final String dirnr = XmlPullUtil.optAttr(pp, "dirnr", null); - final String prod = XmlPullUtil.attr(pp, "prod"); - final String classStr = XmlPullUtil.optAttr(pp, "class", null); - final String dir = XmlPullUtil.optAttr(pp, "dir", null); - final String capacityStr = XmlPullUtil.optAttr(pp, "capacity", null); - final String depStation = XmlPullUtil.optAttr(pp, "depStation", null); - final String delayReason = XmlPullUtil.optAttr(pp, "delayReason", null); - // TODO is_reachable - // TODO disableTrainInfo - // TODO lineFG/lineBG (ZVV) - final String administration = normalizeLineAdministration( - XmlPullUtil.optAttr(pp, "administration", null)); - - if (!"cancel".equals(delay) && !"cancel".equals(eDelay)) { - final Calendar plannedTime = new GregorianCalendar(timeZone); - plannedTime.clear(); - ParserUtils.parseEuropeanTime(plannedTime, fpTime); - if (fpDate.length() == 8) - ParserUtils.parseGermanDate(plannedTime, fpDate); - else if (fpDate.length() == 10) - ParserUtils.parseIsoDate(plannedTime, fpDate); + if (stationBoardHasStationTable) + XmlPullUtil.enter(pp, "StationTable"); else - throw new IllegalStateException("cannot parse: '" + fpDate + "'"); + checkState(!XmlPullUtil.test(pp, "StationTable")); - final Calendar predictedTime; - if (eDelay != null) { - predictedTime = new GregorianCalendar(timeZone); - predictedTime.setTimeInMillis(plannedTime.getTimeInMillis()); - predictedTime.add(Calendar.MINUTE, Integer.parseInt(eDelay)); - } else if (delay != null) { - final Matcher m = P_XML_STATION_BOARD_DELAY.matcher(delay); - if (m.matches()) { - if (m.group(1) != null) { + if (stationBoardHasLocation) { + XmlPullUtil.require(pp, "St"); + + final String evaId = XmlPullUtil.attr(pp, "evaId"); + if (evaId != null) { + if (!evaId.equals(normalizedStationId)) + throw new IllegalStateException( + "stationId: " + normalizedStationId + ", evaId: " + evaId); + + final String name = XmlPullUtil.attr(pp, "name"); + if (name != null) + stationPlaceAndName = splitStationName(name.trim()); + } + XmlPullUtil.requireSkip(pp, "St"); + } else { + checkState(!XmlPullUtil.test(pp, "St")); + } + + while (XmlPullUtil.test(pp, "Journey")) { + final String fpTime = XmlPullUtil.attr(pp, "fpTime"); + final String fpDate = XmlPullUtil.attr(pp, "fpDate"); + final String delay = XmlPullUtil.attr(pp, "delay"); + final String eDelay = XmlPullUtil.optAttr(pp, "e_delay", null); + final String platform = XmlPullUtil.optAttr(pp, "platform", null); + // TODO newpl + final String targetLoc = XmlPullUtil.optAttr(pp, "targetLoc", null); + // TODO hafasname + final String dirnr = XmlPullUtil.optAttr(pp, "dirnr", null); + final String prod = XmlPullUtil.attr(pp, "prod"); + final String classStr = XmlPullUtil.optAttr(pp, "class", null); + final String dir = XmlPullUtil.optAttr(pp, "dir", null); + final String capacityStr = XmlPullUtil.optAttr(pp, "capacity", null); + final String depStation = XmlPullUtil.optAttr(pp, "depStation", null); + final String delayReason = XmlPullUtil.optAttr(pp, "delayReason", null); + // TODO is_reachable + // TODO disableTrainInfo + // TODO lineFG/lineBG (ZVV) + final String administration = normalizeLineAdministration( + XmlPullUtil.optAttr(pp, "administration", null)); + + if (!"cancel".equals(delay) && !"cancel".equals(eDelay)) { + final Calendar plannedTime = new GregorianCalendar(timeZone); + plannedTime.clear(); + ParserUtils.parseEuropeanTime(plannedTime, fpTime); + if (fpDate.length() == 8) + ParserUtils.parseGermanDate(plannedTime, fpDate); + else if (fpDate.length() == 10) + ParserUtils.parseIsoDate(plannedTime, fpDate); + else + throw new IllegalStateException("cannot parse: '" + fpDate + "'"); + + final Calendar predictedTime; + if (eDelay != null) { predictedTime = new GregorianCalendar(timeZone); predictedTime.setTimeInMillis(plannedTime.getTimeInMillis()); - predictedTime.add(Calendar.MINUTE, Integer.parseInt(m.group(1))); + predictedTime.add(Calendar.MINUTE, Integer.parseInt(eDelay)); + } else if (delay != null) { + final Matcher m = P_XML_STATION_BOARD_DELAY.matcher(delay); + if (m.matches()) { + if (m.group(1) != null) { + predictedTime = new GregorianCalendar(timeZone); + predictedTime.setTimeInMillis(plannedTime.getTimeInMillis()); + predictedTime.add(Calendar.MINUTE, Integer.parseInt(m.group(1))); + } else { + predictedTime = null; + } + } else { + throw new RuntimeException("cannot parse delay: '" + delay + "'"); + } } else { predictedTime = null; } - } else { - throw new RuntimeException("cannot parse delay: '" + delay + "'"); + + final Position position = parsePosition(ParserUtils.resolveEntities(platform)); + + final String destinationName; + if (dir != null) + destinationName = dir.trim(); + else if (targetLoc != null) + destinationName = targetLoc.trim(); + else + destinationName = null; + + final Location destination; + if (dirnr != null) { + final String[] destinationPlaceAndName = splitStationName(destinationName); + destination = new Location(LocationType.STATION, dirnr, destinationPlaceAndName[0], + destinationPlaceAndName[1]); + } else { + destination = new Location(LocationType.ANY, null, null, destinationName); + } + + final Line prodLine = parseLineAndType(prod); + final Line line; + if (classStr != null) { + final Product product = intToProduct(Integer.parseInt(classStr)); + if (product == null) + throw new IllegalArgumentException(); + // could check for type consistency here + final Set attrs = prodLine.attrs; + if (attrs != null) + line = newLine(administration, product, prodLine.label, null, + attrs.toArray(new Line.Attr[0])); + else + line = newLine(administration, product, prodLine.label, null); + } else { + final Set attrs = prodLine.attrs; + if (attrs != null) + line = newLine(administration, prodLine.product, prodLine.label, null, + attrs.toArray(new Line.Attr[0])); + else + line = newLine(administration, prodLine.product, prodLine.label, null); + } + + final int[] capacity; + if (capacityStr != null && !"0|0".equals(capacityStr)) { + final String[] capacityParts = capacityStr.split("\\|"); + capacity = new int[] { Integer.parseInt(capacityParts[0]), + Integer.parseInt(capacityParts[1]) }; + } else { + capacity = null; + } + + final String message; + if (delayReason != null) { + final String msg = delayReason.trim(); + message = msg.length() > 0 ? msg : null; + } else { + message = null; + } + + final Departure departure = new Departure(plannedTime.getTime(), + predictedTime != null ? predictedTime.getTime() : null, line, position, destination, + capacity, message); + + final Location location; + if (!stationBoardCanDoEquivs || depStation == null) { + location = new Location(LocationType.STATION, normalizedStationId, + stationPlaceAndName != null ? stationPlaceAndName[0] : null, + stationPlaceAndName != null ? stationPlaceAndName[1] : null); + } else { + final String[] depPlaceAndName = splitStationName(depStation); + location = new Location(LocationType.STATION, null, depPlaceAndName[0], + depPlaceAndName[1]); + } + + StationDepartures stationDepartures = findStationDepartures(r.stationDepartures, location); + if (stationDepartures == null) { + stationDepartures = new StationDepartures(location, new ArrayList(8), null); + r.stationDepartures.add(stationDepartures); + } + + stationDepartures.departures.add(departure); } - } else { - predictedTime = null; + + XmlPullUtil.requireSkip(pp, "Journey"); } - final Position position = parsePosition(ParserUtils.resolveEntities(platform)); + if (stationBoardHasStationTable) + XmlPullUtil.exit(pp, "StationTable"); - final String destinationName; - if (dir != null) - destinationName = dir.trim(); - else if (targetLoc != null) - destinationName = targetLoc.trim(); - else - destinationName = null; + XmlPullUtil.requireEndDocument(pp); - final Location destination; - if (dirnr != null) { - final String[] destinationPlaceAndName = splitStationName(destinationName); - destination = new Location(LocationType.STATION, dirnr, destinationPlaceAndName[0], - destinationPlaceAndName[1]); - } else { - destination = new Location(LocationType.ANY, null, null, destinationName); - } + // sort departures + for (final StationDepartures stationDepartures : r.stationDepartures) + Collections.sort(stationDepartures.departures, Departure.TIME_COMPARATOR); - final Line prodLine = parseLineAndType(prod); - final Line line; - if (classStr != null) { - final Product product = intToProduct(Integer.parseInt(classStr)); - if (product == null) - throw new IllegalArgumentException(); - // could check for type consistency here - final Set attrs = prodLine.attrs; - if (attrs != null) - line = newLine(administration, product, prodLine.label, null, - attrs.toArray(new Line.Attr[0])); - else - line = newLine(administration, product, prodLine.label, null); - } else { - final Set attrs = prodLine.attrs; - if (attrs != null) - line = newLine(administration, prodLine.product, prodLine.label, null, - attrs.toArray(new Line.Attr[0])); - else - line = newLine(administration, prodLine.product, prodLine.label, null); - } - - final int[] capacity; - if (capacityStr != null && !"0|0".equals(capacityStr)) { - final String[] capacityParts = capacityStr.split("\\|"); - capacity = new int[] { Integer.parseInt(capacityParts[0]), Integer.parseInt(capacityParts[1]) }; - } else { - capacity = null; - } - - final String message; - if (delayReason != null) { - final String msg = delayReason.trim(); - message = msg.length() > 0 ? msg : null; - } else { - message = null; - } - - final Departure departure = new Departure(plannedTime.getTime(), - predictedTime != null ? predictedTime.getTime() : null, line, position, destination, - capacity, message); - - final Location location; - if (!stationBoardCanDoEquivs || depStation == null) { - location = new Location(LocationType.STATION, normalizedStationId, - stationPlaceAndName != null ? stationPlaceAndName[0] : null, - stationPlaceAndName != null ? stationPlaceAndName[1] : null); - } else { - final String[] depPlaceAndName = splitStationName(depStation); - location = new Location(LocationType.STATION, null, depPlaceAndName[0], depPlaceAndName[1]); - } - - StationDepartures stationDepartures = findStationDepartures(result.stationDepartures, location); - if (stationDepartures == null) { - stationDepartures = new StationDepartures(location, new ArrayList(8), null); - result.stationDepartures.add(stationDepartures); - } - - stationDepartures.departures.add(departure); + result.set(r); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + firstChars, x); } - - XmlPullUtil.requireSkip(pp, "Journey"); } + }, HttpUrl.parse(uri)); - if (stationBoardHasStationTable) - XmlPullUtil.exit(pp, "StationTable"); - - XmlPullUtil.requireEndDocument(pp); - - // sort departures - for (final StationDepartures stationDepartures : result.stationDepartures) - Collections.sort(stationDepartures.departures, Departure.TIME_COMPARATOR); - - return result; - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (reader != null) - reader.close(); - } + return result.get(); } private StationDepartures findStationDepartures(final List stationDepartures, @@ -813,7 +827,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { false); final String uri = checkNotNull(mgateEndpoint); - final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -878,7 +892,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { false); final String uri = checkNotNull(mgateEndpoint); - final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -977,7 +991,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { true); final String uri = checkNotNull(mgateEndpoint); - final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -1058,7 +1072,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { false); final String uri = checkNotNull(mgateEndpoint); - final CharSequence page = httpClient.get(uri, request, "application/json", Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), request, "application/json", Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -1466,342 +1480,407 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { final CharSequence conReq, final Location from, final @Nullable Location via, final Location to) throws IOException { final String request = wrapReqC(conReq, null); + final String endpoint = extXmlEndpoint != null ? extXmlEndpoint : queryEndpoint; + final AtomicReference result = new AtomicReference(); + httpClient.getInputStream(new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParserFactory factory = XmlPullParserFactory + .newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); + final XmlPullParser pp = factory.newPullParser(); + pp.setInput(body.charStream()); - Reader reader = null; - String firstChars = null; + XmlPullUtil.require(pp, "ResC"); + final String product = XmlPullUtil.attr(pp, "prod").split(" ")[0]; + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT, product, 0, null); + XmlPullUtil.enter(pp, "ResC"); - try { - final String endpoint = extXmlEndpoint != null ? extXmlEndpoint : queryEndpoint; - final InputStream is = httpClient.getInputStream(endpoint, request, "application/xml", null, null); - firstChars = HttpClient.peekFirstChars(is); - reader = new InputStreamReader(is, Charsets.ISO_8859_1); + if (XmlPullUtil.test(pp, "Err")) { + final String code = XmlPullUtil.attr(pp, "code"); + if (code.equals("I3")) { + result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE)); + return; + } + if (code.equals("F1")) { + result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN)); + return; + } + throw new IllegalStateException("error " + code + " " + XmlPullUtil.attr(pp, "text")); + } - final XmlPullParserFactory factory = XmlPullParserFactory - .newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); - final XmlPullParser pp = factory.newPullParser(); - pp.setInput(reader); + XmlPullUtil.enter(pp, "ConRes"); - XmlPullUtil.require(pp, "ResC"); - final String product = XmlPullUtil.attr(pp, "prod").split(" ")[0]; - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT, product, 0, null); - XmlPullUtil.enter(pp, "ResC"); + if (XmlPullUtil.test(pp, "Err")) { + final String code = XmlPullUtil.attr(pp, "code"); + log.debug("Hafas error: {}", code); + if (code.equals("K9260")) { + // Unknown departure station + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM)); + return; + } + if (code.equals("K9280")) { + // Unknown intermediate station + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA)); + return; + } + if (code.equals("K9300")) { + // Unknown arrival station + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO)); + return; + } + if (code.equals("K9360")) { + // Date outside of the timetable period + result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE)); + return; + } + if (code.equals("K9380")) { + // Dep./Arr./Intermed. or equivalent station defined more than once + result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE)); + return; + } + if (code.equals("K895")) { + // Departure/Arrival are too near + result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE)); + return; + } + if (code.equals("K9220")) { + // Nearby to the given address stations could not be found + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS)); + return; + } + if (code.equals("K9240")) { + // Internal error + result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN)); + return; + } + if (code.equals("K890")) { + // No connections found + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } + if (code.equals("K891")) { + // No route found (try entering an intermediate station) + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } + if (code.equals("K899")) { + // An error occurred + result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN)); + return; + } + if (code.equals("K1:890")) { + // Unsuccessful or incomplete search (direction: forward) + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } + if (code.equals("K2:890")) { + // Unsuccessful or incomplete search (direction: backward) + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } + throw new IllegalStateException("error " + code + " " + XmlPullUtil.attr(pp, "text")); + } - if (XmlPullUtil.test(pp, "Err")) { - final String code = XmlPullUtil.attr(pp, "code"); - if (code.equals("I3")) // Input: date outside of the timetable period - return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE); - if (code.equals("F1")) // Spool: Error reading the spoolfile - return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN); - throw new IllegalStateException("error " + code + " " + XmlPullUtil.attr(pp, "text")); - } + // H9380 Dep./Arr./Intermed. or equivalent stations defined more than once + // H9360 Error in data field + // H9320 The input is incorrect or incomplete + // H9300 Unknown arrival station + // H9280 Unknown intermediate station + // H9260 Unknown departure station + // H9250 Part inquiry interrupted + // H9240 Unsuccessful search + // H9230 An internal error occurred + // H9220 Nearby to the given address stations could not be found + // H900 Unsuccessful or incomplete search (timetable change) + // H892 Inquiry too complex (try entering less intermediate stations) + // H891 No route found (try entering an intermediate station) + // H890 Unsuccessful search. + // H500 Because of too many trains the connection is not complete + // H460 One or more stops are passed through multiple times. + // H455 Prolonged stop + // H410 Display may be incomplete due to change of timetable + // H390 Departure/Arrival replaced by an equivalent station + // H895 Departure/Arrival are too near + // H899 Unsuccessful or incomplete search (timetable change - XmlPullUtil.enter(pp, "ConRes"); - - if (XmlPullUtil.test(pp, "Err")) { - final String code = XmlPullUtil.attr(pp, "code"); - log.debug("Hafas error: {}", code); - if (code.equals("K9260")) // Unknown departure station - return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM); - if (code.equals("K9280")) // Unknown intermediate station - return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA); - if (code.equals("K9300")) // Unknown arrival station - return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO); - if (code.equals("K9360")) // Date outside of the timetable period - return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE); - if (code.equals("K9380")) // Dep./Arr./Intermed. or equivalent station defined more that once - return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE); - if (code.equals("K895")) // Departure/Arrival are too near - return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE); - if (code.equals("K9220")) // Nearby to the given address stations could not be found - return new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS); - if (code.equals("K9240")) // Internal error - return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN); - if (code.equals("K890")) // No connections found - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - if (code.equals("K891")) // No route found (try entering an intermediate station) - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - if (code.equals("K899")) // An error occurred - return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN); - if (code.equals("K1:890")) // Unsuccessful or incomplete search (direction: forward) - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - if (code.equals("K2:890")) // Unsuccessful or incomplete search (direction: backward) - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - throw new IllegalStateException("error " + code + " " + XmlPullUtil.attr(pp, "text")); - } - - final String c = XmlPullUtil.optValueTag(pp, "ConResCtxt", null); - final Context context; - if (previousContext == null) - context = new Context(c, c, 0); - else if (later) - context = new Context(c, previousContext.earlierContext, previousContext.sequence + 1); - else - context = new Context(previousContext.laterContext, c, previousContext.sequence + 1); - - XmlPullUtil.enter(pp, "ConnectionList"); - - final List trips = new ArrayList(); - - while (XmlPullUtil.test(pp, "Connection")) { - final String id = context.sequence + "/" + XmlPullUtil.attr(pp, "id"); - - XmlPullUtil.enter(pp, "Connection"); - while (pp.getName().equals("RtStateList")) - XmlPullUtil.next(pp); - XmlPullUtil.enter(pp, "Overview"); - - final Calendar currentDate = new GregorianCalendar(timeZone); - currentDate.clear(); - parseDate(currentDate, XmlPullUtil.valueTag(pp, "Date")); - XmlPullUtil.enter(pp, "Departure"); - XmlPullUtil.enter(pp, "BasicStop"); - while (pp.getName().equals("StAttrList")) - XmlPullUtil.next(pp); - final Location departureLocation = parseLocation(pp); - XmlPullUtil.enter(pp, "Dep"); - XmlPullUtil.skipExit(pp, "Dep"); - final int[] capacity; - if (XmlPullUtil.test(pp, "StopPrognosis")) { - XmlPullUtil.enter(pp, "StopPrognosis"); - if (XmlPullUtil.test(pp, "Arr")) - XmlPullUtil.next(pp); - if (XmlPullUtil.test(pp, "Dep")) - XmlPullUtil.next(pp); - XmlPullUtil.enter(pp, "Status"); - XmlPullUtil.skipExit(pp, "Status"); - final int capacity1st = Integer.parseInt(XmlPullUtil.optValueTag(pp, "Capacity1st", "0")); - final int capacity2nd = Integer.parseInt(XmlPullUtil.optValueTag(pp, "Capacity2nd", "0")); - if (capacity1st > 0 || capacity2nd > 0) - capacity = new int[] { capacity1st, capacity2nd }; + final String c = XmlPullUtil.optValueTag(pp, "ConResCtxt", null); + final Context context; + if (previousContext == null) + context = new Context(c, c, 0); + else if (later) + context = new Context(c, previousContext.earlierContext, previousContext.sequence + 1); else - capacity = null; - XmlPullUtil.skipExit(pp, "StopPrognosis"); - } else { - capacity = null; - } - XmlPullUtil.skipExit(pp, "BasicStop"); - XmlPullUtil.skipExit(pp, "Departure"); + context = new Context(previousContext.laterContext, c, previousContext.sequence + 1); - XmlPullUtil.enter(pp, "Arrival"); - XmlPullUtil.enter(pp, "BasicStop"); - while (pp.getName().equals("StAttrList")) - XmlPullUtil.next(pp); - final Location arrivalLocation = parseLocation(pp); - XmlPullUtil.skipExit(pp, "BasicStop"); - XmlPullUtil.skipExit(pp, "Arrival"); + XmlPullUtil.enter(pp, "ConnectionList"); - final int numTransfers = Integer.parseInt(XmlPullUtil.valueTag(pp, "Transfers")); + final List trips = new ArrayList(); - XmlPullUtil.skipExit(pp, "Overview"); + while (XmlPullUtil.test(pp, "Connection")) { + final String id = context.sequence + "/" + XmlPullUtil.attr(pp, "id"); - final List legs = new ArrayList(4); - - XmlPullUtil.enter(pp, "ConSectionList"); - - final Calendar time = new GregorianCalendar(timeZone); - - while (XmlPullUtil.test(pp, "ConSection")) { - XmlPullUtil.enter(pp, "ConSection"); - - // departure - XmlPullUtil.enter(pp, "Departure"); - XmlPullUtil.enter(pp, "BasicStop"); - while (pp.getName().equals("StAttrList")) - XmlPullUtil.next(pp); - final Location sectionDepartureLocation = parseLocation(pp); - - if (XmlPullUtil.test(pp, "Arr")) { - XmlPullUtil.enter(pp, "Arr"); - XmlPullUtil.skipExit(pp, "Arr"); - } - XmlPullUtil.enter(pp, "Dep"); - time.setTimeInMillis(currentDate.getTimeInMillis()); - parseTime(time, XmlPullUtil.valueTag(pp, "Time")); - final Date departureTime = time.getTime(); - final Position departurePos = parsePlatform(pp); - XmlPullUtil.skipExit(pp, "Dep"); - - XmlPullUtil.skipExit(pp, "BasicStop"); - XmlPullUtil.skipExit(pp, "Departure"); - - // journey - final Line line; - Location destination = null; - - List intermediateStops = null; - - final String tag = pp.getName(); - if (tag.equals("Journey")) { - XmlPullUtil.enter(pp, "Journey"); - while (pp.getName().equals("JHandle")) + XmlPullUtil.enter(pp, "Connection"); + while (pp.getName().equals("RtStateList")) XmlPullUtil.next(pp); - XmlPullUtil.enter(pp, "JourneyAttributeList"); - boolean wheelchairAccess = false; - String name = null; - String category = null; - String shortCategory = null; - while (XmlPullUtil.test(pp, "JourneyAttribute")) { - XmlPullUtil.enter(pp, "JourneyAttribute"); - XmlPullUtil.require(pp, "Attribute"); - final String attrName = XmlPullUtil.attr(pp, "type"); - final String code = XmlPullUtil.optAttr(pp, "code", null); - XmlPullUtil.enter(pp, "Attribute"); - final Map attributeVariants = parseAttributeVariants(pp); - XmlPullUtil.skipExit(pp, "Attribute"); - XmlPullUtil.skipExit(pp, "JourneyAttribute"); + XmlPullUtil.enter(pp, "Overview"); - if ("bf".equals(code)) { - wheelchairAccess = true; - } else if ("NAME".equals(attrName)) { - name = attributeVariants.get("NORMAL"); - } else if ("CATEGORY".equals(attrName)) { - shortCategory = attributeVariants.get("SHORT"); - category = attributeVariants.get("NORMAL"); - // longCategory = attributeVariants.get("LONG"); - } else if ("DIRECTION".equals(attrName)) { - final String[] destinationPlaceAndName = splitStationName( - attributeVariants.get("NORMAL")); - destination = new Location(LocationType.ANY, null, destinationPlaceAndName[0], - destinationPlaceAndName[1]); - } - } - XmlPullUtil.skipExit(pp, "JourneyAttributeList"); - - if (XmlPullUtil.test(pp, "PassList")) { - intermediateStops = new LinkedList(); - - XmlPullUtil.enter(pp, "PassList"); - while (XmlPullUtil.test(pp, "BasicStop")) { - XmlPullUtil.enter(pp, "BasicStop"); - while (XmlPullUtil.test(pp, "StAttrList")) - XmlPullUtil.next(pp); - final Location location = parseLocation(pp); - if (location.id != sectionDepartureLocation.id) { - Date stopArrivalTime = null; - Date stopDepartureTime = null; - Position stopArrivalPosition = null; - Position stopDeparturePosition = null; - - if (XmlPullUtil.test(pp, "Arr")) { - XmlPullUtil.enter(pp, "Arr"); - time.setTimeInMillis(currentDate.getTimeInMillis()); - parseTime(time, XmlPullUtil.valueTag(pp, "Time")); - stopArrivalTime = time.getTime(); - stopArrivalPosition = parsePlatform(pp); - XmlPullUtil.skipExit(pp, "Arr"); - } - - if (XmlPullUtil.test(pp, "Dep")) { - XmlPullUtil.enter(pp, "Dep"); - time.setTimeInMillis(currentDate.getTimeInMillis()); - parseTime(time, XmlPullUtil.valueTag(pp, "Time")); - stopDepartureTime = time.getTime(); - stopDeparturePosition = parsePlatform(pp); - XmlPullUtil.skipExit(pp, "Dep"); - } - - intermediateStops.add(new Stop(location, stopArrivalTime, stopArrivalPosition, - stopDepartureTime, stopDeparturePosition)); - } - XmlPullUtil.skipExit(pp, "BasicStop"); - } - - XmlPullUtil.skipExit(pp, "PassList"); - } - - XmlPullUtil.skipExit(pp, "Journey"); - - if (category == null) - category = shortCategory; - - line = parseLine(category, name, wheelchairAccess); - } else if (tag.equals("Walk") || tag.equals("Transfer") || tag.equals("GisRoute")) { - XmlPullUtil.enter(pp); - XmlPullUtil.enter(pp, "Duration"); - XmlPullUtil.skipExit(pp, "Duration"); - XmlPullUtil.skipExit(pp); - - line = null; - } else { - throw new IllegalStateException("cannot handle: " + pp.getName()); - } - - // polyline - final List path; - if (XmlPullUtil.test(pp, "Polyline")) { - path = new LinkedList(); - XmlPullUtil.enter(pp, "Polyline"); - while (XmlPullUtil.test(pp, "Point")) { - final int x = XmlPullUtil.intAttr(pp, "x"); - final int y = XmlPullUtil.intAttr(pp, "y"); - path.add(new Point(y, x)); + final Calendar currentDate = new GregorianCalendar(timeZone); + currentDate.clear(); + parseDate(currentDate, XmlPullUtil.valueTag(pp, "Date")); + XmlPullUtil.enter(pp, "Departure"); + XmlPullUtil.enter(pp, "BasicStop"); + while (pp.getName().equals("StAttrList")) XmlPullUtil.next(pp); - } - XmlPullUtil.skipExit(pp, "Polyline"); - } else { - path = null; - } - - // arrival - XmlPullUtil.enter(pp, "Arrival"); - XmlPullUtil.enter(pp, "BasicStop"); - while (pp.getName().equals("StAttrList")) - XmlPullUtil.next(pp); - final Location sectionArrivalLocation = parseLocation(pp); - XmlPullUtil.enter(pp, "Arr"); - time.setTimeInMillis(currentDate.getTimeInMillis()); - parseTime(time, XmlPullUtil.valueTag(pp, "Time")); - final Date arrivalTime = time.getTime(); - final Position arrivalPos = parsePlatform(pp); - XmlPullUtil.skipExit(pp, "Arr"); - - XmlPullUtil.skipExit(pp, "BasicStop"); - XmlPullUtil.skipExit(pp, "Arrival"); - - // remove last intermediate - if (intermediateStops != null) - if (!intermediateStops.isEmpty()) - if (!intermediateStops.get(intermediateStops.size() - 1).location - .equals(sectionArrivalLocation)) - intermediateStops.remove(intermediateStops.size() - 1); - - XmlPullUtil.skipExit(pp, "ConSection"); - - if (line != null) { - final Stop departure = new Stop(sectionDepartureLocation, true, departureTime, null, - departurePos, null); - final Stop arrival = new Stop(sectionArrivalLocation, false, arrivalTime, null, arrivalPos, - null); - - legs.add(new Trip.Public(line, destination, departure, arrival, intermediateStops, path, null)); - } else { - if (legs.size() > 0 && legs.get(legs.size() - 1) instanceof Trip.Individual) { - final Trip.Individual lastIndividualLeg = (Trip.Individual) legs.remove(legs.size() - 1); - legs.add(new Trip.Individual(Trip.Individual.Type.WALK, lastIndividualLeg.departure, - lastIndividualLeg.departureTime, sectionArrivalLocation, arrivalTime, null, 0)); + final Location departureLocation = parseLocation(pp); + XmlPullUtil.enter(pp, "Dep"); + XmlPullUtil.skipExit(pp, "Dep"); + final int[] capacity; + if (XmlPullUtil.test(pp, "StopPrognosis")) { + XmlPullUtil.enter(pp, "StopPrognosis"); + if (XmlPullUtil.test(pp, "Arr")) + XmlPullUtil.next(pp); + if (XmlPullUtil.test(pp, "Dep")) + XmlPullUtil.next(pp); + XmlPullUtil.enter(pp, "Status"); + XmlPullUtil.skipExit(pp, "Status"); + final int capacity1st = Integer.parseInt(XmlPullUtil.optValueTag(pp, "Capacity1st", "0")); + final int capacity2nd = Integer.parseInt(XmlPullUtil.optValueTag(pp, "Capacity2nd", "0")); + if (capacity1st > 0 || capacity2nd > 0) + capacity = new int[] { capacity1st, capacity2nd }; + else + capacity = null; + XmlPullUtil.skipExit(pp, "StopPrognosis"); } else { - legs.add(new Trip.Individual(Trip.Individual.Type.WALK, sectionDepartureLocation, - departureTime, sectionArrivalLocation, arrivalTime, null, 0)); + capacity = null; } + XmlPullUtil.skipExit(pp, "BasicStop"); + XmlPullUtil.skipExit(pp, "Departure"); + + XmlPullUtil.enter(pp, "Arrival"); + XmlPullUtil.enter(pp, "BasicStop"); + while (pp.getName().equals("StAttrList")) + XmlPullUtil.next(pp); + final Location arrivalLocation = parseLocation(pp); + XmlPullUtil.skipExit(pp, "BasicStop"); + XmlPullUtil.skipExit(pp, "Arrival"); + + final int numTransfers = Integer.parseInt(XmlPullUtil.valueTag(pp, "Transfers")); + + XmlPullUtil.skipExit(pp, "Overview"); + + final List legs = new ArrayList(4); + + XmlPullUtil.enter(pp, "ConSectionList"); + + final Calendar time = new GregorianCalendar(timeZone); + + while (XmlPullUtil.test(pp, "ConSection")) { + XmlPullUtil.enter(pp, "ConSection"); + + // departure + XmlPullUtil.enter(pp, "Departure"); + XmlPullUtil.enter(pp, "BasicStop"); + while (pp.getName().equals("StAttrList")) + XmlPullUtil.next(pp); + final Location sectionDepartureLocation = parseLocation(pp); + + if (XmlPullUtil.test(pp, "Arr")) { + XmlPullUtil.enter(pp, "Arr"); + XmlPullUtil.skipExit(pp, "Arr"); + } + XmlPullUtil.enter(pp, "Dep"); + time.setTimeInMillis(currentDate.getTimeInMillis()); + parseTime(time, XmlPullUtil.valueTag(pp, "Time")); + final Date departureTime = time.getTime(); + final Position departurePos = parsePlatform(pp); + XmlPullUtil.skipExit(pp, "Dep"); + + XmlPullUtil.skipExit(pp, "BasicStop"); + XmlPullUtil.skipExit(pp, "Departure"); + + // journey + final Line line; + Location destination = null; + + List intermediateStops = null; + + final String tag = pp.getName(); + if (tag.equals("Journey")) { + XmlPullUtil.enter(pp, "Journey"); + while (pp.getName().equals("JHandle")) + XmlPullUtil.next(pp); + XmlPullUtil.enter(pp, "JourneyAttributeList"); + boolean wheelchairAccess = false; + String name = null; + String category = null; + String shortCategory = null; + while (XmlPullUtil.test(pp, "JourneyAttribute")) { + XmlPullUtil.enter(pp, "JourneyAttribute"); + XmlPullUtil.require(pp, "Attribute"); + final String attrName = XmlPullUtil.attr(pp, "type"); + final String code = XmlPullUtil.optAttr(pp, "code", null); + XmlPullUtil.enter(pp, "Attribute"); + final Map attributeVariants = parseAttributeVariants(pp); + XmlPullUtil.skipExit(pp, "Attribute"); + XmlPullUtil.skipExit(pp, "JourneyAttribute"); + + if ("bf".equals(code)) { + wheelchairAccess = true; + } else if ("NAME".equals(attrName)) { + name = attributeVariants.get("NORMAL"); + } else if ("CATEGORY".equals(attrName)) { + shortCategory = attributeVariants.get("SHORT"); + category = attributeVariants.get("NORMAL"); + // longCategory = attributeVariants.get("LONG"); + } else if ("DIRECTION".equals(attrName)) { + final String[] destinationPlaceAndName = splitStationName( + attributeVariants.get("NORMAL")); + destination = new Location(LocationType.ANY, null, destinationPlaceAndName[0], + destinationPlaceAndName[1]); + } + } + XmlPullUtil.skipExit(pp, "JourneyAttributeList"); + + if (XmlPullUtil.test(pp, "PassList")) { + intermediateStops = new LinkedList(); + + XmlPullUtil.enter(pp, "PassList"); + while (XmlPullUtil.test(pp, "BasicStop")) { + XmlPullUtil.enter(pp, "BasicStop"); + while (XmlPullUtil.test(pp, "StAttrList")) + XmlPullUtil.next(pp); + final Location location = parseLocation(pp); + if (location.id != sectionDepartureLocation.id) { + Date stopArrivalTime = null; + Date stopDepartureTime = null; + Position stopArrivalPosition = null; + Position stopDeparturePosition = null; + + if (XmlPullUtil.test(pp, "Arr")) { + XmlPullUtil.enter(pp, "Arr"); + time.setTimeInMillis(currentDate.getTimeInMillis()); + parseTime(time, XmlPullUtil.valueTag(pp, "Time")); + stopArrivalTime = time.getTime(); + stopArrivalPosition = parsePlatform(pp); + XmlPullUtil.skipExit(pp, "Arr"); + } + + if (XmlPullUtil.test(pp, "Dep")) { + XmlPullUtil.enter(pp, "Dep"); + time.setTimeInMillis(currentDate.getTimeInMillis()); + parseTime(time, XmlPullUtil.valueTag(pp, "Time")); + stopDepartureTime = time.getTime(); + stopDeparturePosition = parsePlatform(pp); + XmlPullUtil.skipExit(pp, "Dep"); + } + + intermediateStops.add(new Stop(location, stopArrivalTime, + stopArrivalPosition, stopDepartureTime, stopDeparturePosition)); + } + XmlPullUtil.skipExit(pp, "BasicStop"); + } + + XmlPullUtil.skipExit(pp, "PassList"); + } + + XmlPullUtil.skipExit(pp, "Journey"); + + if (category == null) + category = shortCategory; + + line = parseLine(category, name, wheelchairAccess); + } else if (tag.equals("Walk") || tag.equals("Transfer") || tag.equals("GisRoute")) { + XmlPullUtil.enter(pp); + XmlPullUtil.enter(pp, "Duration"); + XmlPullUtil.skipExit(pp, "Duration"); + XmlPullUtil.skipExit(pp); + + line = null; + } else { + throw new IllegalStateException("cannot handle: " + pp.getName()); + } + + // polyline + final List path; + if (XmlPullUtil.test(pp, "Polyline")) { + path = new LinkedList(); + XmlPullUtil.enter(pp, "Polyline"); + while (XmlPullUtil.test(pp, "Point")) { + final int x = XmlPullUtil.intAttr(pp, "x"); + final int y = XmlPullUtil.intAttr(pp, "y"); + path.add(new Point(y, x)); + XmlPullUtil.next(pp); + } + XmlPullUtil.skipExit(pp, "Polyline"); + } else { + path = null; + } + + // arrival + XmlPullUtil.enter(pp, "Arrival"); + XmlPullUtil.enter(pp, "BasicStop"); + while (pp.getName().equals("StAttrList")) + XmlPullUtil.next(pp); + final Location sectionArrivalLocation = parseLocation(pp); + XmlPullUtil.enter(pp, "Arr"); + time.setTimeInMillis(currentDate.getTimeInMillis()); + parseTime(time, XmlPullUtil.valueTag(pp, "Time")); + final Date arrivalTime = time.getTime(); + final Position arrivalPos = parsePlatform(pp); + XmlPullUtil.skipExit(pp, "Arr"); + + XmlPullUtil.skipExit(pp, "BasicStop"); + XmlPullUtil.skipExit(pp, "Arrival"); + + // remove last intermediate + if (intermediateStops != null) + if (!intermediateStops.isEmpty()) + if (!intermediateStops.get(intermediateStops.size() - 1).location + .equals(sectionArrivalLocation)) + intermediateStops.remove(intermediateStops.size() - 1); + + XmlPullUtil.skipExit(pp, "ConSection"); + + if (line != null) { + final Stop departure = new Stop(sectionDepartureLocation, true, departureTime, null, + departurePos, null); + final Stop arrival = new Stop(sectionArrivalLocation, false, arrivalTime, null, + arrivalPos, null); + + legs.add(new Trip.Public(line, destination, departure, arrival, intermediateStops, path, + null)); + } else { + if (legs.size() > 0 && legs.get(legs.size() - 1) instanceof Trip.Individual) { + final Trip.Individual lastIndividualLeg = (Trip.Individual) legs + .remove(legs.size() - 1); + legs.add(new Trip.Individual(Trip.Individual.Type.WALK, lastIndividualLeg.departure, + lastIndividualLeg.departureTime, sectionArrivalLocation, arrivalTime, null, + 0)); + } else { + legs.add(new Trip.Individual(Trip.Individual.Type.WALK, sectionDepartureLocation, + departureTime, sectionArrivalLocation, arrivalTime, null, 0)); + } + } + } + + XmlPullUtil.skipExit(pp, "ConSectionList"); + + XmlPullUtil.skipExit(pp, "Connection"); + + trips.add(new Trip(id, departureLocation, arrivalLocation, legs, null, capacity, numTransfers)); } + + XmlPullUtil.skipExit(pp, "ConnectionList"); + + result.set(new QueryTripsResult(header, null, from, via, to, context, trips)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); } - - XmlPullUtil.skipExit(pp, "ConSectionList"); - - XmlPullUtil.skipExit(pp, "Connection"); - - trips.add(new Trip(id, departureLocation, arrivalLocation, legs, null, capacity, numTransfers)); } + }, HttpUrl.parse(endpoint), request, "application/xml", null, null); - XmlPullUtil.skipExit(pp, "ConnectionList"); - - return new QueryTripsResult(header, null, from, via, to, context, trips); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (reader != null) - reader.close(); - } + return result.get(); } private final Location parseLocation(final XmlPullParser pp) throws XmlPullParserException, IOException { @@ -2050,563 +2129,591 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { * Many thanks to Malte Starostik and Robert, who helped a lot with analyzing this API! */ - LittleEndianDataInputStream is = null; + final AtomicReference result = new AtomicReference(); - try { - final CustomBufferedInputStream bis = new CustomBufferedInputStream(httpClient.getInputStream(uri)); - final String firstChars = HttpClient.peekFirstChars(bis); + httpClient.getInputStream(new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + final CustomBufferedInputStream bis = new CustomBufferedInputStream( + new GZIPInputStream(body.byteStream())); - // initialize input stream - is = new LittleEndianDataInputStream(bis); - is.mark(expectedBufferSize); + // initialize input stream + final LittleEndianDataInputStream is = new LittleEndianDataInputStream(bis); + is.mark(expectedBufferSize); - // quick check of status - final int version = is.readShortReverse(); - if (version != 6 && version != 5) - throw new IllegalStateException("unknown version: " + version + ", first chars: " + firstChars); - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT, Integer.toString(version), 0, null); + // quick check of status + final int version = is.readShortReverse(); + if (version != 6 && version != 5) + throw new IllegalStateException("unknown version: " + version + ", first chars: " + bodyPeek); + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT, Integer.toString(version), 0, + null); - // quick seek for pointers - is.reset(); - is.skipBytes(0x20); - final int serviceDaysTablePtr = is.readIntReverse(); - final int stringTablePtr = is.readIntReverse(); - - is.reset(); - is.skipBytes(0x36); - final int stationTablePtr = is.readIntReverse(); - final int commentTablePtr = is.readIntReverse(); - - is.reset(); - is.skipBytes(0x46); - final int extensionHeaderPtr = is.readIntReverse(); - - // read strings - final StringTable strings = new StringTable(is, stringTablePtr, serviceDaysTablePtr - stringTablePtr); - - is.reset(); - is.skipBytes(extensionHeaderPtr); - - // read extension header - final int extensionHeaderLength = is.readIntReverse(); - if (extensionHeaderLength < 0x2c) - throw new IllegalStateException("too short: " + extensionHeaderLength); - - is.skipBytes(12); - final int errorCode = is.readShortReverse(); - - if (errorCode == 0) { - // string encoding - is.skipBytes(14); - final Charset stringEncoding = Charset.forName(strings.read(is)); - strings.setEncoding(stringEncoding); - - // read number of trips + // quick seek for pointers is.reset(); - is.skipBytes(30); - - final int numTrips = is.readShortReverse(); - if (numTrips == 0) - return new QueryTripsResult(header, uri, from, via, to, null, new LinkedList()); - - // read rest of header - is.reset(); - is.skipBytes(0x02); - - final Location resDeparture = location(is, strings); - final Location resArrival = location(is, strings); - - is.skipBytes(10); - - final long resDate = date(is); - /* final long resDate30 = */date(is); + is.skipBytes(0x20); + final int serviceDaysTablePtr = is.readIntReverse(); + final int stringTablePtr = is.readIntReverse(); is.reset(); - is.skipBytes(extensionHeaderPtr + 0x8); + is.skipBytes(0x36); + final int stationTablePtr = is.readIntReverse(); + final int commentTablePtr = is.readIntReverse(); - final int seqNr = is.readShortReverse(); - if (seqNr == 0) - throw new SessionExpiredException(); - else if (seqNr < 0) - throw new IllegalStateException("illegal sequence number: " + seqNr); + is.reset(); + is.skipBytes(0x46); + final int extensionHeaderPtr = is.readIntReverse(); - final String requestId = strings.read(is); + // read strings + final StringTable strings = new StringTable(is, stringTablePtr, serviceDaysTablePtr - stringTablePtr); - final int tripDetailsPtr = is.readIntReverse(); - if (tripDetailsPtr == 0) - throw new IllegalStateException("no connection details"); + is.reset(); + is.skipBytes(extensionHeaderPtr); - is.skipBytes(4); + // read extension header + final int extensionHeaderLength = is.readIntReverse(); + if (extensionHeaderLength < 0x2c) + throw new IllegalStateException("too short: " + extensionHeaderLength); - final int disruptionsPtr = is.readIntReverse(); + is.skipBytes(12); + final int errorCode = is.readShortReverse(); - is.skipBytes(10); + if (errorCode == 0) { + // string encoding + is.skipBytes(14); + final Charset stringEncoding = Charset.forName(strings.read(is)); + strings.setEncoding(stringEncoding); - final String ld = strings.read(is); - final int attrsOffset = is.readIntReverse(); - - final int tripAttrsPtr; - if (extensionHeaderLength >= 0x30) { - if (extensionHeaderLength < 0x32) - throw new IllegalArgumentException("too short: " + extensionHeaderLength); + // read number of trips is.reset(); - is.skipBytes(extensionHeaderPtr + 0x2c); - tripAttrsPtr = is.readIntReverse(); - } else { - tripAttrsPtr = 0; - } + is.skipBytes(30); - // determine stops offset - is.reset(); - is.skipBytes(tripDetailsPtr); - final int tripDetailsVersion = is.readShortReverse(); - if (tripDetailsVersion != 1) - throw new IllegalStateException("unknown trip details version: " + tripDetailsVersion); - is.skipBytes(0x02); - - final int tripDetailsIndexOffset = is.readShortReverse(); - final int tripDetailsLegOffset = is.readShortReverse(); - final int tripDetailsLegSize = is.readShortReverse(); - final int stopsSize = is.readShortReverse(); - final int stopsOffset = is.readShortReverse(); - - // read stations - final StationTable stations = new StationTable(is, stationTablePtr, commentTablePtr - stationTablePtr, - strings); - - // read comments - final CommentTable comments = new CommentTable(is, commentTablePtr, tripDetailsPtr - commentTablePtr, - strings); - - final List trips = new ArrayList(numTrips); - - // read trips - for (int iTrip = 0; iTrip < numTrips; iTrip++) { - is.reset(); - is.skipBytes(0x4a + iTrip * 12); - - final int serviceDaysTableOffset = is.readShortReverse(); - - final int legsOffset = is.readIntReverse(); - - final int numLegs = is.readShortReverse(); - - final int numChanges = is.readShortReverse(); - - /* final long duration = time(is, 0, 0); */is.readShortReverse(); - - is.reset(); - is.skipBytes(serviceDaysTablePtr + serviceDaysTableOffset); - - /* final String serviceDaysText = */strings.read(is); - - final int serviceBitBase = is.readShortReverse(); - final int serviceBitLength = is.readShortReverse(); - - int tripDayOffset = serviceBitBase * 8; - for (int i = 0; i < serviceBitLength; i++) { - int serviceBits = is.read(); - if (serviceBits == 0) { - tripDayOffset += 8; - continue; - } - while ((serviceBits & 0x80) == 0) { - serviceBits = serviceBits << 1; - tripDayOffset++; - } - break; + final int numTrips = is.readShortReverse(); + if (numTrips == 0) { + result.set(new QueryTripsResult(header, uri, from, via, to, null, new LinkedList())); + return; } + // read rest of header is.reset(); - is.skipBytes(tripDetailsPtr + tripDetailsIndexOffset + iTrip * 2); - final int tripDetailsOffset = is.readShortReverse(); + is.skipBytes(0x02); + + final Location resDeparture = location(is, strings); + final Location resArrival = location(is, strings); + + is.skipBytes(10); + + final long resDate = date(is); + /* final long resDate30 = */date(is); is.reset(); - is.skipBytes(tripDetailsPtr + tripDetailsOffset); - final int realtimeStatus = is.readShortReverse(); + is.skipBytes(extensionHeaderPtr + 0x8); - /* final short delay = */is.readShortReverse(); + final int seqNr = is.readShortReverse(); + if (seqNr == 0) + throw new SessionExpiredException(); + else if (seqNr < 0) + throw new IllegalStateException("illegal sequence number: " + seqNr); - /* final int legIndex = */is.readShortReverse(); + final String requestId = strings.read(is); - is.skipBytes(2); // 0xffff + final int tripDetailsPtr = is.readIntReverse(); + if (tripDetailsPtr == 0) + throw new IllegalStateException("no connection details"); - /* final int legStatus = */is.readShortReverse(); + is.skipBytes(4); - is.skipBytes(2); // 0x0000 + final int disruptionsPtr = is.readIntReverse(); - String connectionId = null; - if (tripAttrsPtr != 0) { + is.skipBytes(10); + + final String ld = strings.read(is); + final int attrsOffset = is.readIntReverse(); + + final int tripAttrsPtr; + if (extensionHeaderLength >= 0x30) { + if (extensionHeaderLength < 0x32) + throw new IllegalArgumentException("too short: " + extensionHeaderLength); is.reset(); - is.skipBytes(tripAttrsPtr + iTrip * 2); - final int tripAttrsIndex = is.readShortReverse(); - - is.reset(); - is.skipBytes(attrsOffset + tripAttrsIndex * 4); - while (true) { - final String key = strings.read(is); - if (key == null) - break; - else if (key.equals("ConnectionId")) - connectionId = strings.read(is); - else - is.skipBytes(2); - } + is.skipBytes(extensionHeaderPtr + 0x2c); + tripAttrsPtr = is.readIntReverse(); + } else { + tripAttrsPtr = 0; } - final List legs = new ArrayList(numLegs); + // determine stops offset + is.reset(); + is.skipBytes(tripDetailsPtr); + final int tripDetailsVersion = is.readShortReverse(); + if (tripDetailsVersion != 1) + throw new IllegalStateException("unknown trip details version: " + tripDetailsVersion); + is.skipBytes(0x02); - for (int iLegs = 0; iLegs < numLegs; iLegs++) { + final int tripDetailsIndexOffset = is.readShortReverse(); + final int tripDetailsLegOffset = is.readShortReverse(); + final int tripDetailsLegSize = is.readShortReverse(); + final int stopsSize = is.readShortReverse(); + final int stopsOffset = is.readShortReverse(); + + // read stations + final StationTable stations = new StationTable(is, stationTablePtr, + commentTablePtr - stationTablePtr, strings); + + // read comments + final CommentTable comments = new CommentTable(is, commentTablePtr, + tripDetailsPtr - commentTablePtr, strings); + + final List trips = new ArrayList(numTrips); + + // read trips + for (int iTrip = 0; iTrip < numTrips; iTrip++) { is.reset(); - is.skipBytes(0x4a + legsOffset + iLegs * 20); + is.skipBytes(0x4a + iTrip * 12); - final long plannedDepartureTime = time(is, resDate, tripDayOffset); - final Location departureLocation = stations.read(is); + final int serviceDaysTableOffset = is.readShortReverse(); - final long plannedArrivalTime = time(is, resDate, tripDayOffset); - final Location arrivalLocation = stations.read(is); + final int legsOffset = is.readIntReverse(); - final int type = is.readShortReverse(); + final int numLegs = is.readShortReverse(); - final String lineName = strings.read(is); + final int numChanges = is.readShortReverse(); - final Position plannedDeparturePosition = normalizePosition(strings.read(is)); - final Position plannedArrivalPosition = normalizePosition(strings.read(is)); + /* final long duration = time(is, 0, 0); */is.readShortReverse(); - final int legAttrIndex = is.readShortReverse(); + is.reset(); + is.skipBytes(serviceDaysTablePtr + serviceDaysTableOffset); - final List lineAttrs = new ArrayList(); - String lineComment = null; - boolean lineOnDemand = false; - for (final String comment : comments.read(is)) { - if (comment.startsWith("bf ")) { - lineAttrs.add(Line.Attr.WHEEL_CHAIR_ACCESS); - } else if (comment.startsWith("FA ") || comment.startsWith("FB ") - || comment.startsWith("FR ")) { - lineAttrs.add(Line.Attr.BICYCLE_CARRIAGE); - } else if (comment.startsWith("$R ") || comment.startsWith("ga ") - || comment.startsWith("ja ") || comment.startsWith("Vs ") - || comment.startsWith("mu ") || comment.startsWith("mx ")) { - lineOnDemand = true; - lineComment = comment.substring(5); + /* final String serviceDaysText = */strings.read(is); + + final int serviceBitBase = is.readShortReverse(); + final int serviceBitLength = is.readShortReverse(); + + int tripDayOffset = serviceBitBase * 8; + for (int i = 0; i < serviceBitLength; i++) { + int serviceBits = is.read(); + if (serviceBits == 0) { + tripDayOffset += 8; + continue; + } + while ((serviceBits & 0x80) == 0) { + serviceBits = serviceBits << 1; + tripDayOffset++; + } + break; + } + + is.reset(); + is.skipBytes(tripDetailsPtr + tripDetailsIndexOffset + iTrip * 2); + final int tripDetailsOffset = is.readShortReverse(); + + is.reset(); + is.skipBytes(tripDetailsPtr + tripDetailsOffset); + final int realtimeStatus = is.readShortReverse(); + + /* final short delay = */is.readShortReverse(); + + /* final int legIndex = */is.readShortReverse(); + + is.skipBytes(2); // 0xffff + + /* final int legStatus = */is.readShortReverse(); + + is.skipBytes(2); // 0x0000 + + String connectionId = null; + if (tripAttrsPtr != 0) { + is.reset(); + is.skipBytes(tripAttrsPtr + iTrip * 2); + final int tripAttrsIndex = is.readShortReverse(); + + is.reset(); + is.skipBytes(attrsOffset + tripAttrsIndex * 4); + while (true) { + final String key = strings.read(is); + if (key == null) + break; + else if (key.equals("ConnectionId")) + connectionId = strings.read(is); + else + is.skipBytes(2); } } - is.reset(); - is.skipBytes(attrsOffset + legAttrIndex * 4); - String directionStr = null; - int lineClass = 0; - String lineCategory = null; - String routingType = null; - String lineNetwork = null; - while (true) { - final String key = strings.read(is); - if (key == null) - break; - else if (key.equals("Direction")) - directionStr = strings.read(is); - else if (key.equals("Class")) - lineClass = Integer.parseInt(strings.read(is)); - else if (key.equals("Category")) - lineCategory = strings.read(is); - // else if (key.equals("Operator")) - // lineOperator = strings.read(is); - else if (key.equals("GisRoutingType")) - routingType = strings.read(is); - else if (key.equals("AdminCode")) - lineNetwork = normalizeLineAdministration(strings.read(is)); - else - is.skipBytes(2); - } + final List legs = new ArrayList(numLegs); - if (lineCategory == null && lineName != null) - lineCategory = categoryFromName(lineName); - - is.reset(); - is.skipBytes( - tripDetailsPtr + tripDetailsOffset + tripDetailsLegOffset + iLegs * tripDetailsLegSize); - - if (tripDetailsLegSize != 16) - throw new IllegalStateException("unhandled trip details leg size: " + tripDetailsLegSize); - - final long predictedDepartureTime = time(is, resDate, tripDayOffset); - final long predictedArrivalTime = time(is, resDate, tripDayOffset); - final Position predictedDeparturePosition = normalizePosition(strings.read(is)); - final Position predictedArrivalPosition = normalizePosition(strings.read(is)); - - final int bits = is.readShortReverse(); - final boolean arrivalCancelled = (bits & 0x10) != 0; - final boolean departureCancelled = (bits & 0x20) != 0; - - is.readShort(); - - final int firstStopIndex = is.readShortReverse(); - - final int numStops = is.readShortReverse(); - - is.reset(); - is.skipBytes(disruptionsPtr); - - String disruptionText = null; - - if (is.readShortReverse() == 1) { + for (int iLegs = 0; iLegs < numLegs; iLegs++) { is.reset(); - is.skipBytes(disruptionsPtr + 2 + iTrip * 2); + is.skipBytes(0x4a + legsOffset + iLegs * 20); - int disruptionsOffset = is.readShortReverse(); - while (disruptionsOffset != 0) { - is.reset(); - is.skipBytes(disruptionsPtr + disruptionsOffset); + final long plannedDepartureTime = time(is, resDate, tripDayOffset); + final Location departureLocation = stations.read(is); - strings.read(is); // "0" + final long plannedArrivalTime = time(is, resDate, tripDayOffset); + final Location arrivalLocation = stations.read(is); - final int disruptionLeg = is.readShortReverse(); + final int type = is.readShortReverse(); - is.skipBytes(2); // bitmaske + final String lineName = strings.read(is); - strings.read(is); // start of line - strings.read(is); // end of line + final Position plannedDeparturePosition = normalizePosition(strings.read(is)); + final Position plannedArrivalPosition = normalizePosition(strings.read(is)); - strings.read(is); - // id - /* final String disruptionTitle = */strings.read(is); - final String disruptionShortText = ParserUtils.formatHtml(strings.read(is)); + final int legAttrIndex = is.readShortReverse(); - disruptionsOffset = is.readShortReverse(); // next - - if (iLegs == disruptionLeg) { - final int disruptionAttrsIndex = is.readShortReverse(); - - is.reset(); - is.skipBytes(attrsOffset + disruptionAttrsIndex * 4); - - while (true) { - final String key = strings.read(is); - if (key == null) - break; - else if (key.equals("Text")) - disruptionText = ParserUtils.resolveEntities(strings.read(is)); - else - is.skipBytes(2); - } - - if (disruptionShortText != null) - disruptionText = disruptionShortText; + final List lineAttrs = new ArrayList(); + String lineComment = null; + boolean lineOnDemand = false; + for (final String comment : comments.read(is)) { + if (comment.startsWith("bf ")) { + lineAttrs.add(Line.Attr.WHEEL_CHAIR_ACCESS); + } else if (comment.startsWith("FA ") || comment.startsWith("FB ") + || comment.startsWith("FR ")) { + lineAttrs.add(Line.Attr.BICYCLE_CARRIAGE); + } else if (comment.startsWith("$R ") || comment.startsWith("ga ") + || comment.startsWith("ja ") || comment.startsWith("Vs ") + || comment.startsWith("mu ") || comment.startsWith("mx ")) { + lineOnDemand = true; + lineComment = comment.substring(5); } } - } - List intermediateStops = null; - - if (numStops > 0) { is.reset(); - is.skipBytes(tripDetailsPtr + stopsOffset + firstStopIndex * stopsSize); - - if (stopsSize != 26) - throw new IllegalStateException("unhandled stops size: " + stopsSize); - - intermediateStops = new ArrayList(numStops); - - for (int iStop = 0; iStop < numStops; iStop++) { - final long plannedStopDepartureTime = time(is, resDate, tripDayOffset); - final Date plannedStopDepartureDate = plannedStopDepartureTime != 0 - ? new Date(plannedStopDepartureTime) : null; - final long plannedStopArrivalTime = time(is, resDate, tripDayOffset); - final Date plannedStopArrivalDate = plannedStopArrivalTime != 0 - ? new Date(plannedStopArrivalTime) : null; - final Position plannedStopDeparturePosition = normalizePosition(strings.read(is)); - final Position plannedStopArrivalPosition = normalizePosition(strings.read(is)); - - is.readInt(); - - final long predictedStopDepartureTime = time(is, resDate, tripDayOffset); - final Date predictedStopDepartureDate = predictedStopDepartureTime != 0 - ? new Date(predictedStopDepartureTime) : null; - final long predictedStopArrivalTime = time(is, resDate, tripDayOffset); - final Date predictedStopArrivalDate = predictedStopArrivalTime != 0 - ? new Date(predictedStopArrivalTime) : null; - final Position predictedStopDeparturePosition = normalizePosition(strings.read(is)); - final Position predictedStopArrivalPosition = normalizePosition(strings.read(is)); - - final int stopBits = is.readShortReverse(); - final boolean stopArrivalCancelled = (stopBits & 0x10) != 0; - final boolean stopDepartureCancelled = (stopBits & 0x20) != 0; - - is.readShort(); - - final Location stopLocation = stations.read(is); - - final boolean validPredictedDate = !dominantPlanStopTime - || (plannedStopArrivalDate != null && plannedStopDepartureDate != null); - - final Stop stop = new Stop(stopLocation, plannedStopArrivalDate, - validPredictedDate ? predictedStopArrivalDate : null, - plannedStopArrivalPosition, predictedStopArrivalPosition, stopArrivalCancelled, - plannedStopDepartureDate, - validPredictedDate ? predictedStopDepartureDate : null, - plannedStopDeparturePosition, predictedStopDeparturePosition, - stopDepartureCancelled); - - intermediateStops.add(stop); + is.skipBytes(attrsOffset + legAttrIndex * 4); + String directionStr = null; + int lineClass = 0; + String lineCategory = null; + String routingType = null; + String lineNetwork = null; + while (true) { + final String key = strings.read(is); + if (key == null) + break; + else if (key.equals("Direction")) + directionStr = strings.read(is); + else if (key.equals("Class")) + lineClass = Integer.parseInt(strings.read(is)); + else if (key.equals("Category")) + lineCategory = strings.read(is); + // else if (key.equals("Operator")) + // lineOperator = strings.read(is); + else if (key.equals("GisRoutingType")) + routingType = strings.read(is); + else if (key.equals("AdminCode")) + lineNetwork = normalizeLineAdministration(strings.read(is)); + else + is.skipBytes(2); } + + if (lineCategory == null && lineName != null) + lineCategory = categoryFromName(lineName); + + is.reset(); + is.skipBytes(tripDetailsPtr + tripDetailsOffset + tripDetailsLegOffset + + iLegs * tripDetailsLegSize); + + if (tripDetailsLegSize != 16) + throw new IllegalStateException( + "unhandled trip details leg size: " + tripDetailsLegSize); + + final long predictedDepartureTime = time(is, resDate, tripDayOffset); + final long predictedArrivalTime = time(is, resDate, tripDayOffset); + final Position predictedDeparturePosition = normalizePosition(strings.read(is)); + final Position predictedArrivalPosition = normalizePosition(strings.read(is)); + + final int bits = is.readShortReverse(); + final boolean arrivalCancelled = (bits & 0x10) != 0; + final boolean departureCancelled = (bits & 0x20) != 0; + + is.readShort(); + + final int firstStopIndex = is.readShortReverse(); + + final int numStops = is.readShortReverse(); + + is.reset(); + is.skipBytes(disruptionsPtr); + + String disruptionText = null; + + if (is.readShortReverse() == 1) { + is.reset(); + is.skipBytes(disruptionsPtr + 2 + iTrip * 2); + + int disruptionsOffset = is.readShortReverse(); + while (disruptionsOffset != 0) { + is.reset(); + is.skipBytes(disruptionsPtr + disruptionsOffset); + + strings.read(is); // "0" + + final int disruptionLeg = is.readShortReverse(); + + is.skipBytes(2); // bitmaske + + strings.read(is); // start of line + strings.read(is); // end of line + + strings.read(is); + // id + /* final String disruptionTitle = */strings.read(is); + final String disruptionShortText = ParserUtils.formatHtml(strings.read(is)); + + disruptionsOffset = is.readShortReverse(); // next + + if (iLegs == disruptionLeg) { + final int disruptionAttrsIndex = is.readShortReverse(); + + is.reset(); + is.skipBytes(attrsOffset + disruptionAttrsIndex * 4); + + while (true) { + final String key = strings.read(is); + if (key == null) + break; + else if (key.equals("Text")) + disruptionText = ParserUtils.resolveEntities(strings.read(is)); + else + is.skipBytes(2); + } + + if (disruptionShortText != null) + disruptionText = disruptionShortText; + } + } + } + + List intermediateStops = null; + + if (numStops > 0) { + is.reset(); + is.skipBytes(tripDetailsPtr + stopsOffset + firstStopIndex * stopsSize); + + if (stopsSize != 26) + throw new IllegalStateException("unhandled stops size: " + stopsSize); + + intermediateStops = new ArrayList(numStops); + + for (int iStop = 0; iStop < numStops; iStop++) { + final long plannedStopDepartureTime = time(is, resDate, tripDayOffset); + final Date plannedStopDepartureDate = plannedStopDepartureTime != 0 + ? new Date(plannedStopDepartureTime) : null; + final long plannedStopArrivalTime = time(is, resDate, tripDayOffset); + final Date plannedStopArrivalDate = plannedStopArrivalTime != 0 + ? new Date(plannedStopArrivalTime) : null; + final Position plannedStopDeparturePosition = normalizePosition(strings.read(is)); + final Position plannedStopArrivalPosition = normalizePosition(strings.read(is)); + + is.readInt(); + + final long predictedStopDepartureTime = time(is, resDate, tripDayOffset); + final Date predictedStopDepartureDate = predictedStopDepartureTime != 0 + ? new Date(predictedStopDepartureTime) : null; + final long predictedStopArrivalTime = time(is, resDate, tripDayOffset); + final Date predictedStopArrivalDate = predictedStopArrivalTime != 0 + ? new Date(predictedStopArrivalTime) : null; + final Position predictedStopDeparturePosition = normalizePosition(strings.read(is)); + final Position predictedStopArrivalPosition = normalizePosition(strings.read(is)); + + final int stopBits = is.readShortReverse(); + final boolean stopArrivalCancelled = (stopBits & 0x10) != 0; + final boolean stopDepartureCancelled = (stopBits & 0x20) != 0; + + is.readShort(); + + final Location stopLocation = stations.read(is); + + final boolean validPredictedDate = !dominantPlanStopTime + || (plannedStopArrivalDate != null && plannedStopDepartureDate != null); + + final Stop stop = new Stop(stopLocation, plannedStopArrivalDate, + validPredictedDate ? predictedStopArrivalDate : null, + plannedStopArrivalPosition, predictedStopArrivalPosition, + stopArrivalCancelled, plannedStopDepartureDate, + validPredictedDate ? predictedStopDepartureDate : null, + plannedStopDeparturePosition, predictedStopDeparturePosition, + stopDepartureCancelled); + + intermediateStops.add(stop); + } + } + + final Trip.Leg leg; + if (type == 1 /* Fussweg */ || type == 3 /* Uebergang */ || type == 4 /* Uebergang */) { + final Trip.Individual.Type individualType; + if (routingType == null) + individualType = type == 1 ? Trip.Individual.Type.WALK + : Trip.Individual.Type.TRANSFER; + else if ("FOOT".equals(routingType)) + individualType = Trip.Individual.Type.WALK; + else if ("BIKE".equals(routingType)) + individualType = Trip.Individual.Type.BIKE; + else if ("CAR".equals(routingType) || "P+R".equals(routingType)) + individualType = Trip.Individual.Type.CAR; + else + throw new IllegalStateException("unknown routingType: " + routingType); + + final Date departureTime = new Date( + predictedDepartureTime != 0 ? predictedDepartureTime : plannedDepartureTime); + final Date arrivalTime = new Date( + predictedArrivalTime != 0 ? predictedArrivalTime : plannedArrivalTime); + + final Trip.Leg lastLeg = legs.size() > 0 ? legs.get(legs.size() - 1) : null; + if (lastLeg != null && lastLeg instanceof Trip.Individual + && ((Trip.Individual) lastLeg).type == individualType) { + final Trip.Individual lastIndividualLeg = (Trip.Individual) legs + .remove(legs.size() - 1); + leg = new Trip.Individual(individualType, lastIndividualLeg.departure, + lastIndividualLeg.departureTime, arrivalLocation, arrivalTime, null, 0); + } else { + leg = new Trip.Individual(individualType, departureLocation, departureTime, + arrivalLocation, arrivalTime, null, 0); + } + } else if (type == 2) { + final Product lineProduct; + if (lineOnDemand) + lineProduct = Product.ON_DEMAND; + else if (lineClass != 0) + lineProduct = intToProduct(lineClass); + else + lineProduct = normalizeType(lineCategory); + + final Line line = newLine(lineNetwork, lineProduct, normalizeLineName(lineName), + lineComment, lineAttrs.toArray(new Line.Attr[0])); + + final Location direction; + if (directionStr != null) { + final String[] directionPlaceAndName = splitStationName(directionStr); + direction = new Location(LocationType.ANY, null, directionPlaceAndName[0], + directionPlaceAndName[1]); + } else { + direction = null; + } + + final Stop departure = new Stop(departureLocation, true, + plannedDepartureTime != 0 ? new Date(plannedDepartureTime) : null, + predictedDepartureTime != 0 ? new Date(predictedDepartureTime) : null, + plannedDeparturePosition, predictedDeparturePosition, departureCancelled); + final Stop arrival = new Stop(arrivalLocation, false, + plannedArrivalTime != 0 ? new Date(plannedArrivalTime) : null, + predictedArrivalTime != 0 ? new Date(predictedArrivalTime) : null, + plannedArrivalPosition, predictedArrivalPosition, arrivalCancelled); + + leg = new Trip.Public(line, direction, departure, arrival, intermediateStops, null, + disruptionText); + } else { + throw new IllegalStateException("unhandled type: " + type); + } + legs.add(leg); } - final Trip.Leg leg; - if (type == 1 /* Fussweg */ || type == 3 /* Uebergang */ || type == 4 /* Uebergang */) { - final Trip.Individual.Type individualType; - if (routingType == null) - individualType = type == 1 ? Trip.Individual.Type.WALK : Trip.Individual.Type.TRANSFER; - else if ("FOOT".equals(routingType)) - individualType = Trip.Individual.Type.WALK; - else if ("BIKE".equals(routingType)) - individualType = Trip.Individual.Type.BIKE; - else if ("CAR".equals(routingType) || "P+R".equals(routingType)) - individualType = Trip.Individual.Type.CAR; - else - throw new IllegalStateException("unknown routingType: " + routingType); + final Trip trip = new Trip(connectionId, resDeparture, resArrival, legs, null, null, + (int) numChanges); - final Date departureTime = new Date( - predictedDepartureTime != 0 ? predictedDepartureTime : plannedDepartureTime); - final Date arrivalTime = new Date( - predictedArrivalTime != 0 ? predictedArrivalTime : plannedArrivalTime); - - final Trip.Leg lastLeg = legs.size() > 0 ? legs.get(legs.size() - 1) : null; - if (lastLeg != null && lastLeg instanceof Trip.Individual - && ((Trip.Individual) lastLeg).type == individualType) { - final Trip.Individual lastIndividualLeg = (Trip.Individual) legs - .remove(legs.size() - 1); - leg = new Trip.Individual(individualType, lastIndividualLeg.departure, - lastIndividualLeg.departureTime, arrivalLocation, arrivalTime, null, 0); - } else { - leg = new Trip.Individual(individualType, departureLocation, departureTime, - arrivalLocation, arrivalTime, null, 0); - } - } else if (type == 2) { - final Product lineProduct; - if (lineOnDemand) - lineProduct = Product.ON_DEMAND; - else if (lineClass != 0) - lineProduct = intToProduct(lineClass); - else - lineProduct = normalizeType(lineCategory); - - final Line line = newLine(lineNetwork, lineProduct, normalizeLineName(lineName), - lineComment, lineAttrs.toArray(new Line.Attr[0])); - - final Location direction; - if (directionStr != null) { - final String[] directionPlaceAndName = splitStationName(directionStr); - direction = new Location(LocationType.ANY, null, directionPlaceAndName[0], - directionPlaceAndName[1]); - } else { - direction = null; - } - - final Stop departure = new Stop(departureLocation, true, - plannedDepartureTime != 0 ? new Date(plannedDepartureTime) : null, - predictedDepartureTime != 0 ? new Date(predictedDepartureTime) : null, - plannedDeparturePosition, predictedDeparturePosition, departureCancelled); - final Stop arrival = new Stop(arrivalLocation, false, - plannedArrivalTime != 0 ? new Date(plannedArrivalTime) : null, - predictedArrivalTime != 0 ? new Date(predictedArrivalTime) : null, - plannedArrivalPosition, predictedArrivalPosition, arrivalCancelled); - - leg = new Trip.Public(line, direction, departure, arrival, intermediateStops, null, - disruptionText); - } else { - throw new IllegalStateException("unhandled type: " + type); - } - legs.add(leg); + if (realtimeStatus != 2) // Verbindung fällt aus + trips.add(trip); } - final Trip trip = new Trip(connectionId, resDeparture, resArrival, legs, null, null, - (int) numChanges); + // if result is only one single individual leg, don't query for more + final boolean canQueryMore = trips.size() != 1 || trips.get(0).legs.size() != 1 + || !(trips.get(0).legs.get(0) instanceof Trip.Individual); - if (realtimeStatus != 2) // Verbindung fällt aus - trips.add(trip); + result.set(new QueryTripsResult(header, uri, from, via, to, + new QueryTripsBinaryContext(requestId, seqNr, ld, bis.getCount(), canQueryMore), trips)); + } else { + log.debug("Hafas error: {}", errorCode); + if (errorCode == 1) { + throw new SessionExpiredException(); + } else if (errorCode == 2) { + // F2: Your search results could not be stored internally. + throw new SessionExpiredException(); + } else if (errorCode == 8) { + result.set(new QueryTripsResult(header, QueryTripsResult.Status.AMBIGUOUS)); + return; + } else if (errorCode == 13) { + // IN13: Our booking system is currently being used by too many users at the same + // time. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN)); + return; + } else if (errorCode == 19) { + result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN)); + return; + } else if (errorCode == 207) { + // H207: Unfortunately your connection request can currently not be processed. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN)); + return; + } else if (errorCode == 887) { + // H887: Your inquiry was too complex. Please try entering less intermediate stations. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 890) { + // H890: No connections have been found that correspond to your request. It is + // possible + // that the requested service does not operate from or to the places you stated on the + // requested date of travel. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 891) { + // H891: Unfortunately there was no route found. Missing timetable data could be the + // reason. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 892) { + // H892: Your inquiry was too complex. Please try entering less intermediate stations. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 899) { + // H899: there was an unsuccessful or incomplete search due to a timetable change. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 900) { + // Unsuccessful or incomplete search (timetable change) + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 9220) { + // H9220: Nearby to the given address stations could not be found. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS)); + return; + } else if (errorCode == 9240) { + // H9240: Unfortunately there was no route found. Perhaps your start or destination is + // not + // served at all or with the selected means of transport on the required date/time. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS)); + return; + } else if (errorCode == 9260) { + // H9260: Unknown departure station + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM)); + return; + } else if (errorCode == 9280) { + // H9280: Unknown intermediate station + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA)); + return; + } else if (errorCode == 9300) { + // H9300: Unknown arrival station + result.set(new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO)); + return; + } else if (errorCode == 9320) { + // The input is incorrect or incomplete + result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE)); + return; + } else if (errorCode == 9360) { + // H9360: Unfortunately your connection request can currently not be processed. + result.set(new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE)); + return; + } else if (errorCode == 9380) { + // H9380: Dep./Arr./Intermed. or equivalent station defined more than once + result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE)); + return; + } else if (errorCode == 895) { + // H895: Departure/Arrival are too near + result.set(new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE)); + return; + } else { + throw new IllegalStateException("error " + errorCode + " on " + uri); + } } - - // if result is only one single individual leg, don't query for more - final boolean canQueryMore = trips.size() != 1 || trips.get(0).legs.size() != 1 - || !(trips.get(0).legs.get(0) instanceof Trip.Individual); - - final QueryTripsResult result = new QueryTripsResult(header, uri, from, via, to, - new QueryTripsBinaryContext(requestId, seqNr, ld, bis.getCount(), canQueryMore), trips); - - return result; - } else { - log.debug("Hafas error: {}", errorCode); - if (errorCode == 1) - throw new SessionExpiredException(); - else if (errorCode == 2) - // F2: Your search results could not be stored internally. - throw new SessionExpiredException(); - else if (errorCode == 8) - return new QueryTripsResult(header, QueryTripsResult.Status.AMBIGUOUS); - else if (errorCode == 13) - // IN13: Our booking system is currently being used by too many users at the same time. - return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN); - else if (errorCode == 19) - return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN); - else if (errorCode == 207) - // H207: Unfortunately your connection request can currently not be processed. - return new QueryTripsResult(header, QueryTripsResult.Status.SERVICE_DOWN); - else if (errorCode == 887) - // H887: Your inquiry was too complex. Please try entering less intermediate stations. - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 890) - // H890: No connections have been found that correspond to your request. It is possible - // that the requested service does not operate from or to the places you stated on the - // requested date of travel. - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 891) - // H891: Unfortunately there was no route found. Missing timetable data could be the - // reason. - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 892) - // H892: Your inquiry was too complex. Please try entering less intermediate stations. - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 899) - // H899: there was an unsuccessful or incomplete search due to a timetable change. - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 900) - // Unsuccessful or incomplete search (timetable change) - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 9220) - // H9220: Nearby to the given address stations could not be found. - return new QueryTripsResult(header, QueryTripsResult.Status.UNRESOLVABLE_ADDRESS); - else if (errorCode == 9240) - // H9240: Unfortunately there was no route found. Perhaps your start or destination is not - // served at all or with the selected means of transport on the required date/time. - return new QueryTripsResult(header, QueryTripsResult.Status.NO_TRIPS); - else if (errorCode == 9260) - // H9260: Unknown departure station - return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_FROM); - else if (errorCode == 9280) - // H9280: Unknown intermediate station - return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_VIA); - else if (errorCode == 9300) - // H9300: Unknown arrival station - return new QueryTripsResult(header, QueryTripsResult.Status.UNKNOWN_TO); - else if (errorCode == 9320) - // The input is incorrect or incomplete - return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE); - else if (errorCode == 9360) - // H9360: Unfortunately your connection request can currently not be processed. - return new QueryTripsResult(header, QueryTripsResult.Status.INVALID_DATE); - else if (errorCode == 9380) - // H9380: Dep./Arr./Intermed. or equivalent station defined more than once - return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE); - else if (errorCode == 895) - // H895: Departure/Arrival are too near - return new QueryTripsResult(header, QueryTripsResult.Status.TOO_CLOSE); - else - throw new IllegalStateException("error " + errorCode + " on " + uri); } - } finally { - if (is != null) - is.close(); - } + }, HttpUrl.parse(uri)); + + return result.get(); } private Location location(final LittleEndianDataInputStream is, final StringTable strings) throws IOException { @@ -2853,7 +2960,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { protected final NearbyLocationsResult xmlNearbyStations(final String uri) throws IOException { // scrape page - final CharSequence page = httpClient.get(uri); + final CharSequence page = httpClient.get(HttpUrl.parse(uri)); final List stations = new ArrayList(); @@ -2929,7 +3036,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { } protected final NearbyLocationsResult jsonNearbyLocations(final String uri) throws IOException { - final CharSequence page = httpClient.get(uri, jsonNearbyLocationsEncoding); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), jsonNearbyLocationsEncoding); try { final JSONObject head = new JSONObject(page.toString()); @@ -3010,7 +3117,7 @@ public abstract class AbstractHafasProvider extends AbstractNetworkProvider { protected final NearbyLocationsResult htmlNearbyStations(final String uri) throws IOException { final List stations = new ArrayList(); - final CharSequence page = httpClient.get(uri); + final CharSequence page = httpClient.get(HttpUrl.parse(uri)); String oldZebra = null; final Matcher mCoarse = htmlNearbyStationsPattern.matcher(page); diff --git a/enabler/src/de/schildbach/pte/AbstractNavitiaProvider.java b/enabler/src/de/schildbach/pte/AbstractNavitiaProvider.java index 0aa351e5..809458dd 100644 --- a/enabler/src/de/schildbach/pte/AbstractNavitiaProvider.java +++ b/enabler/src/de/schildbach/pte/AbstractNavitiaProvider.java @@ -66,6 +66,8 @@ import de.schildbach.pte.exception.NotFoundException; import de.schildbach.pte.exception.ParserException; import de.schildbach.pte.util.ParserUtils; +import okhttp3.HttpUrl; + /** * @author Antonio El Khoury * @author Andreas Schildbach @@ -641,7 +643,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { private String getStopAreaId(final String stopPointId) throws IOException { final String uri = uri() + "stop_points/" + ParserUtils.urlEncode(stopPointId) + "?depth=1"; - final CharSequence page = httpClient.get(uri); + final CharSequence page = httpClient.get(HttpUrl.parse(uri)); try { final JSONObject head = new JSONObject(page.toString()); @@ -698,7 +700,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { if (maxLocations > 0) queryUri.append("&count=").append(maxLocations); queryUri.append("&depth=3"); - final CharSequence page = httpClient.get(queryUri.toString()); + final CharSequence page = httpClient.get(HttpUrl.parse(queryUri.toString())); try { final JSONObject head = new JSONObject(page.toString()); @@ -773,7 +775,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { queryUri.append("&duration=86400"); queryUri.append("&depth=0"); - final CharSequence page = httpClient.get(queryUri.toString()); + final CharSequence page = httpClient.get(HttpUrl.parse(queryUri.toString())); final JSONObject head = new JSONObject(page.toString()); @@ -821,7 +823,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { throw new ParserException(parseExc); } catch (final NotFoundException fnfExc) { try { - final JSONObject head = new JSONObject(fnfExc.scrapeErrorStream().toString()); + final JSONObject head = new JSONObject(fnfExc.getBodyPeek().toString()); final JSONObject error = head.getJSONObject("error"); final String id = error.getString("id"); @@ -841,7 +843,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { final String queryUri = uri() + "places?q=" + ParserUtils.urlEncode(nameCstr) + "&type[]=stop_area&type[]=address&type[]=poi&type[]=administrative_region" + "&depth=1"; - final CharSequence page = httpClient.get(queryUri); + final CharSequence page = httpClient.get(HttpUrl.parse(queryUri)); try { final List locations = new ArrayList(); @@ -945,7 +947,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { } } - final CharSequence page = httpClient.get(queryUri.toString()); + final CharSequence page = httpClient.get(HttpUrl.parse(queryUri.toString())); try { final JSONObject head = new JSONObject(page.toString()); @@ -1013,7 +1015,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { return new QueryTripsResult(resultHeader, QueryTripsResult.Status.NO_TRIPS); } catch (final NotFoundException fnfExc) { try { - final JSONObject head = new JSONObject(fnfExc.scrapeErrorStream().toString()); + final JSONObject head = new JSONObject(fnfExc.getBodyPeek().toString()); final JSONObject error = head.getJSONObject("error"); final String id = error.getString("id"); @@ -1046,7 +1048,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { final Location from = context.from; final Location to = context.to; final String queryUri = later ? context.nextQueryUri : context.prevQueryUri; - final CharSequence page = httpClient.get(queryUri); + final CharSequence page = httpClient.get(HttpUrl.parse(queryUri)); try { if (from.isIdentified() && to.isIdentified()) { @@ -1076,7 +1078,7 @@ public abstract class AbstractNavitiaProvider extends AbstractNetworkProvider { @Override public Point[] getArea() throws IOException { final String queryUri = uri(); - final CharSequence page = httpClient.get(queryUri); + final CharSequence page = httpClient.get(HttpUrl.parse(queryUri)); try { // Get shape string. diff --git a/enabler/src/de/schildbach/pte/AbstractTsiProvider.java b/enabler/src/de/schildbach/pte/AbstractTsiProvider.java index 5fc3b2ac..b60615e4 100644 --- a/enabler/src/de/schildbach/pte/AbstractTsiProvider.java +++ b/enabler/src/de/schildbach/pte/AbstractTsiProvider.java @@ -59,6 +59,8 @@ import de.schildbach.pte.dto.Trip; import de.schildbach.pte.exception.ParserException; import de.schildbach.pte.util.ParserUtils; +import okhttp3.HttpUrl; + /** * @author Kjell Braden */ @@ -197,7 +199,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); uri.append(parameters); - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final List locations = new ArrayList(); final JSONObject head = new JSONObject(page.toString()); @@ -277,7 +279,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); uri.append(parameters); - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final List stations = new ArrayList(); final JSONObject head = new JSONObject(page.toString()); @@ -315,7 +317,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(stopFinderEndpoint); uri.append(parameters); - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -674,7 +676,7 @@ public abstract class AbstractTsiProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(tripEndpoint); uri.append(parameters); - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); diff --git a/enabler/src/de/schildbach/pte/HslProvider.java b/enabler/src/de/schildbach/pte/HslProvider.java index a0a6aff2..fa518dfa 100644 --- a/enabler/src/de/schildbach/pte/HslProvider.java +++ b/enabler/src/de/schildbach/pte/HslProvider.java @@ -18,7 +18,6 @@ package de.schildbach.pte; import java.io.IOException; -import java.io.InputStream; import java.net.URLEncoder; import java.text.ParsePosition; import java.text.SimpleDateFormat; @@ -33,6 +32,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; @@ -63,6 +63,9 @@ import de.schildbach.pte.exception.ParserException; import de.schildbach.pte.util.HttpClient; import de.schildbach.pte.util.XmlPullUtil; +import okhttp3.HttpUrl; +import okhttp3.ResponseBody; + /** * @author Mats Sjöberg */ @@ -137,34 +140,32 @@ public class HslProvider extends AbstractNetworkProvider { private Location queryStop(final String stationId) throws IOException { final StringBuilder uri = apiUri("stop"); - uri.append("&code=").append(stationId); uri.append(String.format("&dep_limit=1")); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.charStream()); - try { - is = httpClient.getInputStream(uri.toString()); - firstChars = HttpClient.peekFirstChars(is); + XmlPullUtil.enter(pp, "response"); + XmlPullUtil.enter(pp, "node"); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); + final String id = xmlValueTag(pp, "code"); + final String name = xmlValueTag(pp, "name_fi"); + final Point pt = coordStrToPoint(xmlValueTag(pp, "coords")); + result.set(new Location(LocationType.STATION, id, pt.lat, pt.lon, null, name)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } + } + }; - XmlPullUtil.enter(pp, "response"); - XmlPullUtil.enter(pp, "node"); - - final String id = xmlValueTag(pp, "code"); - final String name = xmlValueTag(pp, "name_fi"); - final Point pt = coordStrToPoint(xmlValueTag(pp, "coords")); - - return new Location(LocationType.STATION, id, pt.lat, pt.lon, null, name); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString())); + return result.get(); } // Determine stations near to given location. At least one of @@ -174,7 +175,6 @@ public class HslProvider extends AbstractNetworkProvider { public NearbyLocationsResult queryNearbyLocations(EnumSet types, Location location, int maxDistance, int maxStations) throws IOException { final StringBuilder uri = apiUri("stops_area"); - if (!location.hasLocation()) { if (location.type != LocationType.STATION) throw new IllegalArgumentException("cannot handle: " + location.type); @@ -185,42 +185,42 @@ public class HslProvider extends AbstractNetworkProvider { uri.append("¢er_coordinate=").append(locationToCoords(location)); uri.append(String.format("&limit=%d", maxStations)); uri.append(String.format("&diameter=%d", maxDistance * 2)); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.charStream()); - try { - is = httpClient.getInputStream(uri.toString()); - firstChars = HttpClient.peekFirstChars(is); + final List stations = new ArrayList(); + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); + XmlPullUtil.enter(pp, "response"); - final List stations = new ArrayList(); - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); + while (XmlPullUtil.test(pp, "node")) { + XmlPullUtil.enter(pp, "node"); - XmlPullUtil.enter(pp, "response"); + final String id = xmlValueTag(pp, "code"); + final String name = xmlValueTag(pp, "name"); + final Point pt = coordStrToPoint(xmlValueTag(pp, "coords")); + final String place = xmlValueTag(pp, "address"); - while (XmlPullUtil.test(pp, "node")) { - XmlPullUtil.enter(pp, "node"); + XmlPullUtil.skipExit(pp, "node"); - final String id = xmlValueTag(pp, "code"); - final String name = xmlValueTag(pp, "name"); - final Point pt = coordStrToPoint(xmlValueTag(pp, "coords")); - final String place = xmlValueTag(pp, "address"); + stations.add(new Location(LocationType.STATION, id, pt.lat, pt.lon, place, name)); + } - XmlPullUtil.skipExit(pp, "node"); - - stations.add(new Location(LocationType.STATION, id, pt.lat, pt.lon, place, name)); + result.set(new NearbyLocationsResult(header, stations)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } } + }; - return new NearbyLocationsResult(header, stations); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString())); + return result.get(); } private Line newLine(String code, int type, String message) { @@ -254,78 +254,75 @@ public class HslProvider extends AbstractNetworkProvider { // Get departures at a given station, probably live @Override - public QueryDeparturesResult queryDepartures(String stationId, @Nullable Date queryDate, int maxDepartures, + public QueryDeparturesResult queryDepartures(String stationId, @Nullable Date queryDate, final int maxDepartures, boolean equivs) throws IOException { final StringBuilder uri = apiUri("stop"); - uri.append("&code=").append(stationId); if (queryDate != null) { uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(queryDate)); uri.append("&time=").append(new SimpleDateFormat("HHmm").format(queryDate)); } uri.append(String.format("&dep_limit=%d", maxDepartures)); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.charStream()); - try { - is = httpClient.getInputStream(uri.toString()); - firstChars = HttpClient.peekFirstChars(is); + XmlPullUtil.enter(pp, "response"); + XmlPullUtil.enter(pp, "node"); - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); + // FIXME: id is never used!? + final String id = xmlValueTag(pp, "code"); + final String name = xmlValueTag(pp, "name_fi"); - XmlPullUtil.enter(pp, "response"); - XmlPullUtil.enter(pp, "node"); + final Map lines = new HashMap(); - // FIXME: id is never used!? - final String id = xmlValueTag(pp, "code"); - final String name = xmlValueTag(pp, "name_fi"); + XmlPullUtil.skipUntil(pp, "lines"); + XmlPullUtil.enter(pp, "lines"); + while (XmlPullUtil.test(pp, "node")) { + final String[] parts = XmlPullUtil.valueTag(pp, "node").split(":"); + lines.put(parts[0], newLine(parts[0], 0, parts[1])); + } + XmlPullUtil.skipExit(pp, "lines"); - final Map lines = new HashMap(); + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); + final QueryDeparturesResult r = new QueryDeparturesResult(header); - XmlPullUtil.skipUntil(pp, "lines"); - XmlPullUtil.enter(pp, "lines"); - while (XmlPullUtil.test(pp, "node")) { - final String[] parts = XmlPullUtil.valueTag(pp, "node").split(":"); - lines.put(parts[0], newLine(parts[0], 0, parts[1])); + XmlPullUtil.skipUntil(pp, "departures"); + XmlPullUtil.enter(pp, "departures"); + + final List departures = new ArrayList(maxDepartures); + while (XmlPullUtil.test(pp, "node")) { + XmlPullUtil.enter(pp, "node"); + final String code = xmlValueTag(pp, "code"); + final String time = xmlValueTag(pp, "time"); + final String date = xmlValueTag(pp, "date"); + XmlPullUtil.skipExit(pp, "node"); + + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm"); + Date depDate = sdf.parse(date + time, new ParsePosition(0)); + + final Line line = lines.get(code); + final Location destination = new Location(LocationType.ANY, line.message, null, null); + final Departure departure = new Departure(depDate, null, line, null, destination, null, null); + departures.add(departure); + } + + Location station = new Location(LocationType.STATION, id, null, name); + r.stationDepartures.add(new StationDepartures(station, departures, null)); + result.set(r); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } } - XmlPullUtil.skipExit(pp, "lines"); + }; - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); - final QueryDeparturesResult result = new QueryDeparturesResult(header); - - XmlPullUtil.skipUntil(pp, "departures"); - XmlPullUtil.enter(pp, "departures"); - - final List departures = new ArrayList(maxDepartures); - while (XmlPullUtil.test(pp, "node")) { - XmlPullUtil.enter(pp, "node"); - final String code = xmlValueTag(pp, "code"); - final String time = xmlValueTag(pp, "time"); - final String date = xmlValueTag(pp, "date"); - XmlPullUtil.skipExit(pp, "node"); - - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm"); - Date depDate = sdf.parse(date + time, new ParsePosition(0)); - - final Line line = lines.get(code); - final Location destination = new Location(LocationType.ANY, line.message, null, null); - final Departure departure = new Departure(depDate, null, line, null, destination, null, null); - departures.add(departure); - } - - Location station = new Location(LocationType.STATION, id, null, name); - result.stationDepartures.add(new StationDepartures(station, departures, null)); - - return result; - - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString())); + return result.get(); } /** @@ -339,71 +336,68 @@ public class HslProvider extends AbstractNetworkProvider { @Override public SuggestLocationsResult suggestLocations(CharSequence constraint) throws IOException { final StringBuilder uri = apiUri("geocode"); - // Since HSL is picky about the input we clean out any // character that isn't alphabetic, numeral, -, ', / // or a space. Those should be all chars needed for a // name. String constraintStr = constraint.toString().replaceAll("[^\\p{Ll}\\p{Lu}\\p{Lt}\\p{Lo}\\p{Nd}\\d-'/ ]", ""); uri.append("&key=").append(URLEncoder.encode(constraintStr, "utf-8")); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); + final List locations = new ArrayList(); - try { - is = httpClient.getInputStream(uri.toString()); - firstChars = HttpClient.peekFirstChars(is); + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.charStream()); - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); - final List locations = new ArrayList(); + XmlPullUtil.enter(pp, "response"); - if (firstChars.isEmpty()) - return new SuggestLocationsResult(header, locations); + int weight = 10000; - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); + while (XmlPullUtil.test(pp, "node")) { + XmlPullUtil.enter(pp, "node"); + final String locType = xmlValueTag(pp, "locType"); + String name = xmlValueTag(pp, "name"); + final Point pt = coordStrToPoint(xmlValueTag(pp, "coords")); - XmlPullUtil.enter(pp, "response"); + LocationType type = LocationType.ANY; + if (locType.equals("poi")) + type = LocationType.POI; + if (locType.equals("address")) + type = LocationType.ADDRESS; + if (locType.equals("stop")) + type = LocationType.STATION; - int weight = 10000; + XmlPullUtil.skipUntil(pp, "details"); + XmlPullUtil.enter(pp, "details"); + XmlPullUtil.optSkip(pp, "address"); + final String id = XmlPullUtil.optValueTag(pp, "code", null); + final String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null); + XmlPullUtil.skipExit(pp, "details"); - while (XmlPullUtil.test(pp, "node")) { - XmlPullUtil.enter(pp, "node"); - final String locType = xmlValueTag(pp, "locType"); - String name = xmlValueTag(pp, "name"); - final Point pt = coordStrToPoint(xmlValueTag(pp, "coords")); + XmlPullUtil.skipExit(pp, "node"); - LocationType type = LocationType.ANY; - if (locType.equals("poi")) - type = LocationType.POI; - if (locType.equals("address")) - type = LocationType.ADDRESS; - if (locType.equals("stop")) - type = LocationType.STATION; + if (shortCode != null) + name = name + " (" + shortCode + ")"; - XmlPullUtil.skipUntil(pp, "details"); - XmlPullUtil.enter(pp, "details"); - XmlPullUtil.optSkip(pp, "address"); - final String id = XmlPullUtil.optValueTag(pp, "code", null); - final String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null); - XmlPullUtil.skipExit(pp, "details"); + locations + .add(new SuggestedLocation(new Location(type, id, pt.lat, pt.lon, null, name), weight)); + weight -= 1; + } - XmlPullUtil.skipExit(pp, "node"); - - if (shortCode != null) - name = name + " (" + shortCode + ")"; - - locations.add(new SuggestedLocation(new Location(type, id, pt.lat, pt.lon, null, name), weight)); - weight -= 1; + result.set(new SuggestLocationsResult(header, locations)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } } + }; - return new SuggestLocationsResult(header, locations); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString())); + return result.get(); } @SuppressWarnings("serial") @@ -553,165 +547,166 @@ public class HslProvider extends AbstractNetworkProvider { } private QueryTripsResult queryHslTrips(final Location from, final Location via, final Location to, - QueryTripsHslContext context, Date date, boolean later) throws IOException { + final QueryTripsHslContext context, Date date, final boolean later) throws IOException { final StringBuilder uri = new StringBuilder(context.uri); - uri.append("&date=").append(new SimpleDateFormat("yyyyMMdd").format(date)); uri.append("&time=").append(new SimpleDateFormat("HHmm").format(date)); + final AtomicReference result = new AtomicReference(); - InputStream is = null; - String firstChars = null; + final HttpClient.Callback callback = new HttpClient.Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + try { + final XmlPullParser pp = parserFactory.newPullParser(); + pp.setInput(body.charStream()); + + XmlPullUtil.enter(pp, "response"); + + final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); + + final List trips = new ArrayList(context.trips); + + // we use this for quick checking if trip already exists + Set tripSet = new HashSet(); + for (Trip t : trips) + tripSet.add(t.getId()); + + int insert = later ? trips.size() : 0; + + while (XmlPullUtil.test(pp, "node")) { + XmlPullUtil.enter(pp, "node"); + + XmlPullUtil.enter(pp, "node"); + + List legs = new ArrayList(); + + XmlPullUtil.skipUntil(pp, "legs"); + XmlPullUtil.enter(pp, "legs"); + int numTransfers = 0; + + while (XmlPullUtil.test(pp, "node")) { + XmlPullUtil.enter(pp, "node"); + + int distance = Integer.parseInt(xmlValueTag(pp, "length")); + String legType = xmlValueTag(pp, "type"); + String lineCode = XmlPullUtil.optValueTag(pp, "code", null); + + List path = new ArrayList(); + + Location departure = null; + Date departureTime = null; + Stop departureStop = null; + + Location arrival = null; + Date arrivalTime = null; + + LinkedList stops = new LinkedList(); + + XmlPullUtil.skipUntil(pp, "locs"); + XmlPullUtil.enter(pp, "locs"); + while (XmlPullUtil.test(pp, "node")) { + XmlPullUtil.enter(pp, "node"); + Point pt = xmlCoordsToPoint(pp); + + String arrTime = xmlValueTag(pp, "arrTime"); + String depTime = xmlValueTag(pp, "depTime"); + String name = XmlPullUtil.optValueTag(pp, "name", null); + String code = XmlPullUtil.optValueTag(pp, "code", null); + String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null); + String stopAddress = XmlPullUtil.optValueTag(pp, "stopAddress", null); + + if (name == null) { + name = (path.size() == 0 && from != null && from.name != null) ? from.name : null; + } + + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm"); + Date arrDate = sdf.parse(arrTime, new ParsePosition(0)); + Date depDate = sdf.parse(depTime, new ParsePosition(0)); + + LocationType type = LocationType.ANY; + if (code != null) + type = LocationType.STATION; + Location loc = new Location(type, code, pt.lat, pt.lon, stopAddress, name); + + if (path.size() == 0) { + departure = loc; + departureTime = depDate; + if (type == LocationType.STATION) + departureStop = new Stop(loc, true, departureTime, null, null, null); + + } else { + arrival = loc; + arrivalTime = arrDate; + if (type == LocationType.STATION) { + stops.add(new Stop(loc, arrDate, null, depDate, null)); + } + } + + path.add(pt); + XmlPullUtil.skipExit(pp, "node"); + } + XmlPullUtil.skipExit(pp, "locs"); + XmlPullUtil.skipExit(pp, "node"); + + if (legType.equals("walk")) { + // ugly hack to set the name of the last arrival + if (arrival != null && arrival.name == null) { + arrival = new Location(arrival.type, arrival.id, arrival.lat, arrival.lon, + arrival.place, to.name); + } + + legs.add(new Trip.Individual(Trip.Individual.Type.WALK, departure, departureTime, + arrival, arrivalTime, path, distance)); + } else { + Stop arrivalStop = null; + if (stops.size() > 0) { + Stop last = stops.getLast(); + arrivalStop = new Stop(last.location, false, last.plannedArrivalTime, null, null, + null); + stops.removeLast(); + } + + Line line = null; + if (lineCode != null) + line = newLine(lineCode, Integer.parseInt(legType), null); + + legs.add(new Trip.Public(line, null, departureStop, arrivalStop, stops, path, null)); + numTransfers++; + } + } + XmlPullUtil.skipExit(pp, "legs"); + XmlPullUtil.skipExit(pp, "node"); + XmlPullUtil.skipExit(pp, "node"); + + Trip t = new Trip(null, from, to, legs, null, null, numTransfers - 1); + if (!tripSet.contains(t.getId())) { + Date thisTime = t.getFirstDepartureTime(); + while (insert < trips.size() && thisTime.after(trips.get(insert).getFirstDepartureTime())) + insert++; + + trips.add(insert++, t); + tripSet.add(t.getId()); + } + } + + Date lastDate = trips.get(trips.size() - 1).getFirstDepartureTime(); + Date firstDate = trips.get(0).getFirstDepartureTime(); + if (context.nextDate == null || lastDate.after(context.nextDate)) + context.nextDate = lastDate; + if (context.prevDate == null || firstDate.before(context.prevDate)) + context.prevDate = firstDate; + context.trips = trips; + + result.set(new QueryTripsResult(header, uri.toString(), from, via, to, context, trips)); + } catch (final XmlPullParserException x) { + throw new ParserException("cannot parse xml: " + bodyPeek, x); + } + } + }; context.date = date; - try { - is = httpClient.getInputStream(uri.toString()); - firstChars = HttpClient.peekFirstChars(is); - - final XmlPullParser pp = parserFactory.newPullParser(); - pp.setInput(is, null); - - XmlPullUtil.enter(pp, "response"); - - final ResultHeader header = new ResultHeader(network, SERVER_PRODUCT); - - final List trips = new ArrayList(context.trips); - - // we use this for quick checking if trip already exists - Set tripSet = new HashSet(); - for (Trip t : trips) - tripSet.add(t.getId()); - - int insert = later ? trips.size() : 0; - - while (XmlPullUtil.test(pp, "node")) { - XmlPullUtil.enter(pp, "node"); - - XmlPullUtil.enter(pp, "node"); - - List legs = new ArrayList(); - - XmlPullUtil.skipUntil(pp, "legs"); - XmlPullUtil.enter(pp, "legs"); - int numTransfers = 0; - - while (XmlPullUtil.test(pp, "node")) { - XmlPullUtil.enter(pp, "node"); - - int distance = Integer.parseInt(xmlValueTag(pp, "length")); - String legType = xmlValueTag(pp, "type"); - String lineCode = XmlPullUtil.optValueTag(pp, "code", null); - - List path = new ArrayList(); - - Location departure = null; - Date departureTime = null; - Stop departureStop = null; - - Location arrival = null; - Date arrivalTime = null; - - LinkedList stops = new LinkedList(); - - XmlPullUtil.skipUntil(pp, "locs"); - XmlPullUtil.enter(pp, "locs"); - while (XmlPullUtil.test(pp, "node")) { - XmlPullUtil.enter(pp, "node"); - Point pt = xmlCoordsToPoint(pp); - - String arrTime = xmlValueTag(pp, "arrTime"); - String depTime = xmlValueTag(pp, "depTime"); - String name = XmlPullUtil.optValueTag(pp, "name", null); - String code = XmlPullUtil.optValueTag(pp, "code", null); - String shortCode = XmlPullUtil.optValueTag(pp, "shortCode", null); - String stopAddress = XmlPullUtil.optValueTag(pp, "stopAddress", null); - - if (name == null) { - name = (path.size() == 0 && from != null && from.name != null) ? from.name : null; - } - - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmm"); - Date arrDate = sdf.parse(arrTime, new ParsePosition(0)); - Date depDate = sdf.parse(depTime, new ParsePosition(0)); - - LocationType type = LocationType.ANY; - if (code != null) - type = LocationType.STATION; - Location loc = new Location(type, code, pt.lat, pt.lon, stopAddress, name); - - if (path.size() == 0) { - departure = loc; - departureTime = depDate; - if (type == LocationType.STATION) - departureStop = new Stop(loc, true, departureTime, null, null, null); - - } else { - arrival = loc; - arrivalTime = arrDate; - if (type == LocationType.STATION) { - stops.add(new Stop(loc, arrDate, null, depDate, null)); - } - } - - path.add(pt); - XmlPullUtil.skipExit(pp, "node"); - } - XmlPullUtil.skipExit(pp, "locs"); - XmlPullUtil.skipExit(pp, "node"); - - if (legType.equals("walk")) { - // ugly hack to set the name of the last arrival - if (arrival != null && arrival.name == null) { - arrival = new Location(arrival.type, arrival.id, arrival.lat, arrival.lon, arrival.place, - to.name); - } - - legs.add(new Trip.Individual(Trip.Individual.Type.WALK, departure, departureTime, arrival, - arrivalTime, path, distance)); - } else { - Stop arrivalStop = null; - if (stops.size() > 0) { - Stop last = stops.getLast(); - arrivalStop = new Stop(last.location, false, last.plannedArrivalTime, null, null, null); - stops.removeLast(); - } - - Line line = null; - if (lineCode != null) - line = newLine(lineCode, Integer.parseInt(legType), null); - - legs.add(new Trip.Public(line, null, departureStop, arrivalStop, stops, path, null)); - numTransfers++; - } - } - XmlPullUtil.skipExit(pp, "legs"); - XmlPullUtil.skipExit(pp, "node"); - XmlPullUtil.skipExit(pp, "node"); - - Trip t = new Trip(null, from, to, legs, null, null, numTransfers - 1); - if (!tripSet.contains(t.getId())) { - Date thisTime = t.getFirstDepartureTime(); - while (insert < trips.size() && thisTime.after(trips.get(insert).getFirstDepartureTime())) - insert++; - - trips.add(insert++, t); - tripSet.add(t.getId()); - } - } - - Date lastDate = trips.get(trips.size() - 1).getFirstDepartureTime(); - Date firstDate = trips.get(0).getFirstDepartureTime(); - if (context.nextDate == null || lastDate.after(context.nextDate)) - context.nextDate = lastDate; - if (context.prevDate == null || firstDate.before(context.prevDate)) - context.prevDate = firstDate; - context.trips = trips; - return new QueryTripsResult(header, uri.toString(), from, via, to, context, trips); - } catch (final XmlPullParserException x) { - throw new ParserException("cannot parse xml: " + firstChars, x); - } finally { - if (is != null) - is.close(); - } + httpClient.getInputStream(callback, HttpUrl.parse(uri.toString())); + return result.get(); } } diff --git a/enabler/src/de/schildbach/pte/InvgProvider.java b/enabler/src/de/schildbach/pte/InvgProvider.java index efa055ee..eb5bef4f 100644 --- a/enabler/src/de/schildbach/pte/InvgProvider.java +++ b/enabler/src/de/schildbach/pte/InvgProvider.java @@ -54,6 +54,8 @@ import de.schildbach.pte.dto.StationDepartures; import de.schildbach.pte.dto.Style; import de.schildbach.pte.util.ParserUtils; +import okhttp3.HttpUrl; + /** * @author Andreas Schildbach */ @@ -171,7 +173,7 @@ public class InvgProvider extends AbstractHafasProvider { // scrape page final StringBuilder uri = new StringBuilder(stationBoardEndpoint); appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null); - final CharSequence page = httpClient.get(uri.toString()); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString())); // parse page final Matcher mHeadCoarse = P_DEPARTURES_HEAD_COARSE.matcher(page); diff --git a/enabler/src/de/schildbach/pte/SeptaProvider.java b/enabler/src/de/schildbach/pte/SeptaProvider.java index d4822362..7e8a371d 100644 --- a/enabler/src/de/schildbach/pte/SeptaProvider.java +++ b/enabler/src/de/schildbach/pte/SeptaProvider.java @@ -51,6 +51,8 @@ import de.schildbach.pte.dto.ResultHeader; import de.schildbach.pte.dto.StationDepartures; import de.schildbach.pte.util.ParserUtils; +import okhttp3.HttpUrl; + /** * @author Andreas Schildbach */ @@ -134,7 +136,7 @@ public class SeptaProvider extends AbstractHafasProvider { // scrape page final StringBuilder uri = new StringBuilder(stationBoardEndpoint); appendXmlStationBoardParameters(uri, time, stationId, maxDepartures, false, null); - final CharSequence page = httpClient.get(uri.toString()); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString())); // parse page final Matcher mPageCoarse = P_DEPARTURES_PAGE_COARSE.matcher(page); diff --git a/enabler/src/de/schildbach/pte/VrsProvider.java b/enabler/src/de/schildbach/pte/VrsProvider.java index dac236e0..d00c0ea1 100644 --- a/enabler/src/de/schildbach/pte/VrsProvider.java +++ b/enabler/src/de/schildbach/pte/VrsProvider.java @@ -73,6 +73,8 @@ import de.schildbach.pte.dto.Trip; import de.schildbach.pte.dto.Trip.Leg; import de.schildbach.pte.util.ParserUtils; +import okhttp3.HttpUrl; + /** * @author Michael Dyrna */ @@ -370,7 +372,7 @@ public class VrsProvider extends AbstractNetworkProvider { uri.append("&s=").append(Math.min(16, maxLocations)); // artificial server limit } - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final List locations = new ArrayList(); @@ -427,7 +429,7 @@ public class VrsProvider extends AbstractNetworkProvider { uri.append("&t="); appendDate(uri, time); } - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -514,7 +516,7 @@ public class VrsProvider extends AbstractNetworkProvider { final StringBuilder uri = new StringBuilder(API_BASE); uri.append("?eID=tx_vrsinfo_his_info&i=").append(ParserUtils.urlEncode(stationId)); - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final JSONObject head = new JSONObject(page.toString()); @@ -573,7 +575,7 @@ public class VrsProvider extends AbstractNetworkProvider { final String uri = API_BASE + "?eID=tx_vrsinfo_ass2_objects&sc=" + sc + "&ac=" + ac + "&pc=" + ac + "&t=sap&q=" + ParserUtils.urlEncode(new Location(LocationType.ANY, null, null, constraint.toString()).name); - final CharSequence page = httpClient.get(uri, Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri), Charsets.UTF_8); try { final List locations = new ArrayList(); @@ -691,7 +693,7 @@ public class VrsProvider extends AbstractNetworkProvider { uri.append("p"); } - final CharSequence page = httpClient.get(uri.toString(), Charsets.UTF_8); + final CharSequence page = httpClient.get(HttpUrl.parse(uri.toString()), Charsets.UTF_8); try { final List trips = new ArrayList(); diff --git a/enabler/src/de/schildbach/pte/exception/AbstractHttpException.java b/enabler/src/de/schildbach/pte/exception/AbstractHttpException.java index 3a1c6def..ffac7904 100644 --- a/enabler/src/de/schildbach/pte/exception/AbstractHttpException.java +++ b/enabler/src/de/schildbach/pte/exception/AbstractHttpException.java @@ -18,45 +18,32 @@ package de.schildbach.pte.exception; import java.io.IOException; -import java.io.Reader; -import java.net.URL; -import de.schildbach.pte.util.HttpClient; +import okhttp3.HttpUrl; /** * @author Andreas Schildbach */ @SuppressWarnings("serial") public abstract class AbstractHttpException extends IOException { - private final URL url; - private final Reader errorReader; + private final HttpUrl url; + private final CharSequence bodyPeek; - public AbstractHttpException(final URL url) { + public AbstractHttpException(final HttpUrl url) { this(url, null); } - public AbstractHttpException(final URL url, final Reader errorReader) { + public AbstractHttpException(final HttpUrl url, final CharSequence bodyPeek) { super(url.toString()); this.url = url; - this.errorReader = errorReader; + this.bodyPeek = bodyPeek; } - public URL getUrl() { + public HttpUrl getUrl() { return url; } - public Reader getErrorReader() { - return errorReader; - } - - public CharSequence scrapeErrorStream() throws IOException { - if (errorReader == null) - return null; - - final StringBuilder error = new StringBuilder(HttpClient.SCRAPE_INITIAL_CAPACITY); - HttpClient.copy(errorReader, error); - errorReader.close(); - - return error; + public CharSequence getBodyPeek() { + return bodyPeek; } } diff --git a/enabler/src/de/schildbach/pte/exception/BlockedException.java b/enabler/src/de/schildbach/pte/exception/BlockedException.java index dd4af1b9..8154d71a 100644 --- a/enabler/src/de/schildbach/pte/exception/BlockedException.java +++ b/enabler/src/de/schildbach/pte/exception/BlockedException.java @@ -17,15 +17,14 @@ package de.schildbach.pte.exception; -import java.io.Reader; -import java.net.URL; +import okhttp3.HttpUrl; /** * @author Andreas Schildbach */ @SuppressWarnings("serial") public class BlockedException extends AbstractHttpException { - public BlockedException(final URL url, final Reader errorReader) { - super(url, errorReader); + public BlockedException(final HttpUrl url, final CharSequence bodyPeek) { + super(url, bodyPeek); } } diff --git a/enabler/src/de/schildbach/pte/exception/InternalErrorException.java b/enabler/src/de/schildbach/pte/exception/InternalErrorException.java index c2bf156a..eea649bc 100644 --- a/enabler/src/de/schildbach/pte/exception/InternalErrorException.java +++ b/enabler/src/de/schildbach/pte/exception/InternalErrorException.java @@ -17,15 +17,14 @@ package de.schildbach.pte.exception; -import java.io.Reader; -import java.net.URL; +import okhttp3.HttpUrl; /** * @author Andreas Schildbach */ @SuppressWarnings("serial") public class InternalErrorException extends AbstractHttpException { - public InternalErrorException(final URL url, final Reader errorReader) { - super(url, errorReader); + public InternalErrorException(final HttpUrl url, final CharSequence bodyPeek) { + super(url, bodyPeek); } } diff --git a/enabler/src/de/schildbach/pte/exception/NotFoundException.java b/enabler/src/de/schildbach/pte/exception/NotFoundException.java index c0b8220e..840df389 100644 --- a/enabler/src/de/schildbach/pte/exception/NotFoundException.java +++ b/enabler/src/de/schildbach/pte/exception/NotFoundException.java @@ -17,15 +17,14 @@ package de.schildbach.pte.exception; -import java.io.Reader; -import java.net.URL; +import okhttp3.HttpUrl; /** * @author Andreas Schildbach */ @SuppressWarnings("serial") public class NotFoundException extends AbstractHttpException { - public NotFoundException(final URL url, final Reader errorReader) { - super(url, errorReader); + public NotFoundException(final HttpUrl url, final CharSequence bodyPeek) { + super(url, bodyPeek); } } diff --git a/enabler/src/de/schildbach/pte/exception/UnexpectedRedirectException.java b/enabler/src/de/schildbach/pte/exception/UnexpectedRedirectException.java index c4bc4f8d..3ef32254 100644 --- a/enabler/src/de/schildbach/pte/exception/UnexpectedRedirectException.java +++ b/enabler/src/de/schildbach/pte/exception/UnexpectedRedirectException.java @@ -17,21 +17,21 @@ package de.schildbach.pte.exception; -import java.net.URL; +import okhttp3.HttpUrl; /** * @author Andreas Schildbach */ @SuppressWarnings("serial") public class UnexpectedRedirectException extends AbstractHttpException { - private final URL redirectedUrl; + private final HttpUrl redirectedUrl; - public UnexpectedRedirectException(final URL originalUrl, final URL redirectedUrl) { + public UnexpectedRedirectException(final HttpUrl originalUrl, final HttpUrl redirectedUrl) { super(originalUrl); this.redirectedUrl = redirectedUrl; } - public URL getRedirectedUrl() { + public HttpUrl getRedirectedUrl() { return redirectedUrl; } diff --git a/enabler/src/de/schildbach/pte/util/HttpClient.java b/enabler/src/de/schildbach/pte/util/HttpClient.java index 98c6dff5..aa124f73 100644 --- a/enabler/src/de/schildbach/pte/util/HttpClient.java +++ b/enabler/src/de/schildbach/pte/util/HttpClient.java @@ -17,27 +17,19 @@ package de.schildbach.pte.util; -import java.io.BufferedInputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.Reader; -import java.net.HttpCookie; import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; import java.nio.charset.Charset; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.zip.GZIPInputStream; import javax.annotation.Nullable; import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSession; import org.slf4j.Logger; @@ -51,6 +43,18 @@ import de.schildbach.pte.exception.NotFoundException; import de.schildbach.pte.exception.SessionExpiredException; import de.schildbach.pte.exception.UnexpectedRedirectException; +import okhttp3.Call; +import okhttp3.Cookie; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.logging.HttpLoggingInterceptor; + /** * @author Andreas Schildbach */ @@ -61,15 +65,33 @@ public final class HttpClient { @Nullable private String sessionCookieName = null; @Nullable - private HttpCookie sessionCookie = null; + private Cookie sessionCookie = null; private boolean sslAcceptAllHostnames = false; + private static final OkHttpClient OKHTTP_CLIENT; + static { + final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor( + new HttpLoggingInterceptor.Logger() { + @Override + public void log(final String message) { + log.debug(message); + } + }); + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC); + + final OkHttpClient.Builder builder = new OkHttpClient.Builder(); + builder.followRedirects(false); + builder.followSslRedirects(true); + builder.connectTimeout(5, TimeUnit.SECONDS); + builder.writeTimeout(5, TimeUnit.SECONDS); + builder.readTimeout(15, TimeUnit.SECONDS); + builder.addNetworkInterceptor(loggingInterceptor); + OKHTTP_CLIENT = builder.build(); + } + private static final String SCRAPE_ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; public static final int SCRAPE_INITIAL_CAPACITY = 4096; - private static final int SCRAPE_COPY_SIZE = 2048; private static final int SCRAPE_PEEK_SIZE = 4096; - private static final int SCRAPE_CONNECT_TIMEOUT = 5000; - private static final int SCRAPE_READ_TIMEOUT = 15000; private static final Logger log = LoggerFactory.getLogger(HttpClient.class); @@ -89,201 +111,129 @@ public final class HttpClient { this.sslAcceptAllHostnames = sslAcceptAllHostnames; } - public CharSequence get(final String url) throws IOException { + public CharSequence get(final HttpUrl url) throws IOException { return get(url, null); } - public CharSequence get(final String urlStr, final Charset requestEncoding) throws IOException { - return get(urlStr, null, null, requestEncoding); + public CharSequence get(final HttpUrl url, final Charset requestEncoding) throws IOException { + return get(url, null, null, requestEncoding); } - public CharSequence get(final String urlStr, final String postRequest, final String requestContentType, + public CharSequence get(final HttpUrl url, final String postRequest, final String requestContentType, Charset requestEncoding) throws IOException { if (requestEncoding == null) requestEncoding = Charsets.ISO_8859_1; final StringBuilder buffer = new StringBuilder(SCRAPE_INITIAL_CAPACITY); - final InputStream is = getInputStream(urlStr, postRequest, requestContentType, requestEncoding, null); - final Reader pageReader = new InputStreamReader(is, requestEncoding); - copy(pageReader, buffer); - pageReader.close(); + final Callback callback = new Callback() { + @Override + public void onSuccessful(final CharSequence bodyPeek, final ResponseBody body) throws IOException { + buffer.append(body.string()); + } + }; + getInputStream(callback, url, postRequest, requestContentType, requestEncoding, null); return buffer; } - public InputStream getInputStream(final String url) throws IOException { - return getInputStream(url, null, null); + public interface Callback { + void onSuccessful(CharSequence bodyPeek, ResponseBody body) throws IOException; } - public InputStream getInputStream(final String urlStr, final Charset requestEncoding, final String referer) - throws IOException { - return getInputStream(urlStr, null, null, requestEncoding, referer); + public void getInputStream(final Callback callback, final HttpUrl url) throws IOException { + getInputStream(callback, url, null, null); } - public InputStream getInputStream(final String urlStr, final String postRequest, final String requestContentType, - Charset requestEncoding, final String referer) throws IOException { - log.debug("{}: {}", postRequest != null ? "POST" : "GET", urlStr); + public void getInputStream(final Callback callback, final HttpUrl url, final Charset requestEncoding, + final String referer) throws IOException { + getInputStream(callback, url, null, null, requestEncoding, referer); + } + public void getInputStream(final Callback callback, final HttpUrl url, final String postRequest, + final String requestContentType, Charset requestEncoding, final String referer) throws IOException { if (requestEncoding == null) requestEncoding = Charsets.ISO_8859_1; int tries = 3; while (true) { - final URL url = new URL(urlStr); - final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - - if (connection instanceof HttpsURLConnection && sslAcceptAllHostnames) - ((HttpsURLConnection) connection).setHostnameVerifier(SSL_ACCEPT_ALL_HOSTNAMES); - - connection.setDoInput(true); - connection.setDoOutput(postRequest != null); - connection.setConnectTimeout(SCRAPE_CONNECT_TIMEOUT); - connection.setReadTimeout(SCRAPE_READ_TIMEOUT); - for (final Map.Entry entry : headers.entrySet()) - connection.addRequestProperty(entry.getKey(), entry.getValue()); + final Request.Builder request = new Request.Builder(); + request.url(url); + request.headers(Headers.of(headers)); + if (postRequest != null) + request.post(RequestBody.create(MediaType.parse(requestContentType), postRequest)); + request.header("Accept", SCRAPE_ACCEPT); if (userAgent != null) - connection.addRequestProperty("User-Agent", userAgent); - connection.addRequestProperty("Accept", SCRAPE_ACCEPT); - connection.addRequestProperty("Accept-Encoding", "gzip"); - // workaround to disable Vodafone compression - connection.addRequestProperty("Cache-Control", "no-cache"); - + request.header("User-Agent", userAgent); if (referer != null) - connection.addRequestProperty("Referer", referer); + request.header("Referer", referer); + final Cookie sessionCookie = this.sessionCookie; + if (sessionCookie != null && sessionCookie.name().equals(sessionCookieName)) + request.header("Cookie", sessionCookie.toString()); - final HttpCookie sessionCookie = this.sessionCookie; - if (sessionCookie != null && sessionCookie.getName().equals(sessionCookieName)) - connection.addRequestProperty("Cookie", sessionCookie.toString()); + final OkHttpClient okHttpClient; + if (sslAcceptAllHostnames) + okHttpClient = OKHTTP_CLIENT.newBuilder().hostnameVerifier(SSL_ACCEPT_ALL_HOSTNAMES).build(); + else + okHttpClient = OKHTTP_CLIENT; - if (postRequest != null) { - final byte[] postRequestBytes = postRequest.getBytes(requestEncoding.name()); + final Call call = okHttpClient.newCall(request.build()); + Response response = null; + try { + response = call.execute(); + final int responseCode = response.code(); + final String bodyPeek = response.peekBody(SCRAPE_PEEK_SIZE).string().replaceAll("\\p{C}", ""); + if (responseCode == HttpURLConnection.HTTP_OK) { - connection.setRequestMethod("POST"); - connection.addRequestProperty("Content-Type", requestContentType); - connection.addRequestProperty("Content-Length", Integer.toString(postRequestBytes.length)); + final HttpUrl redirectUrl = testRedirect(url, bodyPeek); + if (redirectUrl != null) + throw new UnexpectedRedirectException(url, redirectUrl); - final OutputStream os = connection.getOutputStream(); - os.write(postRequestBytes); - os.close(); - } + if (testExpired(bodyPeek)) + throw new SessionExpiredException(); + if (testInternalError(bodyPeek)) + throw new InternalErrorException(url, bodyPeek); - final int responseCode = connection.getResponseCode(); - if (responseCode == HttpURLConnection.HTTP_OK) { - final String contentType = connection.getContentType(); - final String contentEncoding = connection.getContentEncoding(); - - InputStream is = new BufferedInputStream(connection.getInputStream()); - - if ("gzip".equalsIgnoreCase(contentEncoding) - || "application/octet-stream".equalsIgnoreCase(contentType)) - is = wrapGzip(is); - - if (!url.getHost().equals(connection.getURL().getHost())) - throw new UnexpectedRedirectException(url, connection.getURL()); - - final String firstChars = peekFirstChars(is); - - final URL redirectUrl = testRedirect(url, firstChars); - if (redirectUrl != null) - throw new UnexpectedRedirectException(url, redirectUrl); - - if (testExpired(firstChars)) - throw new SessionExpiredException(); - - if (testInternalError(firstChars)) - throw new InternalErrorException(url, new InputStreamReader(is, requestEncoding)); - - // save cookie - if (sessionCookieName != null) { - c: for (final Map.Entry> entry : connection.getHeaderFields().entrySet()) { - if ("set-cookie".equalsIgnoreCase(entry.getKey()) - || "set-cookie2".equalsIgnoreCase(entry.getKey())) { - for (final String value : entry.getValue()) { - for (final HttpCookie cookie : HttpCookie.parse(value)) { - if (cookie.getName().equals(sessionCookieName)) { - this.sessionCookie = cookie; - break c; - } - } + // save cookie + if (sessionCookieName != null) { + final List cookies = Cookie.parseAll(url, response.headers()); + for (final Iterator i = cookies.iterator(); i.hasNext();) { + final Cookie cookie = i.next(); + if (cookie.name().equals(sessionCookieName)) { + this.sessionCookie = cookie; + break; } } } + + callback.onSuccessful(bodyPeek, response.body()); + return; + } else if (responseCode == HttpURLConnection.HTTP_BAD_REQUEST + || responseCode == HttpURLConnection.HTTP_UNAUTHORIZED + || responseCode == HttpURLConnection.HTTP_FORBIDDEN + || responseCode == HttpURLConnection.HTTP_NOT_ACCEPTABLE + || responseCode == HttpURLConnection.HTTP_UNAVAILABLE) { + throw new BlockedException(url, bodyPeek); + } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) { + throw new NotFoundException(url, bodyPeek); + } else if (responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { + throw new UnexpectedRedirectException(url, HttpUrl.parse(response.header("Location"))); + } else if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) { + throw new InternalErrorException(url, bodyPeek); + } else { + final String message = "got response: " + responseCode + " " + response.message(); + if (tries-- > 0) + log.info("{}, retrying...", message); + else + throw new IOException(message + ": " + url); } - - return is; - } else if (responseCode == HttpURLConnection.HTTP_BAD_REQUEST - || responseCode == HttpURLConnection.HTTP_UNAUTHORIZED - || responseCode == HttpURLConnection.HTTP_FORBIDDEN - || responseCode == HttpURLConnection.HTTP_NOT_ACCEPTABLE - || responseCode == HttpURLConnection.HTTP_UNAVAILABLE) { - throw new BlockedException(url, new InputStreamReader(connection.getErrorStream(), requestEncoding)); - } else if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) { - throw new NotFoundException(url, new InputStreamReader(connection.getErrorStream(), requestEncoding)); - } else if (responseCode == HttpURLConnection.HTTP_MOVED_PERM - || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { - throw new UnexpectedRedirectException(url, connection.getURL()); - } else if (responseCode == HttpURLConnection.HTTP_INTERNAL_ERROR) { - throw new InternalErrorException(url, - new InputStreamReader(connection.getErrorStream(), requestEncoding)); - } else { - final String message = "got response: " + responseCode + " " + connection.getResponseMessage(); - if (tries-- > 0) - log.info("{}, retrying...", message); - else - throw new IOException(message + ": " + url); + } finally { + if (response != null) + response.close(); } } } - public static final long copy(final Reader reader, final StringBuilder builder) throws IOException { - final char[] buffer = new char[SCRAPE_COPY_SIZE]; - long count = 0; - int n = 0; - while (-1 != (n = reader.read(buffer))) { - builder.append(buffer, 0, n); - count += n; - } - return count; - } - - private static InputStream wrapGzip(final InputStream is) throws IOException { - is.mark(2); - final int byte0 = is.read(); - final int byte1 = is.read(); - is.reset(); - - // check for gzip header - if (byte0 == 0x1f && byte1 == 0x8b) { - final BufferedInputStream is2 = new BufferedInputStream(new GZIPInputStream(is)); - is2.mark(2); - final int byte0_2 = is2.read(); - final int byte1_2 = is2.read(); - is2.reset(); - - // check for gzip header again - if (byte0_2 == 0x1f && byte1_2 == 0x8b) { - // double gzipped - return new BufferedInputStream(new GZIPInputStream(is2)); - } else { - // gzipped - return is2; - } - } else { - // uncompressed - return is; - } - } - - public static String peekFirstChars(final InputStream is) throws IOException { - is.mark(SCRAPE_PEEK_SIZE); - final byte[] firstBytes = new byte[SCRAPE_PEEK_SIZE]; - final int read = is.read(firstBytes); - if (read == -1) - return ""; - is.reset(); - return new String(firstBytes, 0, read).replaceAll("\\p{C}", ""); - } - private static final Pattern P_REDIRECT_HTTP_EQUIV = Pattern.compile( "\\s*(?:window.location|location.href)\\s*=\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE); - public static URL testRedirect(final URL context, final String content) throws MalformedURLException { + public static HttpUrl testRedirect(final HttpUrl base, final String content) { // check for redirect by http-equiv meta tag header final Matcher mHttpEquiv = P_REDIRECT_HTTP_EQUIV.matcher(content); if (mHttpEquiv.find()) - return new URL(context, mHttpEquiv.group(1)); + return base.resolve(mHttpEquiv.group(1)); // check for redirect by window.location javascript final Matcher mScript = P_REDIRECT_SCRIPT.matcher(content); if (mScript.find()) - return new URL(context, mScript.group(1)); + return base.resolve(mScript.group(1)); return null; } diff --git a/enabler/test/de/schildbach/pte/util/HttpClientTest.java b/enabler/test/de/schildbach/pte/util/HttpClientTest.java index 55f345c1..d53a2e03 100644 --- a/enabler/test/de/schildbach/pte/util/HttpClientTest.java +++ b/enabler/test/de/schildbach/pte/util/HttpClientTest.java @@ -21,51 +21,51 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import java.net.URL; - import org.junit.Before; import org.junit.Test; +import okhttp3.HttpUrl; + /** * @author Andreas Schildbach */ public class HttpClientTest { - private URL context; + private HttpUrl base; @Before public void setUp() throws Exception { - context = new URL("http://example.com"); + base = HttpUrl.parse("http://example.com"); } @Test public void vodafoneRedirect() throws Exception { - final URL url = HttpClient.testRedirect(context, + final HttpUrl url = HttpClient.testRedirect(base, "Vodafone Center

Sie werden weitergeleitet ...

Sollten Sie nicht weitergeleitet werden, klicken Sie bitte window.location = \"http://www.hotspot.kabeldeutschland.de/portal/?RequestedURI=http%3A%2F%2Fwww.fahrinfo-berlin.de%2FFahrinfo%2Fbin%2Fajax-getstop.bin%2Fdny%3Fgetstop%3D1%26REQ0JourneyStopsS0A%3D255%26REQ0JourneyStopsS0G%3Dgneisenustra%25DFe%3F%26js%3Dtrue&RedirectReason=Policy&RedirectAqpId=100&DiscardAqpId=100&SubscriberId=4fa432d4a653e5f8b2acb27aa862f98d&SubscriberType=ESM&ClientIP=10.136.25.241&SystemId=10.143.181.2-1%2F2&GroupId=1&PartitionId=2&Application=Unknown&ApplicationGroup=Unknown\" "); assertNotNull(url); - assertEquals("www.hotspot.kabeldeutschland.de", url.getHost()); + assertEquals("www.hotspot.kabeldeutschland.de", url.host()); } @Test public void tplinkRedirect() throws Exception { - final URL url = HttpClient.testRedirect(context, + final HttpUrl url = HttpClient.testRedirect(base, ""); assertNotNull(url); - assertEquals("tplinkextender.net", url.getHost()); + assertEquals("tplinkextender.net", url.host()); } @Test public void mshtmlRedirect() throws Exception { - final URL url = HttpClient.testRedirect(context, + final HttpUrl url = HttpClient.testRedirect(base, "HTML Redirection "); assertNotNull(url); - assertEquals("example.com", url.getHost()); + assertEquals("example.com", url.host()); } @Test