public class WindDirectionDeserializer implements JsonDeserializer<WindDirection> {
    @Override
    public WindDirection deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        int degree = json.getAsJsonPrimitive().getAsInt();
        WindDirection windDirection = new WindDirection();
        windDirection.setDegree(degree);
        return windDirection;
    }
} public class WindDirectionSerializer implements JsonSerializer<WindDirection> {
    @Override
    public JsonElement serialize(WindDirection src, Type typeOfSrc, JsonSerializationContext context) {
        return new JsonPrimitive(src.getDegree());
    }
} @Data
public class WindDirection {
    private int degree;
} @EqualsAndHashCode(callSuper = true)
@Data
public class CurrentWeather extends AbstractWeatherInformation {

    public static final Type TYPE = TypeToken.get(CurrentWeather.class).getType();
    public static final Type TYPE_LIST = TypeToken.getParameterized(List.class, TYPE).getType();

    @SerializedName("coord")
    private Coordinate coordinate;

    private String base;

    @SerializedName("name")
    private String cityName;

    @SerializedName("id")
    private long cityId;

    private String cod;

    private String visibility;

    @SerializedName("sys")
    private SystemParameters systemParameters;
} @Data
public class ForecastInformation<T extends Forecast> {
    private String code;
    private String message;
    private City city;
    private int cnt;
    @SerializedName("list")
    private List<T> forecasts;
} @EqualsAndHashCode(callSuper = true)
@Data
public class DailyForecast extends AbstractWeatherResponse implements Forecast {
    public static final Type TYPE = new TypeToken<ForecastInformation<DailyForecast>>() {
    }.getType();
    public static final Type TYPE_LIST = TypeToken.getParameterized(List.class, TYPE).getType();

    @SerializedName("temp")
    private Temperature temperature;
    private double humidity;
    private double speed;
    private double clouds;
    private double rain;
    @SerializedName("deg")
    private WindDirection direction;
} @EqualsAndHashCode(callSuper = true)
@Data
public class HourlyForecast extends AbstractWeatherInformation implements Forecast {

    public static final Type TYPE = new TypeToken<ForecastInformation<HourlyForecast>>() {
    }.getType();
    public static final Type TYPE_LIST = TypeToken.getParameterized(List.class, TYPE).getType();

    @JsonAdapter(ISOStringDateTypeAdapter.class)
    @SerializedName("dt_txt")
    private Date calculationDate;
} @Data
public class Uvi {
    public static final Type TYPE = TypeToken.get(Uvi.class).getType();
    public static final Type TYPE_LIST = TypeToken.getParameterized(List.class, TYPE).getType();
    @SerializedName("lon")
    private String longitude;

    @SerializedName("lat")
    private String latitude;

    @JsonAdapter(SecondsDateTypeAdapter.class)
    @SerializedName("date")
    private Date date;

    @SerializedName("date_iso")
    private String isoDate;

