View Javadoc
1   package com.guinetik.examples;
2   
3   import com.guinetik.rr.RocketRest;
4   import com.guinetik.rr.RocketRestConfig;
5   import com.guinetik.rr.RocketRestOptions;
6   import com.guinetik.rr.result.ApiError;
7   import com.guinetik.rr.result.Result;
8   
9   import java.io.UnsupportedEncodingException;
10  import java.net.URLEncoder;
11  import java.nio.charset.StandardCharsets;
12  import java.util.Arrays;
13  import java.util.HashMap;
14  import java.util.List;
15  import java.util.Map;
16  import java.util.Scanner;
17  import java.util.concurrent.CompletableFuture;
18  import java.util.concurrent.CountDownLatch;
19  import java.util.stream.Collectors;
20  import java.util.function.Function;
21  import com.fasterxml.jackson.annotation.JsonProperty;
22  /**
23   * Example demonstrating how to use RocketRest's async API to fetch weather data
24   * from wttr.in service.
25   */
26  public class WeatherExample implements Example {
27      
28      private static final String WTTR_API_URL = "https://wttr.in";
29      private static final Scanner scanner = new Scanner(System.in);
30      private RocketRest client;
31      
32      @Override
33      public String getName() {
34          return "Weather API (using async)";
35      }
36      
37      @Override
38      public void run() {
39          System.out.println("==============================");
40          System.out.println("Weather API Example (async)");
41          System.out.println("==============================");
42          System.out.println("This example demonstrates using RocketRest's async API");
43          System.out.println("to fetch weather data from wttr.in service.\n");
44          
45          try {
46              // Initialize RocketRest client
47              initializeClient();
48              
49              // Main menu
50              mainMenu();
51              
52          } catch (Exception e) {
53              System.out.println("❌ Error: " + e.getMessage());
54              e.printStackTrace();
55          } finally {
56              if (client != null) {
57                  client.shutdown();
58              }
59          }
60      }
61      
62      /**
63       * Initialize the RocketRest client with appropriate configuration
64       */
65      private void initializeClient() {
66          RocketRestConfig config = RocketRestConfig.builder(WTTR_API_URL)
67                  .defaultOptions(options -> {
68                      options.set(RocketRestOptions.LOGGING_ENABLED, true);
69                      options.set(RocketRestOptions.TIMING_ENABLED, true);
70                      options.set(RocketRestOptions.LOG_REQUEST_BODY, true);
71                      options.set(RocketRestOptions.LOG_RESPONSE_BODY, true);
72                  })
73                  .build();
74          
75          client = new RocketRest(WTTR_API_URL, config);
76          System.out.println("RocketRest client initialized for wttr.in API\n");
77      }
78      
79      /**
80       * Main menu for the Weather example
81       */
82      private void mainMenu() {
83          boolean exit = false;
84          
85          while (!exit) {
86              System.out.println("\n--- Weather API Menu ---");
87              System.out.println("1. Get Current Weather by Location");
88              System.out.println("2. Get Weather Forecast by Location");
89              System.out.println("3. Compare Weather of Multiple Locations");
90              System.out.println("4. Exit");
91              System.out.print("\nEnter your choice (1-4): ");
92              
93              String choice = scanner.nextLine().trim();
94              
95              switch (choice) {
96                  case "1":
97                      getCurrentWeather();
98                      break;
99                  case "2":
100                     getWeatherForecast();
101                     break;
102                 case "3":
103                     compareMultipleLocations();
104                     break;
105                 case "4":
106                     exit = true;
107                     System.out.println("Exiting Weather API Example. Goodbye!");
108                     break;
109                 default:
110                     System.out.println("Invalid choice. Please try again.");
111             }
112         }
113     }
114     
115     /**
116      * URL encode a location name to handle spaces and special characters
117      */
118     private String encodeLocation(String location) {
119         try {
120             return URLEncoder.encode(location, StandardCharsets.UTF_8.toString())
121                     .replace("+", "%20"); // Some APIs prefer %20 over + for spaces
122         } catch (UnsupportedEncodingException e) {
123             System.out.println("Warning: Error encoding location. Using raw value.");
124             return location;
125         }
126     }
127     
128     /**
129      * Get current weather for a specified location using a functional approach
130      */
131     private void getCurrentWeather() {
132         System.out.println("\n=== Current Weather ===");
133         System.out.print("Enter location (city name or coordinates): ");
134         String location = scanner.nextLine().trim();
135         
136         if (location.isEmpty()) {
137             System.out.println("Location cannot be empty. Returning to menu.");
138             return;
139         }
140         
141         // Encode the location to handle spaces and special characters
142         String encodedLocation = encodeLocation(location);
143         
144         System.out.println("\nFetching current weather for " + location + "...");
145         
146         // Construct query parameters
147         Map<String, String> params = new HashMap<>();
148         params.put("format", "j1");
149         
150         // Define a function to get additional information if needed
151         Function<WeatherResponse, CompletableFuture<WeatherResponse>> enrichWeatherData = 
152                 response -> {
153                     // In a real application, you might fetch additional data here
154                     // and combine it with the original response
155                     
156                     // This is just a placeholder to demonstrate composition
157                     System.out.println("Processing weather data...");
158                     
159                     // For demonstration, just return the original response
160                     return CompletableFuture.completedFuture(response);
161                 };
162         
163         // Use the async API with functional composition
164         client.async()
165                 .get("/" + encodedLocation, WeatherResponse.class, params)
166                 // Chain additional async operations if needed
167                 .thenCompose(enrichWeatherData)
168                 // Process and display the result
169                 .thenAccept(response -> {
170                     System.out.println("Weather data received successfully!");
171                     displayCurrentWeather(response);
172                 })
173                 // Handle errors
174                 .exceptionally(ex -> {
175                     System.out.println("❌ Error fetching weather: " + ex.getMessage());
176                     return null;
177                 })
178                 // Wait for completion (blocking here for the example's simplicity)
179                 .join();
180     }
181     
182     /**
183      * Get weather forecast for a specified location using a reactive approach
184      */
185     private void getWeatherForecast() {
186         System.out.println("\n=== Weather Forecast ===");
187         System.out.print("Enter location (city name or coordinates): ");
188         String location = scanner.nextLine().trim();
189         
190         if (location.isEmpty()) {
191             System.out.println("Location cannot be empty. Returning to menu.");
192             return;
193         }
194         
195         // Encode the location to handle spaces and special characters
196         String encodedLocation = encodeLocation(location);
197         
198         System.out.println("\nFetching weather forecast for " + location + "...");
199         
200         // Construct query parameters
201         Map<String, String> params = new HashMap<>();
202         params.put("format", "j1");
203         
204         // Use the async API with functional composition
205         client.async()
206                 .get("/" + encodedLocation, WeatherResponse.class, params)
207                 // Transform the result
208                 .thenApply(response -> {
209                     // Add processing logic here if needed
210                     return response;
211                 })
212                 // Handle successful completion
213                 .thenAccept(this::displayForecast)
214                 // Handle errors
215                 .exceptionally(ex -> {
216                     System.out.println("❌ Error fetching forecast: " + ex.getMessage());
217                     return null;
218                 })
219                 // Wait for completion (blocking here for the example's simplicity)
220                 .join();
221     }
222     
223     /**
224      * Compare weather of multiple locations asynchronously using a functional approach
225      */
226     private void compareMultipleLocations() {
227         System.out.println("\n=== Compare Multiple Locations ===");
228         System.out.println("Enter locations (separated by commas):");
229         String input = scanner.nextLine().trim();
230         
231         if (input.isEmpty()) {
232             System.out.println("No locations provided. Returning to menu.");
233             return;
234         }
235         
236         // Convert input to a list of locations
237         List<String> locations = Arrays.stream(input.split(","))
238                 .map(String::trim)
239                 .filter(s -> !s.isEmpty())
240                 .collect(Collectors.toList());
241         
242         System.out.println("\nFetching weather data for " + locations.size() + " locations...");
243         
244         // Query parameters
245         Map<String, String> params = new HashMap<>();
246         params.put("format", "j1");
247         
248         // Create a CompletableFuture for each location
249         List<CompletableFuture<Pair<String, WeatherResponse>>> futures = locations.stream()
250                 .map(location -> {
251                     // Encode the location to handle spaces and special characters
252                     String encodedLocation = encodeLocation(location);
253                     
254                     // Create a future that will contain a pair of location and weather response
255                     return client.async()
256                             .get("/" + encodedLocation, WeatherResponse.class, params)
257                             .thenApply(response -> new Pair<>(location, response))
258                             .exceptionally(ex -> {
259                                 System.out.println("❌ Error for " + location + ": " + ex.getMessage());
260                                 return new Pair<>(location, null);
261                             });
262                 })
263                 .collect(Collectors.toList());
264         
265         // Combine all futures into a single future that completes when all complete
266         CompletableFuture<List<Pair<String, WeatherResponse>>> allFutures = 
267                 CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
268                         .thenApply(v -> futures.stream()
269                                 .map(CompletableFuture::join)
270                                 .collect(Collectors.toList()));
271         
272         // Process the results when all futures complete
273         allFutures.thenAccept(results -> {
274             System.out.println("\n=== Weather Comparison ===");
275             
276             results.forEach(pair -> {
277                 String location = pair.getFirst();
278                 WeatherResponse response = pair.getSecond();
279                 
280                 System.out.println("\nLocation: " + location);
281                 if (response != null) {
282                     displayWeatherSummary(response);
283                 } else {
284                     System.out.println("❌ No data available");
285                 }
286             });
287         }).join(); // Wait for all processing to complete
288     }
289     
290     /**
291      * Display current weather information
292      */
293     private void displayCurrentWeather(WeatherResponse response) {
294         if (response == null || response.getCurrent_condition() == null || response.getCurrent_condition().isEmpty()) {
295             System.out.println("No weather data available.");
296             return;
297         }
298         
299         CurrentCondition current = response.getCurrent_condition().get(0);
300         NearestArea area = response.getNearest_area() != null && !response.getNearest_area().isEmpty() 
301                 ? response.getNearest_area().get(0) : null;
302         
303         // Debug output to see raw values
304         System.out.println("\nDEBUG - Current condition raw data: " + current);
305         
306         System.out.println("\n=== Current Weather ===");
307         
308         // Display location information
309         if (area != null) {
310             String areaName = area.getAreaName() != null && !area.getAreaName().isEmpty() 
311                     ? area.getAreaName().get(0).getValue() : "Unknown";
312             String country = area.getCountry() != null && !area.getCountry().isEmpty() 
313                     ? area.getCountry().get(0).getValue() : "";
314             String region = area.getRegion() != null && !area.getRegion().isEmpty() 
315                     ? area.getRegion().get(0).getValue() : "";
316             
317             System.out.println("Location: " + areaName + 
318                     (region.isEmpty() ? "" : ", " + region) + 
319                     (country.isEmpty() ? "" : ", " + country));
320         }
321         
322         // Display observation time
323         System.out.println("Observation Time: " + current.getLocalObsDateTime());
324         
325         // Display current weather conditions
326         String weatherDesc = current.getWeatherDesc() != null && !current.getWeatherDesc().isEmpty() 
327                 ? current.getWeatherDesc().get(0).getValue() : "Unknown";
328         System.out.println("Conditions: " + weatherDesc);
329         
330         // Display temperature
331         System.out.println("Temperature: " + current.getTemp_C() + "°C / " + current.getTemp_F() + "°F");
332         System.out.println("Feels Like: " + current.getFeelsLikeC() + "°C / " + current.getFeelsLikeF() + "°F");
333         
334         // Display other weather details
335         System.out.println("Humidity: " + current.getHumidity() + "%");
336         System.out.println("Wind: " + current.getWindspeedKmph() + " km/h (" + current.getWinddir16Point() + ")");
337         System.out.println("Pressure: " + current.getPressure() + " hPa");
338         System.out.println("Cloud Cover: " + current.getCloudcover() + "%");
339         System.out.println("Visibility: " + current.getVisibility() + " km");
340         System.out.println("UV Index: " + current.getUvIndex());
341     }
342     
343     /**
344      * Display weather forecast information
345      */
346     private void displayForecast(WeatherResponse response) {
347         if (response == null || response.getWeather() == null || response.getWeather().isEmpty()) {
348             System.out.println("No forecast data available.");
349             return;
350         }
351         
352         Weather today = response.getWeather().get(0);
353         NearestArea area = response.getNearest_area() != null && !response.getNearest_area().isEmpty() 
354                 ? response.getNearest_area().get(0) : null;
355         
356         System.out.println("\n=== Weather Forecast ===");
357         
358         // Display location information
359         if (area != null) {
360             String areaName = area.getAreaName() != null && !area.getAreaName().isEmpty() 
361                     ? area.getAreaName().get(0).getValue() : "Unknown";
362             String country = area.getCountry() != null && !area.getCountry().isEmpty() 
363                     ? area.getCountry().get(0).getValue() : "";
364             
365             System.out.println("Location: " + areaName + (country.isEmpty() ? "" : ", " + country));
366         }
367         
368         // Display today's forecast
369         System.out.println("\nForecast for " + today.getDate() + ":");
370         System.out.println("Temperature Range: " + today.getMintempC() + "°C to " + today.getMaxtempC() + "°C");
371         System.out.println("Average Temperature: " + today.getAvgtempC() + "°C");
372         System.out.println("Sunshine Hours: " + today.getSunHour());
373         
374         // Display astronomy information
375         if (today.getAstronomy() != null && !today.getAstronomy().isEmpty()) {
376             Astronomy astronomy = today.getAstronomy().get(0);
377             System.out.println("\nAstronomy:");
378             System.out.println("Sunrise: " + astronomy.getSunrise());
379             System.out.println("Sunset: " + astronomy.getSunset());
380             System.out.println("Moonrise: " + astronomy.getMoonrise());
381             System.out.println("Moonset: " + astronomy.getMoonset());
382             System.out.println("Moon Phase: " + astronomy.getMoon_phase());
383             System.out.println("Moon Illumination: " + astronomy.getMoon_illumination() + "%");
384         }
385     }
386     
387     /**
388      * Display a summary of weather data for comparison
389      */
390     private void displayWeatherSummary(WeatherResponse response) {
391         if (response == null) {
392             System.out.println("No data available.");
393             return;
394         }
395         
396         if (response.getCurrent_condition() != null && !response.getCurrent_condition().isEmpty()) {
397             CurrentCondition current = response.getCurrent_condition().get(0);
398             String weatherDesc = current.getWeatherDesc() != null && !current.getWeatherDesc().isEmpty() 
399                     ? current.getWeatherDesc().get(0).getValue() : "Unknown";
400             
401             System.out.println("Temperature: " + current.getTemp_C() + "°C / " + current.getTemp_F() + "°F");
402             System.out.println("Conditions: " + weatherDesc);
403             System.out.println("Humidity: " + current.getHumidity() + "%");
404             System.out.println("Wind: " + current.getWindspeedKmph() + " km/h (" + current.getWinddir16Point() + ")");
405         } else {
406             System.out.println("No current conditions available.");
407         }
408     }
409     
410     //
411     // Model classes for Weather API responses
412     //
413     
414     /**
415      * Main weather response class
416      */
417     public static class WeatherResponse {
418         private List<CurrentCondition> current_condition;
419         private List<NearestArea> nearest_area;
420         private List<Weather> weather;
421         
422         public List<CurrentCondition> getCurrent_condition() {
423             return current_condition;
424         }
425         
426         public void setCurrent_condition(List<CurrentCondition> current_condition) {
427             this.current_condition = current_condition;
428         }
429         
430         public List<NearestArea> getNearest_area() {
431             return nearest_area;
432         }
433         
434         public void setNearest_area(List<NearestArea> nearest_area) {
435             this.nearest_area = nearest_area;
436         }
437         
438         public List<Weather> getWeather() {
439             return weather;
440         }
441         
442         public void setWeather(List<Weather> weather) {
443             this.weather = weather;
444         }
445     }
446     
447     /**
448      * Current weather conditions
449      */
450     public static class CurrentCondition {
451         @JsonProperty("FeelsLikeC")
452         private String FeelsLikeC;
453         @JsonProperty("FeelsLikeF")
454         private String FeelsLikeF;
455         private String cloudcover;
456         private String humidity;
457         private String localObsDateTime;
458         private String observation_time;
459         private String precipInches;
460         private String precipMM;
461         private String pressure;
462         private String pressureInches;
463         private String temp_C;
464         private String temp_F;
465         private String uvIndex;
466         private String visibility;
467         private String visibilityMiles;
468         private String weatherCode;
469         private List<WeatherDesc> weatherDesc;
470         private String winddir16Point;
471         private String winddirDegree;
472         private String windspeedKmph;
473         private String windspeedMiles;
474         
475         public String getFeelsLikeC() {
476             return FeelsLikeC;
477         }
478         
479         public void setFeelsLikeC(String feelsLikeC) {
480             this.FeelsLikeC = feelsLikeC;
481         }
482         
483         public String getFeelsLikeF() {
484             return FeelsLikeF;
485         }
486         
487         public void setFeelsLikeF(String feelsLikeF) {
488             this.FeelsLikeF = feelsLikeF;
489         }
490         
491         public String getCloudcover() {
492             return cloudcover;
493         }
494         
495         public void setCloudcover(String cloudcover) {
496             this.cloudcover = cloudcover;
497         }
498         
499         public String getHumidity() {
500             return humidity;
501         }
502         
503         public void setHumidity(String humidity) {
504             this.humidity = humidity;
505         }
506         
507         public String getLocalObsDateTime() {
508             return localObsDateTime;
509         }
510         
511         public void setLocalObsDateTime(String localObsDateTime) {
512             this.localObsDateTime = localObsDateTime;
513         }
514         
515         public String getObservation_time() {
516             return observation_time;
517         }
518         
519         public void setObservation_time(String observation_time) {
520             this.observation_time = observation_time;
521         }
522         
523         public String getPrecipInches() {
524             return precipInches;
525         }
526         
527         public void setPrecipInches(String precipInches) {
528             this.precipInches = precipInches;
529         }
530         
531         public String getPrecipMM() {
532             return precipMM;
533         }
534         
535         public void setPrecipMM(String precipMM) {
536             this.precipMM = precipMM;
537         }
538         
539         public String getPressure() {
540             return pressure;
541         }
542         
543         public void setPressure(String pressure) {
544             this.pressure = pressure;
545         }
546         
547         public String getPressureInches() {
548             return pressureInches;
549         }
550         
551         public void setPressureInches(String pressureInches) {
552             this.pressureInches = pressureInches;
553         }
554         
555         public String getTemp_C() {
556             return temp_C;
557         }
558         
559         public void setTemp_C(String temp_C) {
560             this.temp_C = temp_C;
561         }
562         
563         public String getTemp_F() {
564             return temp_F;
565         }
566         
567         public void setTemp_F(String temp_F) {
568             this.temp_F = temp_F;
569         }
570         
571         public String getUvIndex() {
572             return uvIndex;
573         }
574         
575         public void setUvIndex(String uvIndex) {
576             this.uvIndex = uvIndex;
577         }
578         
579         public String getVisibility() {
580             return visibility;
581         }
582         
583         public void setVisibility(String visibility) {
584             this.visibility = visibility;
585         }
586         
587         public String getVisibilityMiles() {
588             return visibilityMiles;
589         }
590         
591         public void setVisibilityMiles(String visibilityMiles) {
592             this.visibilityMiles = visibilityMiles;
593         }
594         
595         public String getWeatherCode() {
596             return weatherCode;
597         }
598         
599         public void setWeatherCode(String weatherCode) {
600             this.weatherCode = weatherCode;
601         }
602         
603         public List<WeatherDesc> getWeatherDesc() {
604             return weatherDesc;
605         }
606         
607         public void setWeatherDesc(List<WeatherDesc> weatherDesc) {
608             this.weatherDesc = weatherDesc;
609         }
610         
611         public String getWinddir16Point() {
612             return winddir16Point;
613         }
614         
615         public void setWinddir16Point(String winddir16Point) {
616             this.winddir16Point = winddir16Point;
617         }
618         
619         public String getWinddirDegree() {
620             return winddirDegree;
621         }
622         
623         public void setWinddirDegree(String winddirDegree) {
624             this.winddirDegree = winddirDegree;
625         }
626         
627         public String getWindspeedKmph() {
628             return windspeedKmph;
629         }
630         
631         public void setWindspeedKmph(String windspeedKmph) {
632             this.windspeedKmph = windspeedKmph;
633         }
634         
635         public String getWindspeedMiles() {
636             return windspeedMiles;
637         }
638         
639         public void setWindspeedMiles(String windspeedMiles) {
640             this.windspeedMiles = windspeedMiles;
641         }
642         
643         // Add debug method to print all field values
644         @Override
645         public String toString() {
646             return "CurrentCondition{" +
647                     "feelsLikeC='" + FeelsLikeC + '\'' +
648                     ", feelsLikeF='" + FeelsLikeF + '\'' +
649                     ", temp_C='" + temp_C + '\'' +
650                     ", temp_F='" + temp_F + '\'' +
651                     ", weatherDesc=" + (weatherDesc != null ? weatherDesc.size() : "null") +
652                     '}';
653         }
654     }
655     
656     /**
657      * Nearest area information
658      */
659     public static class NearestArea {
660         private List<Value> areaName;
661         private List<Value> country;
662         private String latitude;
663         private String longitude;
664         private String population;
665         private List<Value> region;
666         
667         public List<Value> getAreaName() {
668             return areaName;
669         }
670         
671         public void setAreaName(List<Value> areaName) {
672             this.areaName = areaName;
673         }
674         
675         public List<Value> getCountry() {
676             return country;
677         }
678         
679         public void setCountry(List<Value> country) {
680             this.country = country;
681         }
682         
683         public String getLatitude() {
684             return latitude;
685         }
686         
687         public void setLatitude(String latitude) {
688             this.latitude = latitude;
689         }
690         
691         public String getLongitude() {
692             return longitude;
693         }
694         
695         public void setLongitude(String longitude) {
696             this.longitude = longitude;
697         }
698         
699         public String getPopulation() {
700             return population;
701         }
702         
703         public void setPopulation(String population) {
704             this.population = population;
705         }
706         
707         public List<Value> getRegion() {
708             return region;
709         }
710         
711         public void setRegion(List<Value> region) {
712             this.region = region;
713         }
714     }
715     
716     /**
717      * Weather forecast information
718      */
719     public static class Weather {
720         private List<Astronomy> astronomy;
721         private String avgtempC;
722         private String avgtempF;
723         private String date;
724         private String maxtempC;
725         private String maxtempF;
726         private String mintempC;
727         private String mintempF;
728         private String sunHour;
729         private String totalSnow_cm;
730         private String uvIndex;
731         
732         public List<Astronomy> getAstronomy() {
733             return astronomy;
734         }
735         
736         public void setAstronomy(List<Astronomy> astronomy) {
737             this.astronomy = astronomy;
738         }
739         
740         public String getAvgtempC() {
741             return avgtempC;
742         }
743         
744         public void setAvgtempC(String avgtempC) {
745             this.avgtempC = avgtempC;
746         }
747         
748         public String getAvgtempF() {
749             return avgtempF;
750         }
751         
752         public void setAvgtempF(String avgtempF) {
753             this.avgtempF = avgtempF;
754         }
755         
756         public String getDate() {
757             return date;
758         }
759         
760         public void setDate(String date) {
761             this.date = date;
762         }
763         
764         public String getMaxtempC() {
765             return maxtempC;
766         }
767         
768         public void setMaxtempC(String maxtempC) {
769             this.maxtempC = maxtempC;
770         }
771         
772         public String getMaxtempF() {
773             return maxtempF;
774         }
775         
776         public void setMaxtempF(String maxtempF) {
777             this.maxtempF = maxtempF;
778         }
779         
780         public String getMintempC() {
781             return mintempC;
782         }
783         
784         public void setMintempC(String mintempC) {
785             this.mintempC = mintempC;
786         }
787         
788         public String getMintempF() {
789             return mintempF;
790         }
791         
792         public void setMintempF(String mintempF) {
793             this.mintempF = mintempF;
794         }
795         
796         public String getSunHour() {
797             return sunHour;
798         }
799         
800         public void setSunHour(String sunHour) {
801             this.sunHour = sunHour;
802         }
803         
804         public String getTotalSnow_cm() {
805             return totalSnow_cm;
806         }
807         
808         public void setTotalSnow_cm(String totalSnow_cm) {
809             this.totalSnow_cm = totalSnow_cm;
810         }
811         
812         public String getUvIndex() {
813             return uvIndex;
814         }
815         
816         public void setUvIndex(String uvIndex) {
817             this.uvIndex = uvIndex;
818         }
819     }
820     
821     /**
822      * Astronomy information
823      */
824     public static class Astronomy {
825         private String moon_illumination;
826         private String moon_phase;
827         private String moonrise;
828         private String moonset;
829         private String sunrise;
830         private String sunset;
831         
832         public String getMoon_illumination() {
833             return moon_illumination;
834         }
835         
836         public void setMoon_illumination(String moon_illumination) {
837             this.moon_illumination = moon_illumination;
838         }
839         
840         public String getMoon_phase() {
841             return moon_phase;
842         }
843         
844         public void setMoon_phase(String moon_phase) {
845             this.moon_phase = moon_phase;
846         }
847         
848         public String getMoonrise() {
849             return moonrise;
850         }
851         
852         public void setMoonrise(String moonrise) {
853             this.moonrise = moonrise;
854         }
855         
856         public String getMoonset() {
857             return moonset;
858         }
859         
860         public void setMoonset(String moonset) {
861             this.moonset = moonset;
862         }
863         
864         public String getSunrise() {
865             return sunrise;
866         }
867         
868         public void setSunrise(String sunrise) {
869             this.sunrise = sunrise;
870         }
871         
872         public String getSunset() {
873             return sunset;
874         }
875         
876         public void setSunset(String sunset) {
877             this.sunset = sunset;
878         }
879     }
880     
881     /**
882      * Weather description
883      */
884     public static class WeatherDesc {
885         private String value;
886         
887         public String getValue() {
888             return value;
889         }
890         
891         public void setValue(String value) {
892             this.value = value;
893         }
894     }
895     
896     /**
897      * Generic value class for nested structures
898      */
899     public static class Value {
900         private String value;
901         
902         public String getValue() {
903             return value;
904         }
905         
906         public void setValue(String value) {
907             this.value = value;
908         }
909     }
910     
911     /**
912      * Simple pair class to hold a location name and its weather response
913      */
914     private static class Pair<F, S> {
915         private final F first;
916         private final S second;
917         
918         public Pair(F first, S second) {
919             this.first = first;
920             this.second = second;
921         }
922         
923         public F getFirst() {
924             return first;
925         }
926         
927         public S getSecond() {
928             return second;
929         }
930     }
931 }