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
015package org.apache.tapestry.valid;
016
017import java.text.DateFormat;
018import java.text.ParseException;
019import java.text.SimpleDateFormat;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.GregorianCalendar;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.tapestry.IMarkupWriter;
027import org.apache.tapestry.IRequestCycle;
028import 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
038public class DateValidator extends BaseValidator
039{
040    private DateFormat _format;
041
042    private String _displayFormat;
043
044    private Date _minimum;
045
046    private Date _maximum;
047
048    private Calendar _calendar;
049
050    private String _scriptPath = "/org/apache/tapestry/valid/DateValidator.script";
051
052    private static DateFormat defaultDateFormat = new SimpleDateFormat("MM/dd/yyyy");
053
054    private static final String defaultDateDisplayFormat = "MM/DD/YYYY";
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 defaultDateFormat;
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 defaultDateDisplayFormat;
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}