com.appsimobile.appsii.module.weather.loader.YahooWeatherApiClient.java Source code

Java tutorial

Introduction

Here is the source code for com.appsimobile.appsii.module.weather.loader.YahooWeatherApiClient.java

Source

/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.appsimobile.appsii.module.weather.loader;

import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.util.CircularArray;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.appsimobile.appsii.BuildConfig;
import com.appsimobile.appsii.R;
import com.appsimobile.appsii.ResponseParserException;
import com.appsimobile.appsii.annotation.VisibleForTesting;
import com.appsimobile.appsii.module.weather.Utils;
import com.appsimobile.util.ArrayUtils;

import org.json.JSONException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.Comparator;
import java.util.Locale;

/**
 * Client code for the Yahoo! Weather RSS feeds and GeoPlanet API.
 */
public class YahooWeatherApiClient {

    private static final String TAG = "YahooWeatherApiClient";

    private static final int MAX_SEARCH_RESULTS = 10;

    private static final int PARSE_STATE_NONE = 0;

    private static final int PARSE_STATE_PLACE = 1;

    private static final int PARSE_STATE_WOEID = 2;

    private static final int PARSE_STATE_NAME = 3;

    private static final int PARSE_STATE_COUNTRY = 4;

    private static final int PARSE_STATE_ADMIN1 = 5;

    private static final int PARSE_STATE_TIMEZONE = 6;

    private static final XmlPullParserFactory sXmlPullParserFactory;

    static {
        try {
            sXmlPullParserFactory = XmlPullParserFactory.newInstance();
            sXmlPullParserFactory.setNamespaceAware(true);
        } catch (XmlPullParserException e) {
            Log.e(TAG, "Could not instantiate XmlPullParserFactory", e);
            throw new RuntimeException(e);
        }
    }

