import java.io.FileNotFoundException; import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.ArrayList; import java.util.Scanner; import java.io.File; /** * IForecastLoader implementation that can be instantialized. Returns an array of Forecast objects * from either an API or local XML file (if given the path). * * @author Tommy F */ public class ForecastLoader { /** * This method loads the weather forecast from the API. * * @return A list of forecast objects * @throws IllegalStateException if an error occurred and the data could not be fetched/parsed */ public List<IForecast> fetchWeather() throws IllegalStateException { try { // URL to fetch weather data from: URL url = new URL("https://api.openweathermap.org/data/2.5/forecast?lat=43.07&lon=-89.40&mode=xml" + "&appid=838e9c763ec92ece472fa2cb7aa58556"); // Open input stream and save to String InputStream stream = url.openStream(); String text = new String(stream.readAllBytes(), StandardCharsets.UTF_8); // Pass into helper method return parseString(text); } catch (Exception e) { throw new IllegalStateException(); } } /** * This method loads the weather forecast from an XML file. This is for backup only if the API * does not work. It requires an XML file with the same exact contents as the API response. * * @param filepathToXML path to the XML file relative to the location of this class * @return A list of forecast objects * @throws FileNotFoundException If the file given does not exist * @throws IllegalStateException if an error occurred and the data could not be parsed */ public List<IForecast> fetchWeatherXML(String filepathToXML) throws FileNotFoundException, IllegalStateException { try { // Open file File file = new File(filepathToXML); Scanner inputStream = new Scanner(file, "UTF-8"); // Skip past XML header inputStream.nextLine(); // Save entire XML response as string String fullResponse = inputStream.nextLine(); inputStream.close(); // Pass into helper method return parseString(fullResponse); } catch (FileNotFoundException e) { throw new FileNotFoundException("ForecastLoader could not find the XML file given"); } catch (Exception e) { throw new IllegalStateException(); } } /** * Private helper method that parses XML input into an array of Forecast objects. * * @param fullResponse The XML input to parse, given as a full String in the exact format returned * from the API * @return A list of Forecast objects */ private List<IForecast> parseString(String fullResponse) { List<IForecast> forecastArray = new ArrayList<>(); // Split response into list, separated by "<" String[] response = fullResponse.split("<"); // Keep note the index representing the start of each forecast ArrayList<Integer> indexes = new ArrayList<Integer>(); for (int i = 0; i < response.length; i++) { if (response[i].contains("time")) { if (!response[i].contains("/") && !response[i].contains("calc") && !response[i].contains("zone")) { indexes.add(i); } } } // Variables that will be used for the forecast objects int timecode = 0; double avgTempKelvin = 0; double minTempKelvin = 0; double maxTempKelvin = 0; String description = ""; int humidity = 0; double windSpeed = 0; int windDirectionDeg = 0; // Variables that will be used only for parsing String workingString; int endIndex; for (Integer i : indexes) { // 1. Create timecode int from string representation workingString = response[i].substring(11, 24); // Trim string char[] timeCharArray = workingString.toCharArray(); // Save as char array to filter non // nums. workingString = ""; // Remove characters in the array which are not numbers for (int j = 0; j < timeCharArray.length; j++) { if (timeCharArray[j] != '-' && timeCharArray[j] != 'T') { workingString = workingString + timeCharArray[j]; // Only add integer chars to String } } // Convert String representation of timecode to int timecode = Integer.parseInt(workingString); // 2. Extract weather description workingString = response[i + 1].substring(26); endIndex = workingString.indexOf('"'); description = workingString.substring(0, endIndex); // 3. Extract wind direction workingString = response[i + 5].substring(19); endIndex = workingString.indexOf('"'); windDirectionDeg = Integer.parseInt(workingString.substring(0, endIndex)); // 4. Extract wind speed workingString = response[i + 7].substring(15); endIndex = workingString.indexOf('"'); windSpeed = Double.parseDouble(workingString.substring(0, endIndex)); // 5. Extract humidity workingString = response[i + 17].substring(16); endIndex = workingString.indexOf('"'); humidity = Integer.parseInt(workingString.substring(0, endIndex)); // 6. Extract temperatures: // Average temp. workingString = response[i + 11].substring(33); endIndex = workingString.indexOf('"'); avgTempKelvin = Double.parseDouble(workingString.substring(0, endIndex)); // Minimum temp. workingString = workingString.substring(endIndex + 1); endIndex = workingString.indexOf('"'); workingString = workingString.substring(endIndex + 1, workingString.length()); endIndex = workingString.indexOf('"'); minTempKelvin = Double.parseDouble(workingString.substring(0, endIndex)); // Maximum temp. workingString = workingString.substring(endIndex + 1); endIndex = workingString.indexOf('"'); maxTempKelvin = Double.parseDouble(workingString.substring(endIndex + 1, workingString.length() - 2)); // Create new Forecast object with weather data Forecast fcast = new Forecast(timecode, avgTempKelvin, minTempKelvin, maxTempKelvin, description, humidity, windSpeed, windDirectionDeg); forecastArray.add(fcast); // Add new object to the array } return forecastArray; } }