001 // Copyright 2004, 2005 The Apache Software Foundation
002 //
003 // Licensed under the Apache License, Version 2.0 (the "License");
004 // you may not use this file except in compliance with the License.
005 // You may obtain a copy of the License at
006 //
007 // http://www.apache.org/licenses/LICENSE-2.0
008 //
009 // Unless required by applicable law or agreed to in writing, software
010 // distributed under the License is distributed on an "AS IS" BASIS,
011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012 // See the License for the specific language governing permissions and
013 // limitations under the License.
014
015 package org.apache.tapestry.valid;
016
017 import java.text.DateFormat;
018 import java.text.ParseException;
019 import java.text.SimpleDateFormat;
020 import java.util.Calendar;
021 import java.util.Date;
022 import java.util.GregorianCalendar;
023 import java.util.HashMap;
024 import java.util.Map;
025
026 import org.apache.tapestry.IMarkupWriter;
027 import org.apache.tapestry.IRequestCycle;
028 import org.apache.tapestry.form.IFormComponent;
029
030 /**
031 * Provides input validation for strings treated as dates. In addition, allows a minimum and maximum
032 * date to be set.
033 *
034 * @author Howard Lewis Ship
035 * @since 1.0.8
036 */
037
038 public class DateValidator extends BaseValidator
039 {
040 public static final String DEFAULT_DISPLAY_FORMAT = "MM/DD/YYYY";
041
042 public static final DateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy");
043
044 private DateFormat _format;
045
046 private String _displayFormat;
047
048 private Date _minimum;
049
050 private Date _maximum;
051
052 private Calendar _calendar;
053
054 private String _scriptPath = "/org/apache/tapestry/valid/DateValidator.script";
055
056 private String _dateTooEarlyMessage;
057
058 private String _dateTooLateMessage;
059
060 private String _invalidDateFormatMessage;
061
062 public DateValidator()
063 {
064
065 }
066
067 /**
068 * Initializes the DateValidator with properties defined by the initializer.
069 *
070 * @since 4.0
071 */
072
073 public DateValidator(String initializer)
074 {
075 super(initializer);
076 }
077
078 public void setFormat(DateFormat value)
079 {
080 _format = value;
081 }
082
083 public DateFormat getFormat()
084 {
085 return _format;
086 }
087
088 /**
089 * @return the {@link DateFormat}the validator will use, returning the default if no other date
090 * format is specified via {@link #setFormat(DateFormat)}
091 * @since 3.0
092 */
093 public DateFormat getEffectiveFormat()
094 {
095 if (_format == null)
096 return DEFAULT_DATE_FORMAT;
097
098 return _format;
099 }
100
101 public String getDisplayFormat()
102 {
103 return _displayFormat;
104 }
105
106 public void setDisplayFormat(String value)
107 {
108 _displayFormat = value;
109 }
110
111 /**
112 * @return the display format message the validator will use, returning the default if no other
113 * display format message is specified. The default is the
114 * {@link SimpleDateFormat#toPattern()}for {@link SimpleDateFormat}s, or "MM/DD/YYYY"
115 * for unknown {@link DateFormat}subclasses.
116 * @since 3.0
117 */
118 public String getEffectiveDisplayFormat()
119 {
120 if (_displayFormat == null)
121 {
122 DateFormat format = getEffectiveFormat();
123 if (format instanceof SimpleDateFormat)
124 return ((SimpleDateFormat) format).toPattern();
125
126 return DEFAULT_DISPLAY_FORMAT;
127 }
128
129 return _displayFormat;
130 }
131
132 public String toString(IFormComponent file, Object value)
133 {
134 if (value == null)
135 return null;
136
137 Date date = (Date) value;
138
139 DateFormat format = getEffectiveFormat();
140
141 // DateFormat is not threadsafe, so guard access to it.
142
143 synchronized (format)
144 {
145 return format.format(date);
146 }
147 }
148
149 public Object toObject(IFormComponent field, String value) throws ValidatorException
150 {
151 if (checkRequired(field, value))
152 return null;
153
154 DateFormat format = getEffectiveFormat();
155
156 Date result;
157
158 try
159 {
160 // DateFormat is not threadsafe, so guard access
161 // to it.
162
163 synchronized (format)
164 {
165 result = format.parse(value);
166 }
167
168 if (_calendar == null)
169 _calendar = new GregorianCalendar();
170
171 _calendar.setTime(result);
172
173 // SimpleDateFormat allows two-digit dates to be
174 // entered, i.e., 12/24/66 is Dec 24 0066 ... that's
175 // probably not what is really wanted, so treat
176 // it as an invalid date.
177
178 if (_calendar.get(Calendar.YEAR) < 1000)
179 result = null;
180
181 }
182 catch (ParseException ex)
183 {
184 // ParseException does not include a useful error message
185 // about what's wrong.
186 result = null;
187 }
188
189 if (result == null)
190 throw new ValidatorException(buildInvalidDateFormatMessage(field),
191 ValidationConstraint.DATE_FORMAT);
192
193 // OK, check that the date is in range.
194
195 if (_minimum != null && _minimum.compareTo(result) > 0)
196 throw new ValidatorException(buildDateTooEarlyMessage(field, format.format(_minimum)),
197 ValidationConstraint.TOO_SMALL);
198
199 if (_maximum != null && _maximum.compareTo(result) < 0)
200 throw new ValidatorException(buildDateTooLateMessage(field, format.format(_maximum)),
201 ValidationConstraint.TOO_LARGE);
202
203 return result;
204
205 }
206
207 public Date getMaximum()
208 {
209 return _maximum;
210 }
211
212 public void setMaximum(Date maximum)
213 {
214 _maximum = maximum;
215 }
216
217 public Date getMinimum()
218 {
219 return _minimum;
220 }
221
222 public void setMinimum(Date minimum)
223 {
224 _minimum = minimum;
225 }
226
227 /**
228 * @since 2.2
229 */
230
231 public void renderValidatorContribution(IFormComponent field, IMarkupWriter writer,
232 IRequestCycle cycle)
233 {
234 if (!(isClientScriptingEnabled() && isRequired()))
235 return;
236
237 Map symbols = new HashMap();
238
239 symbols.put("requiredMessage", buildRequiredMessage(field));
240
241 processValidatorScript(_scriptPath, cycle, field, symbols);
242 }
243
244 /**
245 * @since 2.2
246 */
247
248 public String getScriptPath()
249 {
250 return _scriptPath;
251 }
252
253 /**
254 * Allows a developer to use the existing validation logic with a different client-side script.
255 * This is often sufficient to allow application-specific error presentation (perhaps by using
256 * DHTML to update the content of a <span> tag, or to use a more sophisticated pop-up
257 * window than <code>window.alert()</code>).
258 *
259 * @since 2.2
260 */
261
262 public void setScriptPath(String scriptPath)
263 {
264 _scriptPath = scriptPath;
265 }
266
267 /** @since 3.0 */
268
269 public String getDateTooEarlyMessage()
270 {
271 return _dateTooEarlyMessage;
272 }
273
274 /** @since 3.0 */
275
276 public String getDateTooLateMessage()
277 {
278 return _dateTooLateMessage;
279 }
280
281 /** @since 3.0 */
282
283 public String getInvalidDateFormatMessage()
284 {
285 return _invalidDateFormatMessage;
286 }
287
288 /** @since 3.0 */
289
290 protected String buildInvalidDateFormatMessage(IFormComponent field)
291 {
292 String pattern = getPattern(_invalidDateFormatMessage, "invalid-date-format", field
293 .getPage().getLocale());
294
295 return formatString(pattern, field.getDisplayName(), getEffectiveDisplayFormat());
296 }
297
298 /** @since 3.0 * */
299
300 protected String buildDateTooEarlyMessage(IFormComponent field, String earliestDate)
301 {
302 String pattern = getPattern(_dateTooEarlyMessage, "date-too-early", field.getPage()
303 .getLocale());
304
305 return formatString(pattern, field.getDisplayName(), earliestDate);
306 }
307
308 /** @since 3.0 */
309
310 protected String buildDateTooLateMessage(IFormComponent field, String latestDate)
311 {
312 String pattern = getPattern(_dateTooLateMessage, "date-too-late", field.getPage()
313 .getLocale());
314
315 return formatString(pattern, field.getDisplayName(), latestDate);
316 }
317
318 /**
319 * Overrides the bundle key <code>date-too-early</code>. Parameter {0} is the display name of
320 * the field. Parameter {1} is the earliest allowed date.
321 *
322 * @since 3.0
323 */
324
325 public void setDateTooEarlyMessage(String string)
326 {
327 _dateTooEarlyMessage = string;
328 }
329
330 /**
331 * Overrides the bundle key <code>date-too-late</code>. Parameter {0} is the display name of
332 * the field. Parameter {1} is the latest allowed date.
333 *
334 * @since 3.0
335 */
336
337 public void setDateTooLateMessage(String string)
338 {
339 _dateTooLateMessage = string;
340 }
341
342 /**
343 * Overrides the bundle key <code>invalid-date-format</code>. Parameter {0} is the display
344 * name of the field. Parameter {1} is the allowed format.
345 *
346 * @since 3.0
347 */
348
349 public void setInvalidDateFormatMessage(String string)
350 {
351 _invalidDateFormatMessage = string;
352 }
353
354 }