    @Nullable
    public static CircularArray<WeatherData> getWeatherForWoeids(CircularArray<String> woeids, String unit)
            throws CantGetWeatherException {

        if (woeids == null || woeids.isEmpty())
            return null;

        HttpURLConnection connection = null;
        try {
            String url = buildWeatherQueryUrl(woeids, unit);
            if (BuildConfig.DEBUG)
                Log.d("YahooWeatherApiClient", "loading weather from: " + url);

            connection = Utils.openUrlConnection(url);

            CircularArray<WeatherData> result = new CircularArray<>();
            WeatherDataParser.parseWeatherData(result, connection.getInputStream(), woeids);

            return result;
        } catch (JSONException | ResponseParserException | ParseException | IOException | NumberFormatException e) {
            throw new CantGetWeatherException(true, R.string.no_weather_data, "Error parsing weather feed XML.", e);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private static String buildWeatherQueryUrl(CircularArray<String> woeids, String unit) {
        // http://developer.yahoo.com/weather/
        String endPoint = "https://query.yahooapis.com/v1/public/yql?format=json&q=";
        String query = "select * from weather.forecast where woeid in (%s) and u=\"%s\"";
        String param = ArrayUtils.join(", ", woeids);
        String queryString = String.format(Locale.ROOT, query, param, unit);
        if (BuildConfig.DEBUG)
            Log.d("YahooWeatherApiClient", "yql query: " + queryString);
        try {
            queryString = URLEncoder.encode(queryString, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.wtf("YahooWeatherApiClient", "error encoding url", e);
        }

        return endPoint + queryString;
    }

    public static LocationInfo getLocationInfo(Location location) throws CantGetWeatherException {
        LocationInfo li = new LocationInfo();

        // first=tagname (admin1, locality3) second=woeid

        HttpURLConnection connection = null;
        try {
            connection = Utils.openUrlConnection(buildPlaceSearchUrl(location));

            InputStream inputStream = connection.getInputStream();
            return parseLocationInfo(li, inputStream);

        } catch (IOException | XmlPullParserException e) {
            throw new CantGetWeatherException(true, R.string.no_weather_data, "Error parsing place search XML", e);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }
    }

    private static String buildPlaceSearchUrl(Location l) {
        // GeoPlanet API
        return "http://where.yahooapis.com/v1/places.q('" + l.getLatitude() + "," + l.getLongitude() + "')"
                + "?appid=" + YahooWeatherApiConfig.APP_ID;
    }

    @VisibleForTesting
    static LocationInfo parseLocationInfo(LocationInfo li, InputStream in)
            throws XmlPullParserException, IOException, CantGetWeatherException {

        CircularArray<Pair<String, String>> alternateWoeids = new CircularArray<>();
        String primaryWoeid = null;

        XmlPullParser xpp = sXmlPullParserFactory.newPullParser();
        xpp.setInput(new InputStreamReader(in));

        boolean inWoe = false;
        boolean inTown = false;
        boolean inCountry = false;
        boolean inTimezone = false;
        int eventType = xpp.getEventType();

        while (eventType != XmlPullParser.END_DOCUMENT) {
            String tagName = xpp.getName();

            if (eventType == XmlPullParser.START_TAG && "woeid".equals(tagName)) {
                inWoe = true;
            } else if (eventType == XmlPullParser.TEXT && inWoe) {
                primaryWoeid = xpp.getText();
            } else if (eventType == XmlPullParser.START_TAG && tagName.startsWith("timezone")) {
                inTimezone = true;
            } else if (eventType == XmlPullParser.TEXT && inTimezone) {
                li.timezone = xpp.getText();
            } else if (eventType == XmlPullParser.START_TAG && (tagName.startsWith("locality")
                    || tagName.startsWith("admin") || tagName.startsWith("country"))) {
                for (int i = xpp.getAttributeCount() - 1; i >= 0; i--) {
                    String attrName = xpp.getAttributeName(i);
                    if ("type".equals(attrName) && "Town".equals(xpp.getAttributeValue(i))) {
                        inTown = true;
                    } else if ("type".equals(attrName) && "Country".equals(xpp.getAttributeValue(i))) {
                        inCountry = true;
                    } else if ("woeid".equals(attrName)) {
                        String woeid = xpp.getAttributeValue(i);
                        if (!TextUtils.isEmpty(woeid)) {
                            alternateWoeids.addLast(new Pair<>(tagName, woeid));
                        }
                    }
                }
            } else if (eventType == XmlPullParser.TEXT && inTown) {
                li.town = xpp.getText();
            } else if (eventType == XmlPullParser.TEXT && inCountry) {
                li.country = xpp.getText();
            }

            if (eventType == XmlPullParser.END_TAG) {
                inWoe = false;
                inTown = false;
                inCountry = false;
                inTimezone = false;
            }

            eventType = xpp.next();
        }

        // Add the primary woeid if it was found.
        if (!TextUtils.isEmpty(primaryWoeid)) {
            li.woeids.addLast(primaryWoeid);
        }

        // Sort by descending tag name to order by decreasing precision
        // (locality3, locality2, locality1, admin3, admin2, admin1, etc.)
        ArrayUtils.sort(alternateWoeids, new Comparator<Pair<String, String>>() {
            @Override
            public int compare(Pair<String, String> pair1, Pair<String, String> pair2) {
                return pair1.first.compareTo(pair2.first);
            }
        });

        int N = alternateWoeids.size();
        for (int i = 0; i < N; i++) {
            Pair<String, String> pair = alternateWoeids.get(i);
            li.woeids.addLast(pair.second);
        }

        if (li.woeids.size() > 0) {
            return li;
        }

        throw new CantGetWeatherException(true, R.string.no_weather_data, "No WOEIDs found nearby.");
    }

    public static CircularArray<LocationSearchResult> findLocationsAutocomplete(String startsWith) {
        CircularArray<LocationSearchResult> results = new CircularArray<>();

        HttpURLConnection connection = null;
        try {
            connection = Utils.openUrlConnection(buildPlaceSearchStartsWithUrl(startsWith));
            InputStream inputStream = connection.getInputStream();

            parseLocationSearchResults(results, inputStream);

        } catch (IOException | XmlPullParserException e) {
            Log.w(TAG, "Error parsing place search XML");
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
        }

        return results;
    }

    private static String buildPlaceSearchStartsWithUrl(String startsWith) {
        // GeoPlanet API
        startsWith = startsWith.replaceAll("[^\\w ]+", "").replaceAll(" ", "%20");
        return "http://where.yahooapis.com/v1/places.q('" + startsWith + "%2A');" + "count=" + MAX_SEARCH_RESULTS
                + "?appid=" + YahooWeatherApiConfig.APP_ID;
    }

    @VisibleForTesting
    static void parseLocationSearchResults(CircularArray<LocationSearchResult> results, InputStream inputStream)
            throws XmlPullParserException, IOException {
        XmlPullParser xpp = sXmlPullParserFactory.newPullParser();
        xpp.setInput(new InputStreamReader(inputStream));

        LocationSearchResult result = null;
        String name = null, country = null, admin1 = null;
        String timezone = null;
        StringBuilder sb = new StringBuilder();

        int state = PARSE_STATE_NONE;
        int eventType = xpp.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            String tagName = xpp.getName();

            if (eventType == XmlPullParser.START_TAG) {
                switch (state) {
                case PARSE_STATE_NONE:
                    if ("place".equals(tagName)) {
                        state = PARSE_STATE_PLACE;
                        result = new LocationSearchResult();
                        name = country = admin1 = null;
                    }
                    break;

                case PARSE_STATE_PLACE:
                    if ("name".equals(tagName)) {
                        state = PARSE_STATE_NAME;
                    } else if ("woeid".equals(tagName)) {
                        state = PARSE_STATE_WOEID;
                    } else if ("country".equals(tagName)) {
                        state = PARSE_STATE_COUNTRY;
                    } else if ("admin1".equals(tagName)) {
                        state = PARSE_STATE_ADMIN1;
                    } else if ("timezone".equals(tagName)) {
                        state = PARSE_STATE_TIMEZONE;
                    }
                    break;
                }

            } else if (eventType == XmlPullParser.TEXT) {
                switch (state) {
                case PARSE_STATE_WOEID:
                    result.woeid = xpp.getText();
                    break;

                case PARSE_STATE_NAME:
                    name = xpp.getText();
                    break;

                case PARSE_STATE_COUNTRY:
                    country = xpp.getText();
                    break;

                case PARSE_STATE_ADMIN1:
                    admin1 = xpp.getText();
                    break;

                case PARSE_STATE_TIMEZONE:
                    timezone = xpp.getText();
                    break;
                }

            } else if (eventType == XmlPullParser.END_TAG) {
                if ("place".equals(tagName)) {
                    //                        // Sort by descending tag name to order by decreasing precision
                    //                        // (locality3, locality2, locality1, admin3, admin2, admin1, etc.)
                    //                        Collections.sort(alternateWoeids, new Comparator<Pair<String, String>>() {
                    //                            @Override
                    //                            public int compare(Pair<String, String> pair1,
                    //                                    Pair<String, String> pair2) {
                    //                                return pair1.first.compareTo(pair2.first);
                    //                            }
                    //                        });
                    sb.setLength(0);
                    if (!TextUtils.isEmpty(name)) {
                        sb.append(name);
                    }
                    if (!TextUtils.isEmpty(admin1)) {
                        if (sb.length() > 0) {
                            sb.append(", ");
                        }
                        sb.append(admin1);
                    }
                    result.displayName = sb.toString();
                    result.country = country;
                    result.timezone = timezone;
                    results.addLast(result);
                    state = PARSE_STATE_NONE;

                } else if (state != PARSE_STATE_NONE) {
                    state = PARSE_STATE_PLACE;
                }
            }

            eventType = xpp.next();
        }
    }

    static class YahooWeatherApiConfig {

        public static final String APP_ID = "kGO140TV34HVTae_DDS93fM_w3AJmtmI23gxUFnHKWyrOGcRzoFjYpw8Ato6BxhvbTg-";
    }

    public static class LocationInfo {

        // Sorted by decreasing precision
        // (point of interest, locality3, locality2, locality1, admin3, admin2, admin1, etc.)
        public final CircularArray<String> woeids = new CircularArray<>();

        public String town;

        public String country;

        public String timezone;
    }

    public static class LocationSearchResult implements Parcelable {

        public static final Creator<LocationSearchResult> CREATOR = new Creator<LocationSearchResult>() {
            @Override
            public LocationSearchResult createFromParcel(Parcel in) {
                return new LocationSearchResult(in);
            }

            @Override
            public LocationSearchResult[] newArray(int size) {
                return new LocationSearchResult[size];
            }
        };

        public String woeid;

        public String displayName;

        public String country;

        public String timezone;

        public LocationSearchResult() {

        }

        protected LocationSearchResult(Parcel in) {
            woeid = in.readString();
            displayName = in.readString();
            country = in.readString();
            timezone = in.readString();
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(woeid);
            dest.writeString(displayName);
            dest.writeString(country);
            dest.writeString(timezone);
        }
    }
}