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 &lt;span&gt; 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    }