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> </td></tr>
40 * <tr><td>formatx</td><td>see SimpleDateFormat javadoc</td>
41 * <td> </td></tr>
42 * <tr><td colspan=3>where x is >= 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ü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 >= 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 }