    @SerializedName("value")
    private double ultravioletIndex;
} public interface Query {

    /**
     * The base URL of the OpenWeatherMap API.
     */
    String BASE_URL = "https://api.openweathermap.org/data/2.5";

    String QUESTION_MARK = "?";
    String AND = "&";

    String toStringRepresentation(String apiKey);

    Language getLanguage();

    void setLanguage(Language language);

    UnitFormat getUnitFormat();

    void setUnitFormat(UnitFormat unitFormat);

    /**
     * Returns the base URL of the query.
     */
    String getBaseUrl();

    /**
     * Sets the base URL of the query to the specified {@code baseURL}.
     *
     * @param baseUrl the base URL the query uses to connect to the API
     */
    void setBaseUrl(String baseUrl);
} public interface QueryWithResponseFormat extends Query {
    ResponseFormat getResponseFormat();

    void setResponseFormat(ResponseFormat responseFormat);
} public enum ResponseFormat {
    JSON("json"),
    XML("xml"),
    HTML("html");
    private final String stringRepresentation;

    ResponseFormat(String stringRepresentation) {
        this.stringRepresentation = stringRepresentation;
    }

    public static ResponseFormat fromStringRepresentation(String stringRepresentation) {
        for (ResponseFormat responseFormat : values()) {
            if (responseFormat.stringRepresentation.equals(stringRepresentation)) {
                return responseFormat;
            }
        }
        throw new IllegalArgumentException("No ResponseFormat was found with value:" + stringRepresentation);
    }

    public String getStringRepresentation() {
        return stringRepresentation;
    }
} public interface CurrentWeatherMultipleLocationsQuery extends Query {
} public interface CurrentWeatherOneLocationQuery extends QueryWithResponseFormat {
    String SEARCH_PATH = "/weather";
} public interface DailyForecastQuery extends ForecastQuery {
    String SEARCH_PATH = "/daily";
} public interface HourlyForecastQuery extends ForecastQuery {
} public interface CurrentUviQuery extends UviQuery {
} public interface ForecastUviQuery extends UviQuery {
    String FORECAST = "/forecast";
} public interface HistoryUviQuery extends UviQuery {
    String HISTORY = "/history";
}
import com.google.gson.*;
import org.openweathermap.api.gson.WindDirectionDeserializer;
import org.openweathermap.api.gson.WindDirectionSerializer;
import org.openweathermap.api.model.WindDirection;
import org.openweathermap.api.model.currentweather.CurrentWeather;
import org.openweathermap.api.model.forecast.ForecastInformation;
import org.openweathermap.api.model.forecast.daily.DailyForecast;
import org.openweathermap.api.model.forecast.hourly.HourlyForecast;
import org.openweathermap.api.model.uvi.Uvi;
import org.openweathermap.api.query.Query;
import org.openweathermap.api.query.QueryWithResponseFormat;
import org.openweathermap.api.query.ResponseFormat;
import org.openweathermap.api.query.currentweather.CurrentWeatherMultipleLocationsQuery;
import org.openweathermap.api.query.currentweather.CurrentWeatherOneLocationQuery;
import org.openweathermap.api.query.forecast.daily.DailyForecastQuery;
import org.openweathermap.api.query.forecast.hourly.HourlyForecastQuery;
import org.openweathermap.api.query.uvi.current.CurrentUviQuery;
import org.openweathermap.api.query.uvi.forecast.ForecastUviQuery;
import org.openweathermap.api.query.uvi.history.HistoryUviQuery;
import java.lang.reflect.Type;
import java.util.List;
/*
 * Copyright 2021, Yurii Serhiichuk
 *
 * 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 org.openweathermap.api;




public abstract class AbstractDataWeatherClient implements DataWeatherClient {

    @Override
    public ForecastInformation<HourlyForecast> getForecastInformation(HourlyForecastQuery query) {
        ForecastInformation<HourlyForecast> result = getResult(query, HourlyForecast.TYPE);
        return result;
    }

    @Override
    public ForecastInformation<DailyForecast> getForecastInformation(DailyForecastQuery query) {
        ForecastInformation<DailyForecast> result = getResult(query, DailyForecast.TYPE);
        return result;
    }

    @Override
    public String getWeatherData(Query query) {
        return makeRequest(query);
    }


    @Override
    public CurrentWeather getCurrentWeather(CurrentWeatherOneLocationQuery query) {
        CurrentWeather result = getResult(query, CurrentWeather.TYPE);
        return result;
    }

    @Override
    public List<CurrentWeather> getCurrentWeather(CurrentWeatherMultipleLocationsQuery query) {
        String data = getWeatherData(query);
        JsonObject jsonObject = JsonParser.parseString(data).getAsJsonObject();
        JsonArray list = jsonObject.getAsJsonArray("list");
        List<CurrentWeather> result = gson().fromJson(list, CurrentWeather.TYPE_LIST);
        return result;
    }

    @Override
    public Uvi getCurrentUvi(CurrentUviQuery query) {
        Uvi result = getResult(query, Uvi.TYPE);
        return result;
    }

    @Override
    public List<Uvi> getUviForecast(ForecastUviQuery query) {
        List<Uvi> result = getResult(query, Uvi.TYPE_LIST);
        return result;
    }

    @Override
    public List<Uvi> getUviHistory(HistoryUviQuery query) {
        List<Uvi> result = getResult(query, Uvi.TYPE_LIST);
        return result;
    }

    protected abstract String makeRequest(Query query);

    private <T> T getResult(QueryWithResponseFormat query, Type type) {
        ResponseFormat responseFormat = query.getResponseFormat();
        if (responseFormat == null || responseFormat == ResponseFormat.JSON) {
            return getResult((Query) query, type);
        }
        return null;
    }

    private <T> T getResult(Query query, Type type) {
        String data = getWeatherData(query);
        final T result = gson().fromJson(data, type);
        return result;
    }

    protected static Gson gson() {
        return GsonHolder.INSTANCE.value;
    }

    private enum GsonHolder {
        INSTANCE;
        @SuppressWarnings({"NonSerializableFieldInSerializableClass", "ImmutableEnumChecker"})
        private final Gson value = new GsonBuilder()
                .registerTypeAdapter(WindDirection.class, new WindDirectionDeserializer())
                .registerTypeAdapter(WindDirection.class, new WindDirectionSerializer())