WeatherForecast / ForecastLoader.java
ForecastLoader.java
Raw
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;
  }
}