1   package com.atlassian.core.cron.parser;
2   
3   import com.atlassian.core.util.collection.EasyList;
4   import org.apache.log4j.Logger;
5   
6   import java.util.List;
7   
8   /**
9    * Represents the minutes part of a cron string. This class is responsible for parsing and validating this entry.
10   * The {@link #isValid()} method refers only to what is supported by the cron editor for the minutes entry.
11   * <p/>
12   * Valid minutes include only those that are either: a multiple of 5 between 0 and 55 inclusive; or "0/15" or "0/30"
13   * meaning every 15 minutes or every 30 minutes.
14   */
15  public class CronMinutesEntry
16  {
17      private static final Logger log = Logger.getLogger(CronMinutesEntry.class);
18  
19      static final String MINUTE_INCREMENT_SEPARATOR = "/";
20  
21      /**
22       * cronEntry should only contain legal characters '/', and digit
23       */
24      private static final String REGEX_VALID = "[\\d/]+";
25  
26      private static final int UNSET_FLAG = -1;
27  
28      private static final int MAX_MINUTES = 59;
29  
30      /** we only support minute values that are divisible by this. */
31      private static final int MINUTE_FACTOR = 5;
32  
33      /**
34       * We are only supporting these minute increments.
35       */
36      private static final List VALID_INCREMENT_IN_MINUTES = EasyList.build(new Integer(15), new Integer(30));
37      private int runOnce = UNSET_FLAG;
38      private boolean valid = true;
39  
40      private int increment = UNSET_FLAG;
41  
42      /**
43       * Parses the given minute field into this CronMinutesEntry state.
44       * @param cronEntry the minute field of a cron string.
45       */
46      public CronMinutesEntry(String cronEntry)
47      {
48          if (cronEntry == null)
49          {
50              throw new IllegalArgumentException("Can not create a cron entry from a null value.");
51          }
52          parseEntry(cronEntry);
53      }
54  
55      /**
56       * Indicates that the minute field is able to be handled by the editor, concerns
57       * both the minute value and the optional increment value.
58       * @return true only if the minute field is valid.
59       */
60      public boolean isValid()
61      {
62          boolean validIncrement = (increment == UNSET_FLAG) || (VALID_INCREMENT_IN_MINUTES.contains(new Integer(increment)) && runOnce == 0);
63  
64          return valid && runOnce <= MAX_MINUTES && runOnce >= 0 && (runOnce % MINUTE_FACTOR == 0) && validIncrement;
65      }
66  
67  
68      /**
69       * The minute value for a single fire time.
70       * @return the minute value.
71       */
72      public int getRunOnce()
73      {
74          return runOnce;
75      }
76  
77      private void parseEntry(String cronEntry)
78      {
79  
80          if (!cronEntry.matches(REGEX_VALID))
81          {
82              valid = false;
83          }
84          else
85          {
86              int separator = cronEntry.indexOf(MINUTE_INCREMENT_SEPARATOR);
87              if (separator >= 0)
88              {
89                  String incrementStr = cronEntry.substring(separator + 1, cronEntry.length());
90                  try
91                  {
92                      this.increment = Integer.parseInt(incrementStr);
93                  }
94                  catch (NumberFormatException nfe)
95                  {
96                      log.debug("The increment portion of the hour cron entry must be an integer.");
97                      valid = false;
98                  }
99                  //Chop this off, we don't need it anymore
100                 cronEntry = cronEntry.substring(0, separator);
101             }
102             try
103             {
104                 this.runOnce = Integer.parseInt(cronEntry);
105             }
106             catch (NumberFormatException nfe)
107             {
108                 log.debug("The minute of the cron entry must be an integer, instead it is: " + cronEntry);
109                 valid = false;
110             }
111         }
112     }
113 
114 
115     public int getIncrement()
116     {
117         return increment;
118     }
119 
120     public boolean hasIncrement()
121     {
122         return increment > 0;
123     }
124 }