001    package org.hackystat.utilities.time.interval;
002    
003    import java.io.Serializable;
004    import java.text.DateFormatSymbols;
005    import java.text.ParseException;
006    import java.text.SimpleDateFormat;
007    import java.util.Comparator;
008    import java.util.Date;
009    import java.util.Locale;
010    import java.util.TreeMap;
011    
012    import org.hackystat.utilities.time.period.Day;
013    import org.hackystat.utilities.time.period.Week;
014    
015    /**
016     * Provides support for constants and methods in the interval selector.
017     *
018     * @author Hongbing Kou, Philip Johnson
019     */
020    class IntervalUtility {
021      /** Year options from 2000 to 2020. */
022      private TreeMap<String, String> yearOptions;  //NOPMD
023      /** Three letters month map from Jan to Dec. */
024      private TreeMap<String, String> monthOptions;//NOPMD
025      /** Day map from first day to last day of the month.*/
026      private TreeMap<String, String> dayOptions; //NOPMD
027      /** Last half year's week map. */
028      private TreeMap<String, String> weekOptions; //NOPMD
029    
030      /**
031       * Initializes interval utility object.
032       */
033      private IntervalUtility() {
034        // Day map
035        this.dayOptions = new TreeMap<String, String>();
036        for (int i = 1; i <= 31; i++) {
037          String dayValue = (i < 10) ? "0" + i : String.valueOf(i);
038          this.dayOptions.put(dayValue, dayValue);
039        }
040        
041        // Month options
042        this.monthOptions = new TreeMap<String, String>();
043        DateFormatSymbols dateSymbols = new DateFormatSymbols();
044        String[] monthStrings = dateSymbols.getMonths();
045    
046        for (int i = 0; i < monthStrings.length - 1; i++) {
047          String monthValue = (i >= 0 && i < 10) ? "0" + i : String.valueOf(i);
048          this.monthOptions.put(monthStrings[i], monthValue);
049        }
050    
051        
052        // Year options
053        this.yearOptions = new TreeMap<String, String>();
054        for (int i = 2000; i < 2020; i++) {
055          String year = String.valueOf(i);
056          yearOptions.put(year, year);
057        }
058        
059        // Week options
060        this.weekOptions = new TreeMap<String, String>(new WeekComparator());
061        
062        fillWeekOptions();
063      }
064    
065      /**
066       * Fills out the week options.
067       */
068      private void fillWeekOptions() {
069        Day today = Day.getInstance();
070        Week start = new Week(today.inc(-1 * 52 * 7));
071    
072        Week end = new Week();        
073        for (Week week = start; week.compareTo(end) <= 0; week = week.inc()) {
074          this.weekOptions.put(week.getWeekRepresentation(), week.getWeekRepresentation());
075        }
076      }
077      
078      /**
079       * Returns a Week instance given a week string. 
080       * The week string will be something like "11-Jan-2004 to 17-Jan-2004".
081       * Test cases always pass an English locale-based string, so this method will
082       * first try to parse the string using the current locale, then try to parse
083       * it using Locale.US, then fail if neither works. 
084       * 
085       * @param weekString Week string from the week selector.
086       * @return Week object represented by the week string.
087       */
088      public Week getWeek(String weekString) {
089        SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
090        Day day = null;
091        try {
092          day = Day.getInstance(formatter.parse(weekString.substring(0, 11)));
093          return new Week(day);
094        }
095        catch (ParseException e) {
096          // Now we're hosed; both the default and US Locales didn't work. 
097          //ServerProperties.getInstance().getLogger().warning("Cannot parse the first day from " +
098            //                      "week string " + weekString + e);
099         throw new IllegalArgumentException("Week string " + weekString + " is not well formatted" + e);
100        }    
101      }
102    
103      /**
104       * Gets map of years.
105       * 
106       * @return TreeMap of the year.
107       */
108      public TreeMap<String, String> getYearOptions() { //NOPMD
109        return this.yearOptions;
110      }
111      
112      /**
113       * Gets map of months.
114       * 
115       * @return TreeMap of the months.
116       */
117      public TreeMap<String, String> getMonthOptions() { //NOPMD
118        return this.monthOptions;
119      }
120      
121      /**
122       * Gets map of weeks.
123       * 
124       * @return TreeMap of the past 26 weeks.
125       */
126      public TreeMap<String, String> getWeekOptions() { //NOPMD
127        checkWeekUpdates();
128        return this.weekOptions;
129      }
130      
131      /**
132       * Update the week options if today is not in. 
133       */
134      private void checkWeekUpdates() {
135        String lastWeekString = this.weekOptions.firstKey();
136        Week endWeek = getWeek(lastWeekString);
137        
138        // If today is not in the range of week then rebuild the week options.
139        Day today = Day.getInstance();
140        if (today.compareTo(endWeek.getLastDay()) > 0) {
141          fillWeekOptions();   
142        }
143      }
144      
145      /**
146       * Gets map of days.
147       * 
148       * @return TreeMap of the days in a month.
149       */
150      public TreeMap<String, String> getDayOptions() { //NOPMD
151        return this.dayOptions;
152      }
153      
154      /**
155       * Gets the singleton interval utility class.
156       *
157       * @return Singleton interval utility object.
158       */
159      public static IntervalUtility getInstance() {
160        return IntervalUtilityHolder.THE_INSTANCE;
161      }
162      
163      /** Week comparator class. */
164      private static class WeekComparator implements Comparator<String>, Serializable {
165        /**
166         * 
167         */
168        private static final long serialVersionUID = 1L;
169    
170        /**
171         * Compares two month names.
172         *
173         * @param o1  The first month of the comparison
174         * @param o2  The second month of the comparison
175         * @return    -1, 0, 1 if o1 is less than, equal to, or greater than o2
176         */
177        public int compare(String o1, String o2) {
178          String week1 = o1;
179          String week2 = o2;
180    
181          SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
182          
183          try {
184            Date date1 = formatter.parse(week1.substring(0, 11));      
185            Date date2 = formatter.parse(week2.substring(0, 11));
186            
187            //return date1.compareTo(date2);
188            // Sort week in reverse chronological order.
189            return date2.compareTo(date1);
190          }
191          catch (ParseException e) {
192            System.out.println(o1 + " or " + o2 + " cannot be parsed for comparison");
193            e.printStackTrace();
194            return -1;
195          }      
196        }  
197      }
198      
199      /**
200       * Creates a factory to hold the single IntervalUtility instance to avoid concurrent modification
201       * problem according to Page 194, Chapter 9 of Effective Java Programming.
202       */
203      private static class IntervalUtilityHolder {
204        /** Singleton IntervalUtility object. */
205        static final IntervalUtility THE_INSTANCE = new IntervalUtility(); //NOPMD
206      }
207    
208      /**
209       * Get day object from year, month and day strings. 
210       * 
211       * @param yearString Year string in format '2004'.
212       * @param monthString Month string in format '00', '02', '20' etc.
213       * @param dayString  Day string in '12', '02' etc.
214       * 
215       * @return Day object.
216       */
217      public Day getDay(String yearString, String monthString, String dayString) {
218        int year = Integer.parseInt(yearString);
219        int month = Integer.parseInt(monthString);
220        int day = Integer.parseInt(dayString);
221        
222        return Day.getInstance(year, month, day);
223      }
224    
225      /**
226       * Gets current year value in the year options.
227       * 
228       * @return Current year string.
229       */
230      public String getCurrentYear() {
231        return Day.getInstance().getYearString();
232      }
233    
234      /**
235       * Gets current month value. 
236       * 
237       * @return Current month string.
238       */
239      public String getCurrentMonth() {
240        return Day.getInstance().getMonthString();
241      }
242    
243      /**
244       * Gets current day string. 
245       * 
246       * @return Current day string.
247       */
248      public String getCurrentDay() {
249        return Day.getInstance().getDayString();
250      }
251    
252      /**
253       * Gets current week string.
254       * 
255       * @return Current week
256       */
257      public String getCurrentWeek() {
258        return this.weekOptions.lastKey();
259      }
260    }