View Javadoc

1   package com.atlassian.core.cron.generator;
2   
3   import com.atlassian.core.cron.CronEditorBean;
4   import com.atlassian.core.i18n.I18nTextProvider;
5   import com.atlassian.core.util.map.EasyMap;
6   import org.apache.commons.lang.StringUtils;
7   import org.apache.commons.lang.Validate;
8   
9   import java.text.NumberFormat;
10  import java.util.Arrays;
11  import java.util.Map;
12  
13  /**
14   * Describes Cron Expressions in human readable text.
15   */
16  public class CronExpressionDescriptor
17  {
18      private static final Map<String, String> MINUTE_INCREMENT_TO_MESG_KEY = EasyMap.build(
19              "15", "cron.editor.every.15.minutes",
20              "30", "cron.editor.every.30.minutes",
21              "60", "cron.editor.every.hour",
22              "120", "cron.editor.every.2.hours",
23              "180", "cron.editor.every.3.hours"
24      );
25  
26      private static final Map<String, String> DAY_NUMBERS = EasyMap.build(
27              "1", "sunday",
28              "2", "monday",
29              "3", "tuesday",
30              "4", "wednesday",
31              "5", "thursday",
32              "6", "friday",
33              "7", "saturday"
34      );
35  
36      private static final String CRON_EDITOR_KEY_PREFIX = "cron.editor.";
37      private static final int MINUTES_DIGITS = 2;
38      private static final int DAYS_IN_WEEK = 7;
39      private static final String LAST_COMMA_REGEX = ",([^,]*)$";
40  
41      private final I18nTextProvider i18n;
42  
43  
44      /**
45       * Creates a Descriptor for
46       * @param i18n the I18nBean.
47       */
48      public CronExpressionDescriptor(I18nTextProvider i18n)
49      {
50          Validate.notNull(i18n, "I18nTextProvider must nnot be null");
51          this.i18n = i18n;
52      }
53  
54      /**
55       * Returns the translated name of the day for the given day number using
56       * our cron editor day numbering scheme.
57       * @param number the day number.
58       * @return the name of the day.
59       */
60      private String getDay(String number)
61      {
62          String keypart = DAY_NUMBERS.get(number);
63          Validate.notNull(keypart, "Unable to get day for number '" + number + "'. CronEditorBean likely to be inconsistent");
64          return i18n.getText(CRON_EDITOR_KEY_PREFIX + keypart);
65      }
66  
67      /**
68       * Renders a nice locale-specific and human readable description of the given
69       * cronEditorBean's schedule or, if this can't be understood, the underlying
70       * cron expression.
71       *
72       * @param bean the cronEditorBean.
73       * @return the description.
74       */
75      public String getPrettySchedule(CronEditorBean bean)
76      {
77          if (bean.isAdvancedMode()) {
78              return bean.getCronString();
79          }
80          StringBuilder desc = new StringBuilder();
81          if (bean.isDailyMode())
82          {
83              desc.append(i18n.getText("cron.editor.daily")).append(" ");
84              desc.append(getTimePart(bean));
85          }
86          else if (bean.isDayPerWeekMode())
87          {
88              desc.append(getDayPerWeekDescriptor(bean));
89          }
90          else if (bean.isDaysPerMonthMode())
91          {
92              desc.append(getDayPerMonthDescriptor(bean));
93          }
94  
95          return desc.toString();
96      }
97  
98      private String getDayPerWeekDescriptor(CronEditorBean bean)
99      {
100         StringBuilder desc = new StringBuilder();
101         desc.append(i18n.getText("cron.editor.each"));
102         desc.append(" ");
103 
104         String[] daysArray = bean.getSpecifiedDaysPerWeek().split(",");
105         Arrays.sort(daysArray);
106         String daysString = StringUtils.join(daysArray, ",");
107         daysString = daysString.replaceAll(LAST_COMMA_REGEX, " and $1"); // replace last comma with "and"
108         for (int i = 1; i <= DAYS_IN_WEEK; i++)
109         {
110             String dayNum = Integer.toString(i);
111             daysString = daysString.replaceAll(dayNum, getDay(dayNum));
112         }
113         daysString = daysString.replaceAll(",", ", ");
114         desc.append(daysString).append(" ");
115 
116         desc.append(getTimePart(bean));
117         return desc.toString();
118     }
119 
120     /**
121      * Renders the day per month mode description either the numeric day
122      * (e.g. 1st day of the month) or the ordinal day of week of month (e.g. last Monday of the month)
123      * @param bean the bean whose state is rendered.
124      * @return the rendered description.
125      */
126     private String getDayPerMonthDescriptor(CronEditorBean bean)
127     {
128         StringBuilder desc = new StringBuilder();
129         if (bean.isDayOfWeekOfMonth())
130         {
131             String ordinal = i18n.getText("cron.editor.ordinal." + bean.getDayInMonthOrdinal());
132             String ordinalWeekday = ordinal + " " + getDay(bean.getSpecifiedDaysPerWeek());
133             desc.append(i18n.getText("cron.editor.the.of.every.month", new String[]{ordinalWeekday}));
134             desc.append(" ");
135             desc.append(getTimePart(bean));
136         }
137         else
138         {
139             // numeric day of month
140             desc.append(i18n.getText("cron.editor.the.day.of.every.month", new String[]{i18n.getText("cron.editor.nth." + bean.getDayOfMonth())}));
141             desc.append(" ");
142             desc.append(getTimePart(bean));
143         }
144         return desc.toString();
145     }
146 
147     /**
148      * Renders the time as a run once (e.g. "at 5:25 pm") or a repetition in a range
149      * (e.g. "every 3 hours from 1:00 pm to 10:00 pm")
150      *
151      * @param bean the bean containing the time state.
152      * @return the rendered description.
153      */
154     private String getTimePart(CronEditorBean bean)
155     {
156         StringBuilder desc = new StringBuilder();
157         if (!bean.isRange())
158         {
159             desc.append(getRunOnce(bean));
160         }
161         else
162         {
163             desc.append(getRepeatInRange(bean));
164         }
165         return desc.toString();
166     }
167 
168     /**
169      * Renders the run-once time like this "at 5:25 pm".
170      *
171      * @param bean contains the run once time.
172      * @return a description of the run once time.
173      */
174     private String getRunOnce(CronEditorBean bean)
175     {
176         StringBuilder desc = new StringBuilder();
177         desc.append(i18n.getText("cron.editor.at"));
178         desc.append(" ");
179         desc.append(bean.getHoursRunOnce()).append(":").append(getPaddedMinutes(bean.getMinutes())).append(" ").append(bean.getHoursRunOnceMeridian());
180         return desc.toString();
181     }
182 
183     /**
184      * Renders the repetition in a time range like this "every 3 hours from 1:00 pm to 10:00 pm".
185      *
186      * @param bean containing the repeat and range.
187      * @return the rendered description.
188      */
189     private String getRepeatInRange(CronEditorBean bean)
190     {
191         StringBuilder desc = new StringBuilder();
192         String increment = bean.getIncrementInMinutes();
193         if (increment.equals("0"))
194         {
195             // once per day, we leave this expression out
196         }
197         else
198         {
199             String key = MINUTE_INCREMENT_TO_MESG_KEY.get(increment);
200             desc.append(i18n.getText(key));
201         }
202 
203         if (!bean.is24HourRange())
204         {
205             desc.append(" ");
206             desc.append(i18n.getText("cron.editor.from"));
207             desc.append(" ");
208             desc.append(bean.getHoursFrom()).append(":00 ").append(bean.getHoursFromMeridian());
209             desc.append(" ");
210             desc.append(i18n.getText("cron.editor.to"));
211             desc.append(" ");
212             desc.append(bean.getHoursTo()).append(":00 ").append(bean.getHoursToMeridian());
213         }
214         return desc.toString();
215     }
216 
217     private String getPaddedMinutes(String minutes)
218     {
219         NumberFormat format = NumberFormat.getInstance();
220         format.setMinimumIntegerDigits(MINUTES_DIGITS);
221         return format.format(Integer.parseInt(minutes));
222     }
223 }