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 }