/*
* Copyright (C) 2014 Francesco Azzola
* Surviving with Android (http://www.survivingwithandroid.com)
*
* 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.survivingwithandroid.weather.lib;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.SystemClock;
import com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException;
import com.survivingwithandroid.weather.lib.exception.LocationProviderNotFoundException;
import com.survivingwithandroid.weather.lib.exception.WeatherLibException;
import com.survivingwithandroid.weather.lib.model.City;
import com.survivingwithandroid.weather.lib.model.CurrentWeather;
import com.survivingwithandroid.weather.lib.model.HistoricalWeather;
import com.survivingwithandroid.weather.lib.model.WeatherForecast;
import com.survivingwithandroid.weather.lib.model.WeatherHourForecast;
import com.survivingwithandroid.weather.lib.provider.IWeatherProvider;
import com.survivingwithandroid.weather.lib.request.Params;
import com.survivingwithandroid.weather.lib.request.WeatherProviderFeature;
import com.survivingwithandroid.weather.lib.request.WeatherRequest;
import com.survivingwithandroid.weather.lib.response.GenericResponseParser;
import com.survivingwithandroid.weather.lib.util.LogUtils;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.List;
/**
* This class represents the entry point to get the Weather information. This is another implementation of {@link WeatherClient},
* this is a <b>synchronous</b> client and you should consider it when using this class in your main thread.
* This class uses a IWeatherProvider to parse the response. Internally this class is based on standard {@link java.net.HttpURLConnection}
* <p/>
* <p>
* Usually you need only one instance of this class in your app so that the class implements the Singleton pattern
* To get the reference to this class you have to use getInstance method:
* </p>
* {@code WeatherClient client = StandardHttpClient.getInstance();}
* <p>
* soon after you the the reference and before you make any requests you have to pass the {@link android.content.Context}
* to this class.
* </p>
*
* @author Francesco Azzola
*/
public class StandardHttpClient extends WeatherClient {
public static int LOCATION_TIMEOUT = 5;
// weather provider
private CityEventListener cityListener;
private static StandardHttpClient me;
/**
* @deprecated Release 1.4 repleaced by {@link com.survivingwithandroid.weather.lib.WeatherClient.ClientBuilder}
**/
public static StandardHttpClient getInstance() {
if (me == null)
me = new StandardHttpClient();
return me;
}
/**
* This method has to be called before any HTTP request
*
* @param ctx Reference to the {@link android.content.Context}
*/
@Override
public void init(Context ctx) {
super.init(ctx);
}
/**
* This method update the current provider configuration
*
* @param config WeatherConfig info
* @see WeatherConfig
*/
@Override
public void updateWeatherConfig(WeatherConfig config) {
super.updateWeatherConfig(config);
}
/**
* Get the current weather condition. It returns a class structure that is indipendent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the current weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
*
* @param location a String representing the location id
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getCurrentCondition(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener)}
*/
@Override
public void getCurrentCondition(String location, WeatherEventListener listener) throws ApiKeyRequiredException {
getCurrentCondition(new WeatherRequest(location), listener);
}
/**
* Search the city using a name pattern. It returns a class structure that is indipendent from the
* provider used that holds the city list matching the pattern.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onCityListRetrieved passing a {@link java.util.List} of cities.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param pattern a String representing the pattern
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
*/
@Override
public void searchCity(String pattern, CityEventListener listener) throws ApiKeyRequiredException {
String url = provider.getQueryCityURL(pattern);
LogUtils.LOGD("Search city URL [" + url + "]");
// If the url is null trying to use geocoder
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
List<City> cityResult = provider.getCityResultList(data);
listener.onCityListRetrieved(cityResult);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Get the forecast weather condition. It returns a class structure that is indipendent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherForecast} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param location a String representing the location id
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getForecastWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener)}
*/
@Override
public void getForecastWeather(String location, ForecastWeatherEventListener listener) throws ApiKeyRequiredException {
getForecastWeather(new WeatherRequest(location), listener);
}
/**
* Get the forecast weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherHourForecast} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param location a String representing the location id
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getHourForecastWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener)}
*/
@Override
public void getHourForecastWeather(String location, HourForecastWeatherEventListener listener) throws ApiKeyRequiredException {
getHourForecastWeather(new WeatherRequest(location), listener);
}
@Override
public void getDefaultProviderImage(String icon, WeatherImageListener listener) {
String imageURL = provider.getQueryImageURL(icon);
try {
Bitmap bmp = readByte(imageURL);
listener.onImageReady(bmp);
}
catch (Throwable t) {
listener.onConnectionError(new WeatherLibException(t));
}
}
/**
* Get the historical weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param location a String representing the location id
* @param d1 is the starting date
* @param d2
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} @param2 d2 is the end date
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @deprecated use instead {@link com.survivingwithandroid.weather.lib.WeatherClient#getHistoricalWeather(com.survivingwithandroid.weather.lib.request.WeatherRequest, java.util.Date, java.util.Date, com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener)}
*/
@Override
public void getHistoricalWeather(String location, Date d1, Date d2, HistoricalWeatherEventListener listener) {
getHistoricalWeather(new WeatherRequest(location), d1, d2, listener);
}
@Override
public void searchCityByLocation(Criteria criteria, CityEventListener listener) throws LocationProviderNotFoundException {
super.handleLocation(criteria, listener);
}
/**
* This method is used to set the Weather provider. The Weather provider must implement ({@link com.survivingwithandroid.weather.lib.provider.IWeatherProvider}
* interface. This method has to be called before making any HTTP request otherwise the client doesn't know how
* to parse the request
*
* @param provider {@link com.survivingwithandroid.weather.lib.provider.IWeatherProvider}
* @see {@link com.survivingwithandroid.weather.lib.provider.openweathermap.OpenweathermapProvider}
* @see {@link com.survivingwithandroid.weather.lib.provider.yahooweather.YahooWeatherProvider}
*/
@Override
public void setProvider(IWeatherProvider provider) {
super.setProvider(provider);
}
/**
* This method retrieves the image using the url generated by the weahter provider {@link com.survivingwithandroid.weather.lib.provider.IWeatherProvider}
*
* @param cityId String representing the city id
* @param params {@link com.survivingwithandroid.weather.lib.request.Params}: list of parameters used to create the image
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherImageListener} listener that gets notified when the image is ready to use
*/
@Override
public void getWeatherImage(String cityId, Params params, final WeatherImageListener listener) {
String imageURL = provider.getQueryLayerURL(cityId, params);
try {
Bitmap bmp = readByte(imageURL);
listener.onImageReady(bmp);
}
catch (Throwable t) {
listener.onConnectionError(new WeatherLibException(t));
}
}
private Bitmap readByte(String url) throws Throwable {
HttpURLConnection connection = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
connection = (HttpURLConnection) (new URL(url)).openConnection();
connection.setRequestMethod("GET");
connection.connect();
InputStream is = connection.getInputStream();
byte[] b = new byte[1024];
while ( is.read(b) != -1)
baos.write(b);
Bitmap img = BitmapFactory.decodeByteArray(baos.toByteArray(), 0, baos.toByteArray().length);
return img;
} catch (Throwable t) {
throw t;
} finally {
try {
connection.disconnect();
} catch (Throwable t) { }
}
}
private String connectAndRead(String url) throws Throwable {
HttpURLConnection connection = null;
StringBuffer buffer = new StringBuffer();
try {
connection = (HttpURLConnection) (new URL(url)).openConnection();
connection.setRequestMethod("GET");
connection.connect();
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {
buffer.append(line + "\r\n");
}
} catch (Throwable t) {
throw t;
} finally {
try {
connection.disconnect();
} catch (Throwable t) {
}
}
return buffer.toString();
}
private LocationListener locListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
searchCityByLocation(location, cityListener);
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
@Override
public void onProviderEnabled(String s) {
}
@Override
public void onProviderDisabled(String s) {
}
};
@Override
protected void searchCityByLocation(Location location, final CityEventListener listener) throws ApiKeyRequiredException {
String url = provider.getQueryCityURLByLocation(location);
// If the url is null trying to use geocoder
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
//Log.d("SwA", "Data [" + data + "]");
List<City> cityResult = provider.getCityResultList(data);
listener.onCityListRetrieved(cityResult);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Search the city using latitude and longitude. It returns a class structure that is indipendent from the
* provider used that holds the city list matching the pattern.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onCityListRetrieved passing a {@link java.util.List} of cities.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param lat a double representing the latitude
* @param lon a double representing the longitude
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.CityEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @since 1.5.3
*/
@Override
public void searchCity(double lat, double lon, CityEventListener listener) throws ApiKeyRequiredException {
String url = provider.getQueryCityURLByCoord(lat, lon);
// If the url is null trying to use geocoder
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
//Log.d("SwA", "Data [" + data + "]");
List<City> cityResult = provider.getCityResultList(data);
listener.onCityListRetrieved(cityResult);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
// New methods
/**
* Get the current weather condition. It returns a class structure that is indipendent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the current weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
*/
@Override
public void getCurrentCondition(WeatherRequest request, WeatherEventListener listener) throws ApiKeyRequiredException {
String url = provider.getQueryCurrentWeatherURL(request);
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
//LogUtils.LOGD("Data [" + data + "]");
CurrentWeather weather = provider.getCurrentCondition(data);
listener.onWeatherRetrieved(weather);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Get the current weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data. This method is synchronous so it has not to be run on
* the main UI thread.
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @throws com.survivingwithandroid.weather.lib.exception.WeatherLibException
* @return a valid CurrentWeather object
*/
public CurrentWeather getCurrentCondition(WeatherRequest request)
throws ApiKeyRequiredException, WeatherLibException {
String url = provider.getQueryCurrentWeatherURL(request);
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
throw new WeatherLibException(t);
}
return provider.getCurrentCondition(data);
}
/**
* Get the forecast weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherForecast} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.ForecastWeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
*/
@Override
public void getForecastWeather(WeatherRequest request, ForecastWeatherEventListener listener) throws ApiKeyRequiredException {
String url = provider.getQueryForecastWeatherURL(request);
LogUtils.LOGD("Forecast URL [" + url + "]");
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
//Log.d("SwA", "Data [" + data + "]");
WeatherForecast forecast = provider.getForecastWeather(data);
listener.onWeatherRetrieved(forecast);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Get the forecast weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data. This method is synchronous so it has not to be run on
* the main UI thread.
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
* @throws com.survivingwithandroid.weather.lib.exception.WeatherLibException
* @return a valid WeatherForecast object
*/
public WeatherForecast getForecastWeather(WeatherRequest request)
throws ApiKeyRequiredException, WeatherLibException {
String url = provider.getQueryForecastWeatherURL(request);
LogUtils.LOGD("Forecast URL [" + url + "]");
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
throw new WeatherLibException(t);
}
return provider.getForecastWeather(data);
}
/**
* Get the forecast weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.WeatherHourForecast} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HourForecastWeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
*/
@Override
public void getHourForecastWeather(WeatherRequest request, HourForecastWeatherEventListener listener) throws ApiKeyRequiredException {
String url = provider.getQueryHourForecastWeatherURL(request);
LogUtils.LOGD("Hourly forecast URL [" + url + "]");
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
WeatherHourForecast forecast = provider.getHourForecastWeather(data);
listener.onWeatherRetrieved(forecast);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Get the historical weather condition. It returns a class structure that is independent from the
* provider used to ge the weather data.
* This method is an async method, in other word you have to implement your listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} to
* get notified when the weather data is ready.
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @param d1 is the starting date
* @param d2
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.HistoricalWeatherEventListener} @param2 d2 is the end date
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
*/
@Override
public void getHistoricalWeather(WeatherRequest request, Date d1, Date d2, HistoricalWeatherEventListener listener) {
String url = provider.getQueryHistoricalWeatherURL(request, d1, d2);
LogUtils.LOGD("Historical Weather URL [" + url + "]");
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
HistoricalWeather historicalWeather = provider.getHistoricalWeather(data);
listener.onWeatherRetrieved(historicalWeather);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Get a specific weather provider feature not implemented in all weather provider
* <p>
* When the data is ready this method calls the onWeatherRetrieved passing the {@link com.survivingwithandroid.weather.lib.model.HistoricalWeather} weather information.
* If there are some errors during the request parsing, it calls onWeatherError passing the exception or
* onConnectionError if the errors happened during the HTTP connection
* </p>
*
* @param request {@link com.survivingwithandroid.weather.lib.request.WeatherRequest}
* @param extRequest is the extended request as required by the weather provider
* @param parser is the parser used to parsed the response {@link com.survivingwithandroid.weather.lib.response.GenericResponseParser}
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.GenericRequestWeatherEventListener}
* @throws com.survivingwithandroid.weather.lib.exception.ApiKeyRequiredException
*/
@Override
public <T extends WeatherProviderFeature, S> void getProviderWeatherFeature(WeatherRequest request, T extRequest, GenericResponseParser<S> parser, GenericRequestWeatherEventListener<S> listener) {
String url = extRequest.getURL();
LogUtils.LOGD("Generic Weather Feature URL ["+url+"]");
String data = null;
try {
data = connectAndRead(url);
} catch (Throwable t) {
listener.onConnectionError(t);
return;
}
try {
S result = parser.parseData(data);
listener.onResponseRetrieved(result);
} catch (WeatherLibException t) {
listener.onWeatherError(t);
}
}
/**
* Get an image at the specified URL and inform the listener when the image is ready
*
* @param url String representing the url
* @param listener {@link com.survivingwithandroid.weather.lib.WeatherClient.WeatherImageListener}
* @since 1.5.3
* */
@Override
public void getImage(String url, WeatherImageListener listener) {
try {
Bitmap bmp = readByte(url);
listener.onImageReady(bmp);
}
catch (Throwable t) {
listener.onConnectionError(new WeatherLibException(t));
}
}
}