View Javadoc
1   package org.apache.fulcrum.intake.validator;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.text.ParseException;
23  import java.text.SimpleDateFormat;
24  import java.util.ArrayList;
25  import java.util.Date;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.commons.lang3.StringUtils;
30  
31  /**
32   * Validates numbers with the following constraints in addition to those
33   * listed in DefaultValidator.
34   *
35   * <table>
36   * <caption>Validation rules</caption>
37   * <tr><th>Name</th><th>Valid Values</th><th>Default Value</th></tr>
38   * <tr><td>format</td><td>see SimpleDateFormat javadoc</td>
39   * <td>&nbsp;</td></tr>
40   * <tr><td>formatx</td><td>see SimpleDateFormat javadoc</td>
41   * <td>&nbsp;</td></tr>
42   * <tr><td colspan=3>where x is &gt;= 1 to specify multiple date
43   *         formats.  Only one format rule should have a message</td></tr>
44   * <tr><td>flexible</td><td>true, as long as DateFormat can parse the date,
45   *                            allow it, and false</td>
46   * <td>false</td></tr>
47   * </table>
48   *
49   * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
50   * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
51   * @author <a href="mailto:Colin.Chalmers@maxware.nl">Colin Chalmers</a>
52   * @author <a href="mailto:jh@byteaction.de">J&uuml;rgen Hoffmann</a>
53   * @author <a href="mailto:seade@backstagetech.com.au">Scott Eade</a>
54   * @version $Id$
55   */
56  public class DateStringValidator
57          extends DefaultValidator<Date>
58  {
59      private static final String DEFAULT_DATE_MESSAGE =
60              "Date could not be parsed";
61  
62      /** A list of date formats to try */
63      private List<String> dateFormats = null;
64  
65      /** An error message if no date could be parsed */
66      private String dateFormatMessage = null;
67  
68      /** A flag that is passed to the DateFormat lenient feature */
69      private boolean flexible = false;
70  
71      /**
72       * Default Constructor
73       */
74      public DateStringValidator()
75      {
76          super();
77          dateFormats = new ArrayList<String>(5);
78      }
79  
80      /**
81       * Constructor to use when initializing Object
82       *
83       * @param paramMap a map of parameters
84       * @throws InvalidMaskException one of the mask rules is invalid
85       */
86      @Override
87  	public void init(Map<String, ? extends Constraint> paramMap)
88              throws InvalidMaskException
89      {
90          super.init(paramMap);
91  
92          Constraint constraint = paramMap.get(FORMAT_RULE_NAME);
93  
94          if (constraint != null)
95          {
96              dateFormats.add(constraint.getValue());
97              setDateFormatMessage(constraint.getMessage());
98          }
99  
100         for(int i = 1 ;; i++)
101         {
102             constraint = paramMap.get(FORMAT_RULE_NAME + i);
103 
104             if (constraint == null)
105             {
106                 break; // for
107             }
108 
109             dateFormats.add(constraint.getValue());
110             setDateFormatMessage(constraint.getMessage());
111         }
112 
113         if (StringUtils.isEmpty(dateFormatMessage))
114         {
115             dateFormatMessage = DEFAULT_DATE_MESSAGE;
116         }
117 
118         constraint = paramMap.get(FLEXIBLE_RULE_NAME);
119 
120         if (constraint != null)
121         {
122             flexible = Boolean.valueOf(constraint.getValue()).booleanValue();
123         }
124     }
125 
126     /**
127      * Determine whether a testValue meets the criteria specified
128      * in the constraints defined for this validator
129      *
130      * @param testValue a <code>String</code> to be tested
131      * @throws ValidationException containing an error message if the
132      * testValue did not pass the validation tests.
133      */
134     @Override
135 	public void assertValidity(String testValue)
136             throws ValidationException
137     {
138         super.assertValidity(testValue);
139 
140         if (required || StringUtils.isNotEmpty(testValue))
141         {
142             try
143             {
144                 parse(testValue);
145             }
146             catch (ParseException e)
147             {
148                 errorMessage = dateFormatMessage;
149                 throw new ValidationException(dateFormatMessage);
150             }
151         }
152     }
153 
154     /**
155      * Parses the String s according to the rules/formats for this validator.
156      * The formats provided by the "formatx" rules (where x is &gt;= 1) are
157      * used <strong>before</strong> the "format" rules to allow for a display
158      * format that includes a 4 digit year, but that will parse the date using
159      * a format that accepts 2 digit years.
160      *
161      * @param s possibly a date string
162      * @return the date parsed
163      * @throws ParseException indicates that the string could not be
164      * parsed into a date.
165      */
166     public Date parse(String s)
167             throws ParseException
168     {
169         Date date = null;
170 
171         if (s == null)
172         {
173             throw new ParseException("Input string was null", -1);
174         }
175 
176         SimpleDateFormat sdf = new SimpleDateFormat();
177         sdf.setLenient(flexible);
178 
179         for (int i = 1; i < dateFormats.size() && date == null; i++)
180         {
181             sdf.applyPattern(dateFormats.get(i));
182 
183             try
184             {
185                 date = sdf.parse(s);
186             }
187             catch (ParseException e)
188             {
189                 // ignore
190             }
191         }
192 
193         if (date == null)
194         {
195             sdf.applyPattern(dateFormats.get(0));
196 
197             try
198             {
199                 date = sdf.parse(s);
200             }
201             catch (ParseException e)
202             {
203                 // ignore
204             }
205         }
206 
207         if (date == null)
208         {
209             // Try default
210             date = SimpleDateFormat.getInstance().parse(s);
211         }
212 
213         // if the date still has not been parsed at this point, throw
214         // a ParseException.
215         if (date == null)
216         {
217             throw new ParseException("Could not parse the date", 0);
218         }
219 
220         return date;
221     }
222 
223     /**
224      * Formats a date into a String.  The format used is from
225      * the first format rule found for the field.
226      *
227      * @param date the Date object to convert into a string.
228      * @return formatted date
229      */
230     public String format(Date date)
231     {
232         String s = null;
233 
234         if (date != null && !dateFormats.isEmpty())
235         {
236             SimpleDateFormat sdf = new SimpleDateFormat(dateFormats.get(0));
237             s = sdf.format(date);
238         }
239 
240         return s;
241     }
242 
243 
244     // ************************************************************
245     // **                Bean accessor methods                   **
246     // ************************************************************
247 
248     /**
249      * Get the value of minLengthMessage.
250      *
251      * @return value of minLengthMessage.
252      */
253     public String getDateFormatMessage()
254     {
255         return dateFormatMessage;
256     }
257 
258     /**
259      * Only sets the message if the new message has some information.
260      * So the last setMessage call with valid data wins.  But later calls
261      * with null or empty string will not affect a previous valid setting.
262      *
263      * @param message  Value to assign to minLengthMessage.
264      */
265     public void setDateFormatMessage(String message)
266     {
267         if (StringUtils.isNotEmpty(message))
268         {
269             dateFormatMessage = message;
270         }
271     }
272 
273     /**
274      * Get the value of dateFormats.
275      *
276      * @return value of dateFormats.
277      */
278     public List<String> getDateFormats()
279     {
280         return dateFormats;
281     }
282 
283     /**
284      * Set the value of dateFormats.
285      *
286      * @param formats  Value to assign to dateFormats.
287      */
288     public void setDateFormats(List<String> formats)
289     {
290         this.dateFormats = formats;
291     }
292 
293     /**
294      * Get the value of flexible.
295      *
296      * @return value of flexible.
297      */
298     public boolean isFlexible()
299     {
300         return flexible;
301     }
302 
303     /**
304      * Set the value of flexible.
305      *
306      * @param flexible  Value to assign to flexible.
307      */
308     public void setFlexible(boolean flexible)
309     {
310         this.flexible = flexible;
311     }
312 }