1 package com.atlassian.core.cron.parser;
2
3 import com.atlassian.core.cron.CronEditorBean;
4 import org.apache.commons.lang.StringUtils;
5
6 import java.util.StringTokenizer;
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 public class CronExpressionParser
30 {
31
32
33
34 public static final String DEFAULT_CRONSTRING = "0 0 1 ? * *";
35
36 private static final String VALID_DAY_OF_MONTH = "0123456789L";
37 private static final String WILDCARD = "*";
38 private static final String NOT_APPLICABLE = "?";
39 private static final int MINUTES_IN_HOUR = 60;
40 private static final int NUM_CRON_FIELDS = 7;
41 private static final int NUM_CRON_FIELDS_NO_YEAR = NUM_CRON_FIELDS - 1;
42
43 private CronMinutesEntry minutesEntry;
44 private CronHoursEntry hoursEntry;
45 private String dayOfMonth;
46 private String month;
47 private String daysOfWeek;
48 private CronDayOfWeekEntry daysOfWeekEntry;
49 private String year;
50 private String cronString;
51 private boolean isDaily;
52 private boolean isDayPerWeek;
53 private boolean isDaysPerMonth;
54 private boolean isAdvanced;
55 private boolean validForEditor;
56 private String seconds;
57
58
59
60
61
62 public CronExpressionParser()
63 {
64 this(DEFAULT_CRONSTRING);
65 }
66
67
68
69
70
71
72 public CronExpressionParser(String cronString)
73 {
74 this.cronString = cronString;
75 parseAndValidateCronString(this.cronString);
76 }
77
78
79
80
81
82
83
84 public CronEditorBean getCronEditorBean()
85 {
86 CronEditorBean cronEditorBean = new CronEditorBean();
87 cronEditorBean.setCronString(cronString);
88
89 cronEditorBean.setSeconds(seconds);
90
91 cronEditorBean.setDayOfMonth(dayOfMonth);
92 cronEditorBean.setIncrementInMinutes(Integer.toString(getIncrementInMinutes()));
93
94
95 if (getIncrementInMinutes() == 0)
96 {
97 cronEditorBean.setHoursRunOnce(Integer.toString(getHoursEntry().getRunOnce()));
98 cronEditorBean.setHoursRunOnceMeridian(getHoursEntry().getRunOnceMeridian());
99 }
100 else
101 {
102
103 cronEditorBean.setHoursFrom(Integer.toString(getHoursEntry().getFrom()));
104 cronEditorBean.setHoursFromMeridian(getHoursEntry().getFromMeridian());
105 cronEditorBean.setHoursTo(Integer.toString(getHoursEntry().getTo()));
106 cronEditorBean.setHoursToMeridian(getHoursEntry().getToMeridian());
107 }
108
109
110 cronEditorBean.setMinutes(Integer.toString(getMinutesEntry().getRunOnce()));
111
112
113 cronEditorBean.setSpecifiedDaysOfWeek(getDaysOfWeekEntry().getDaysAsNumbers());
114 cronEditorBean.setDayInMonthOrdinal(getDaysOfWeekEntry().getDayInMonthOrdinal());
115
116
117 if (isDailyMode())
118 {
119 cronEditorBean.setMode(CronEditorBean.DAILY_SPEC_MODE);
120 }
121 else if (isDayPerWeekMode())
122 {
123 cronEditorBean.setMode(CronEditorBean.DAYS_OF_WEEK_SPEC_MODE);
124 }
125 else if (isDaysPerMonthMode())
126 {
127
128 cronEditorBean.setDayOfWeekOfMonth(isDayOfWeekOfMonth());
129 cronEditorBean.setMode(CronEditorBean.DAYS_OF_MONTH_SPEC_MODE);
130 }
131 else
132 {
133 cronEditorBean.setMode(CronEditorBean.ADVANCED_MODE);
134 }
135
136 return cronEditorBean;
137 }
138
139
140
141
142
143
144
145 public String getCronString()
146 {
147 return cronString;
148 }
149
150
151
152
153
154
155
156
157 public boolean isValidForEditor()
158 {
159 return validForEditor;
160 }
161
162
163
164
165
166
167 public boolean isAdvancedMode()
168 {
169 return isAdvanced;
170 }
171
172
173
174
175
176
177 public boolean isDailyMode()
178 {
179 return isDaily;
180 }
181
182
183
184
185
186
187 public boolean isDayPerWeekMode()
188 {
189 return isDayPerWeek;
190 }
191
192
193
194
195
196
197 public boolean isDaysPerMonthMode()
198 {
199 return isDaysPerMonth;
200 }
201
202
203
204
205
206
207
208 public boolean isDayOfWeekOfMonth()
209 {
210 return notApplicable(dayOfMonth) && !isWild(daysOfWeek) && !notApplicable(daysOfWeek);
211 }
212
213
214
215
216
217
218 public String getDayOfMonth()
219 {
220 return dayOfMonth;
221 }
222
223
224
225
226
227
228 public CronMinutesEntry getMinutesEntry()
229 {
230 return minutesEntry;
231 }
232
233
234
235
236
237
238 public CronHoursEntry getHoursEntry()
239 {
240 return hoursEntry;
241 }
242
243
244
245
246
247
248 public CronDayOfWeekEntry getDaysOfWeekEntry()
249 {
250 return daysOfWeekEntry;
251 }
252
253
254
255
256
257
258
259
260 public int getIncrementInMinutes()
261 {
262 return calculateIncrementInMinutes();
263 }
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 private int calculateIncrementInMinutes()
280 {
281 int incrementInMinutes = 0;
282 final boolean minutesHasIncrement = minutesEntry.hasIncrement();
283 final boolean hoursHasIncrement = hoursEntry.hasIncrement();
284 final int minutesIncrement = minutesEntry.getIncrement();
285 final int hoursIncrement = hoursEntry.getIncrement();
286
287 if (minutesHasIncrement && hoursHasIncrement && hoursIncrement != 1)
288 {
289 incrementInMinutes = 0;
290 }
291 else if (minutesHasIncrement)
292 {
293 incrementInMinutes = minutesIncrement;
294 }
295 else if (hoursHasIncrement)
296 {
297 incrementInMinutes = hoursIncrement * MINUTES_IN_HOUR;
298 }
299
300 return incrementInMinutes;
301 }
302
303 private void parseAndValidateCronString(String cronString)
304 {
305 parseCronString(cronString);
306
307
308 updateEditorFlags();
309
310 if (!validForEditor)
311 {
312
313 parseCronString(DEFAULT_CRONSTRING);
314 }
315 }
316
317 private void parseCronString(String cronString)
318 {
319 StringTokenizer st = new StringTokenizer(cronString);
320 if (st.countTokens() != NUM_CRON_FIELDS && st.countTokens() != NUM_CRON_FIELDS_NO_YEAR)
321 {
322 throw new IllegalArgumentException("The provided cron string does not have " + NUM_CRON_FIELDS + " parts: " + cronString);
323 }
324
325
326
327
328 this.seconds = st.nextToken();
329 String minutes = st.nextToken();
330 String hours = st.nextToken();
331 this.dayOfMonth = st.nextToken();
332 this.month = st.nextToken();
333 this.daysOfWeek = st.nextToken();
334 this.hoursEntry = new CronHoursEntry(hours);
335 this.minutesEntry = new CronMinutesEntry(minutes);
336 this.daysOfWeekEntry = new CronDayOfWeekEntry(daysOfWeek);
337
338 if(st.hasMoreTokens())
339 {
340 this.year = st.nextToken();
341 }
342 }
343
344
345
346
347
348 private void updateEditorFlags()
349 {
350 isDaily = (isWild(dayOfMonth) || notApplicable(dayOfMonth)) && isWild(month) && (isWild(daysOfWeek) || notApplicable(daysOfWeek));
351 isDayPerWeek = (isWild(dayOfMonth) || notApplicable(dayOfMonth)) && isWild(month) && daysOfWeekEntry.getDayInMonthOrdinal() == null && !isWild(daysOfWeek);
352
353 boolean numericDayOfMonth = !notApplicable(dayOfMonth) && !isWild(dayOfMonth) && isWild(month) && notApplicable(daysOfWeek) && StringUtils.containsOnly(dayOfMonth.toUpperCase(), VALID_DAY_OF_MONTH);
354 boolean dayOfWeekOfMonth = notApplicable(dayOfMonth) && isWild(month) && !isWild(daysOfWeek) && !notApplicable(daysOfWeek) && daysOfWeekEntry.getDayInMonthOrdinal() != null;
355 isDaysPerMonth = dayOfWeekOfMonth || numericDayOfMonth;
356
357 boolean isValidMode = isDaily || isDayPerWeek || isDaysPerMonth;
358
359 boolean hoursAndMinutesAreValid = hoursEntry.isValid() && minutesEntry.isValid();
360 boolean daysOfWeekAreValid = daysOfWeekEntry.isValid();
361
362
363
364 boolean incrementsValid = !(hoursEntry.hasIncrement() && minutesEntry.hasIncrement()) || hoursEntry.getIncrement() == 1;
365
366
367
368
369
370
371
372
373
374 hoursAndMinutesAreValid = hoursAndMinutesAreValid && !(hoursEntry.isRunOnce() && minutesEntry.hasIncrement());
375
376 validForEditor = "0".equals(seconds) && isWild(month) && isValidMode && hoursAndMinutesAreValid && daysOfWeekAreValid && incrementsValid && StringUtils.isEmpty(year);
377
378 if (!validForEditor)
379 {
380 isDaily = false;
381 isDayPerWeek = false;
382 isDaysPerMonth = false;
383 isAdvanced = true;
384 }
385 }
386
387 private boolean isWild(String expressionPart)
388 {
389 return WILDCARD.equals(expressionPart);
390 }
391
392 private boolean notApplicable(String expressionPart)
393 {
394 return NOT_APPLICABLE.equals(expressionPart);
395 }
396 }