gt:         boolean result = (timestamp > cutoff);
context: /*
   * Copyright 2011 Matt Kenney
   *
   * This file is part of mwthr.
   *
   * mwthr is free software: you can redistribute it and/or modify it under
   * the terms of the GNU Affero General Public License as published by the Free
   * Software Foundation, either version 3 of the License, or (at your option) any
   * later version.
   *
   * mwthr 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 Affero General Public License for more
   * details.
   *
   * You should have received a copy of the GNU Affero General Public License
   * along with mwthr.  If not, see <http://www.gnu.org/licenses/>.
   */
  package com.mwthr.nws;
  
  import java.io.ByteArrayInputStream;
  import java.io.InputStream;
  import java.net.URL;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Calendar;
  import java.util.Date;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Locale;
  import java.util.Map;
  import java.util.TimeZone;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.xpath.XPath;
  import javax.xml.xpath.XPathConstants;
  import javax.xml.xpath.XPathFactory;
  import org.w3c.dom.Document;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  
  import com.mwthr.gae.URLDataHandler;
  
  /**
   * This class fetches XML data from a URL and returns it as a
   * {@link java.util.Map java.util.Map}.
   */
  public class Graph
      implements URLDataHandler
  {
      /**
       * 90 minutes.
       */
      private static final long CACHE_LIFE = 5400000L;
  
      private final DateFormat formatCreation, formatForecast, formatHour, formatDay;
  
      private final List<Map<String, String>> stations;
  
      private final long cutoff;
      
      private TimeZone tz = null;
  
      /**
       * Constructor.
       * @param stations observation station properties maps
       */
      public Graph(List<Map<String, String>> stations)
      {
          this.stations = stations;
  
          // get max timestamp for a valid cache entry
          cutoff = System.currentTimeMillis() - CACHE_LIFE;
  
          // format for parsing observation timestamp
          formatCreation = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
          formatCreation.setTimeZone(TimeZone.getTimeZone("GMT"));
  
          // format for parsing observation timestamp
          formatForecast = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
          formatForecast.setTimeZone(TimeZone.getTimeZone("GMT"));
  
          // format for showing hour of day
          formatHour = new SimpleDateFormat("h aa", Locale.US);
          formatHour.setTimeZone(TimeZone.getTimeZone("GMT"));
  
          // format for showing day of week
          formatDay = new SimpleDateFormat("EEE", Locale.US);
          formatDay.setTimeZone(TimeZone.getTimeZone("GMT"));
      }
  
      private String makeGraphURL(Map<String, String> props, long startTime, int hourCount)
      {
          StringBuilder buffer = new StringBuilder();
          buffer.append("http://chart.apis.google.com/chart");
          buffer.append("?cht=lxy"); // Chart Type
          buffer.append("&chco=FF0000,0000CC"); // Series colors
          buffer.append("&chdl=Temperature|Probability+of+Precipitation"); // Chart legend text
          buffer.append("&chdls=737373,24"); // Chart legend text style
          buffer.append("&chdlp=t"); // Chart legend position
          buffer.append("&chg=0,8,4,1,0,4"); // Grid
          buffer.append("&chls=2"); // Line width
          buffer.append("&chtt=+"); // Chart margin
          buffer.append("&chxt=x,y,y,r,r"); // Visible Axes
          buffer.append("&chxs=0,737373,24|1,FF0000,24|2,FF0000,24|3,0000CC,24|4,0000CC,24"); // Axis Label Style
  
          // Chart size
          buffer.append("&chs=592x500");
  
          // Custom Axis Labels
          buffer.append("&chxl=0:");
          DateFormat format = hourCount > 24 ? formatDay : formatHour;
          Calendar cal = format.getCalendar();
          cal.setTimeInMillis(startTime);
          cal.set(Calendar.MINUTE, 0);
          cal.set(Calendar.SECOND, 0);
          cal.set(Calendar.MILLISECOND, 0);
          StringBuilder positions = new StringBuilder();
          for (int i = 1; i < hourCount; i++)
          {
              cal.add(Calendar.HOUR_OF_DAY, 1);
              if ((hourCount > 24) ? (cal.get(Calendar.HOUR_OF_DAY) == 12) : ((cal.get(Calendar.HOUR_OF_DAY) % 4) == 0))
              {
                  long pos = (cal.getTimeInMillis() - startTime)/3600000L;
                  positions.append(",");
                  positions.append(pos);
                  buffer.append("|");
                  buffer.append(format.format(cal.getTime()));
              }
          }
          buffer.append("|2:|%C2%B0F|4:|%25");
  
          // Axis Label Positions
          buffer.append("&chxp=0");
          buffer.append(positions.toString());
          buffer.append("|1,0,20,40,60,80,100|2,50|3,0,20,40,60,80,100|4,50");
  
          // Axis Range
          buffer.append("&chxr=0,0,");
          buffer.append(hourCount);
          buffer.append("|1,-15,110|2,-15,110|3,-15,110|4,-15,110"); 
  
          // Data Scale
          buffer.append("&chds=0,");
          buffer.append(hourCount);
          buffer.append(",-15,110,0,");
          buffer.append(hourCount);
          buffer.append(",-15,110");
  
          // Data
          buffer.append("&chd=t");
  
          String[] data = new String[] { "temperature", "probability-of-precipitation" };
  
          for (int k = 0; k < data.length; k++)
          {
              String[] xValues = props.get(data[k] + "-x").split(",");
              String[] yValues = props.get(data[k] + "-y").split(",");
              int count = 0;
              for (int i = 0; i < xValues.length; i++)
              {
                  final double x = Double.parseDouble(xValues[i]);
                  if (x < 0)
                  {
                      // interpolate at start of graph, if possible
                      if (i + 1 < xValues.length && i < yValues.length)
                      {
                          final double x2 = Double.parseDouble(xValues[i + 1]);
                          final double xc = Math.min(x2, 0.0);
                          final double y1 = Double.parseDouble(yValues[i]);
                          final double y2 = Double.parseDouble(yValues[i + 1]);
                          final double yc = y1 + (xc - x)/(x2 - x)*(y2 - y1);
                          xValues[i] = String.format("%.2f", xc);
                          yValues[i] = String.format("%.2f", yc);
                      }
                  }
                  else if (x > hourCount)
                  {
                      // interpolate at end of graph, if possible
                      if (i < 1 || yValues.length <= i)
                      {
                          break;
                      }
                      final double x1 = Double.parseDouble(xValues[i - 1]);
                      final double xc = hourCount;
                      final double y1 = Double.parseDouble(yValues[i - 1]);
                      final double y2 = Double.parseDouble(yValues[i]);
                      final double yc = y1 + (xc - x1)/(x - x1)*(y2 - y1);
                      xValues[i] = String.format("%.2f", xc);
                      yValues[i] = String.format("%.2f", yc);
                  }
                  buffer.append(i > 0 ? "," : (k > 0 ? "|" : ":"));
                  buffer.append(xValues[i]);
                  count++;
                  if (x > hourCount)
                  {
                      break;
                  }
              }
              for (int i = 0; i < count && i < yValues.length; i++)
              {
                  buffer.append(i > 0 ? "," : "|");
                  buffer.append(yValues[i]);
              }
          }
  
          return buffer.toString();
      }
  
      @Override
      public List<URL> getURLs()
      {
          List<URL> result = new ArrayList<URL>();
  
          StringBuilder buffer = new StringBuilder();
          buffer.append("http://www.weather.gov/forecasts/xml/sample_products/browser_interface/ndfdXMLclient.php");
          buffer.append("?product=time-series&temp=temp&pop12=pop12");
          int length = buffer.length();
  
          for (Map<String, String> station : stations)
          {
              // build the NDFD URL
              buffer.setLength(length);
              buffer.append("&lat=");
              buffer.append(station.get("lat"));
              buffer.append("&lon=");
              buffer.append(station.get("lon"));
              String urlString = buffer.toString();
              try
              {
                  result.add(new URL(urlString));
              }
              catch (Exception e)
              {
                  Logger.getLogger(getClass().getName()).log(Level.WARNING, "Problem parsing \"" + urlString + "\"", e);
              }
          }
          return result;
      }
  
      @Override
      public boolean isValid(Map<String, String> props)
      {
          String text = props.get("creation-date");
          long timestamp = WeatherDataHandler.parseDate(formatCreation, text);
  