/** * ThingSpeak Java Client Copyright 2014, Andrew Bythell <abythell@ieee.org> * http://angryelectron.com * * The ThingSpeak Java Client is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * The ThingSpeak Java Client is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with * theThingSpeak Java Client. If not, see <http://www.gnu.org/licenses/>. */ package com.angryelectron.thingspeak.pub; import com.angryelectron.thingspeak.ThingSpeakException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.mashape.unirest.http.HttpResponse; import com.mashape.unirest.http.JsonNode; import com.mashape.unirest.http.Unirest; import com.mashape.unirest.http.exceptions.UnirestException; import com.mashape.unirest.request.GetRequest; import java.lang.reflect.Type; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; /** * Provides a custom iterator for PublicChannelCollections which works with * ThingSpeak's paginated results. * * @author abythell */ class PublicIterator implements Iterator<PublicChannel> { /** * URL of the Thingspeak server. */ private final String url; /** * Optional tag to search for. If null, all channels are returned. */ private final String tag; /** * Current page of results and an iterator to the list of PublicChannel * channels it contains. */ private PublicJSONResult results; private Iterator<PublicChannel> iterator; /** * ThingSpeak channels don't always contain elevation, latitude, or * longitude data. These empty strings cause NumberFormatExceptions when * using the default GSON de-serializer for Double. This replacement * de-serializes empty strings to 0.0. */ private static class LocationDeserializer implements JsonDeserializer<Double> { @Override public Double deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { try { return Double.parseDouble(json.getAsString()); } catch (NumberFormatException ex) { return 0.0; } } } /** * Build a GSON de-serializer for the ThingSpeak PublicChannel Channel JSON. */ private final Gson gson = new GsonBuilder() .registerTypeAdapter(Double.class, new LocationDeserializer()) .setDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").create(); /** * Constructor. * * @param url ThingSpeak server URL (eg. http://api.thingspeak.com). * @param tag Get channels with this tag only, or null to return all * channels. */ protected PublicIterator(String url, String tag) { this.url = url; this.tag = tag; thingRequest(1); } /** * Make requests to the ThingSpeak server and parse the results. * * @param page The page of results to request. */ private void thingRequest(Integer page) { try { GetRequest request = Unirest.get(url + "/channels/public.json"); if (tag != null) { request.field("tag", tag); } request.field("page", page); HttpResponse<JsonNode> response = request.asJson(); if (response.getCode() != 200) { throw new ThingSpeakException("Request failed with code " + response.getCode()); } results = gson.fromJson(response.getBody().toString(), PublicJSONResult.class); iterator = results.iterator(); } catch (UnirestException | ThingSpeakException ex) { Logger.getLogger(PublicIterator.class.getName()).log(Level.SEVERE, null, ex); results = null; } } @Override public boolean hasNext() { if (iterator.hasNext()) { /* current page still has unreturned channels */ return true; } else if (results.isLastPage()) { /* current page has returned all channels and there * are no more pages left */ return false; } else { /* current page has returned all channels but there * are more pages remaining. */ thingRequest(results.getCurrentPage() + 1); return true; } } @Override public PublicChannel next() { return iterator.next(); } @Override public void remove() { iterator.remove(); } Integer size() { return results.getTotalEntries(); } }