001/*
002 * Copyright 2003-2005 The Apache Software Foundation
003 * Copyright 2005 Stephen McConnell
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package net.dpml.cli.validation;
018
019import java.text.DateFormat;
020import java.text.ParsePosition;
021
022import java.util.Date;
023import java.util.Iterator;
024import java.util.List;
025import java.util.ListIterator;
026
027import net.dpml.cli.resource.ResourceConstants;
028import net.dpml.cli.resource.ResourceHelper;
029
030/**
031 * The <code>DateValidator</code> validates the argument values
032 * are date or time value(s).
033 *
034 * The following example shows how to validate that
035 * an argument value(s) is a Date of the following
036 * type: d/M/yy (see {@link java.text.DateFormat}).
037 *
038 * <pre>
039 * DateFormat date = new SimpleDateFormat("d/M/yy");
040 * ...
041 * ArgumentBuilder builder = new ArgumentBuilder();
042 * Argument dateFormat =
043 *     builder.withName("date");
044 *            .withValidator(new DateValidator(dateFormat));
045 * </pre>
046 *
047 * The following example shows how to validate that
048 * an argument value(s) is a time of the following
049 * type: HH:mm:ss (see {@link java.text.DateFormat}).
050 *
051 * <pre>
052 * DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
053 * ...
054 * ArgumentBuilder builder = new ArgumentBuilder();
055 * Argument time =
056 *     builder.withName("time");
057 *            .withValidator(new DateValidator(timeFormat));
058 * </pre>
059 *
060 * @author <a href="@PUBLISHER-URL@">@PUBLISHER-NAME@</a>
061 * @version @PROJECT-VERSION@
062 * @see java.text.DateFormat
063 */
064public class DateValidator implements Validator 
065{
066    /** i18n */
067    private static final ResourceHelper RESOURCES = 
068      ResourceHelper.getResourceHelper();
069
070    /** an array of permitted DateFormats */
071    private DateFormat[] m_formats;
072
073    /** minimum Date allowed i.e: a valid date occurs later than this date */
074    private Date m_minimum;
075
076    /** maximum Date allowed i.e: a valid date occurs earlier than this date */
077    private Date m_maximum;
078
079    /** leniant parsing */
080    private boolean m_isLenient;
081
082    /**
083     * Creates a Validator for the default date/time format
084     */
085    public DateValidator() 
086    {
087        this( DateFormat.getInstance() );
088    }
089
090    /**
091     * Creates a Validator for the specified DateFormat.
092     *
093     * @param format
094     *            a DateFormat which dates must conform to
095     */
096    public DateValidator( final DateFormat format )
097    {
098        setFormat( format );
099    }
100
101    /**
102     * Creates a Validator for the List of specified DateFormats.
103     *
104     * @param formats a List of DateFormats which dates must conform to
105     */
106    public DateValidator( final List formats )
107    {
108        for( Iterator iter = formats.iterator(); iter.hasNext();)
109        {
110            DateFormat format = (DateFormat) iter.next();
111        }
112        setFormats( formats );
113    }
114
115    /**
116     * Creates a Validator for dates.
117     *
118     * @return DateValidator a Validator for dates
119     */
120    public static DateValidator getDateInstance()
121    {
122        return new DateValidator( DateFormat.getDateInstance() );
123    }
124
125    /**
126     * Creates a Validator for times.
127     *
128     * @return DateValidator a Validator for times
129     */
130    public static DateValidator getTimeInstance()
131    {
132        return new DateValidator( DateFormat.getTimeInstance() );
133    }
134
135    /**
136     * Creates a Validator for date/times
137     *
138     * @return DateValidator a Validator for date/times
139     */
140    public static DateValidator getDateTimeInstance()
141    {
142        return new DateValidator( DateFormat.getDateTimeInstance() );
143    }
144
145   /**
146    * Validate each String value in the specified List against this instances
147    * permitted DateFormats.
148    *
149    * If a value is valid then it's <code>String</code> value in the list is
150    * replaced with it's <code>Date</code> value.
151    *
152    * @param values the list of values to validate 
153    * @exception InvalidArgumentException if a value is invalid
154    * @see net.dpml.cli.validation.Validator#validate(java.util.List)
155    */
156    public void validate( final List values ) throws InvalidArgumentException
157    {
158        // for each value
159        for( final ListIterator i = values.listIterator(); i.hasNext();) 
160        {
161            final Object next = i.next();
162            if( next instanceof Date )
163            {
164                return;
165            }
166        
167            final String value = (String) next;
168
169            Date date = null;
170
171            // create a resuable ParsePosition instance
172            final ParsePosition pp = new ParsePosition( 0 );
173
174            // for each permitted DateFormat
175            for( int f=0; ( f<m_formats.length ) && ( date == null ); ++f )
176            {
177                // reset the parse position
178                pp.setIndex( 0 );
179                date = m_formats[f].parse( value, pp );
180
181                // if the wrong number of characters have been parsed
182                if( pp.getIndex() < value.length() )
183                {
184                    date = null;
185                }
186            }
187
188            // if date has not been set throw an InvalidArgumentException
189            if( date == null )
190            {
191                throw new InvalidArgumentException( value );
192            }
193
194            // if the date is outside the bounds
195            if( isDateEarlier( date ) || isDateLater( date ) )
196            {
197                throw new InvalidArgumentException(
198                  RESOURCES.getMessage(
199                    ResourceConstants.DATEVALIDATOR_DATE_OUTOFRANGE,
200                    value ) );
201            }
202
203            // replace the value in the list with the actual Date
204            i.set( date );
205        }
206    }
207
208   /**
209    * Set the leaniant flag.
210    * @param lenient true if leniant
211    */
212    public void setLeniant( final boolean lenient )
213    {
214        for( int i=0; i<m_formats.length; i++ )
215        {
216            m_formats[i].setLenient( lenient );
217        }
218        m_isLenient = lenient;
219    }
220
221   /**
222    * Return the leaniant flag.
223    * @return true if leniant
224    */
225    public boolean isLeniant() 
226    {
227        return m_isLenient;
228    }
229
230    /**
231     * Returns the maximum date permitted.
232     *
233     * @return Date the maximum date permitted. If no maximum date has been
234     *         specified then return <code>null</code>.
235     */
236    public Date getMaximum()
237    {
238        return m_maximum;
239    }
240
241    /**
242     * Sets the maximum Date to the specified value.
243     *
244     * @param maximum
245     *            the maximum Date permitted
246     */
247    public void setMaximum( final Date maximum )
248    {
249        m_maximum = maximum;
250    }
251
252    /**
253     * Returns the minimum date permitted.
254     *
255     * @return Date the minimum date permitted. If no minimum date has been
256     *         specified then return <code>null</code>.
257     */
258    public Date getMinimum()
259    {
260        return m_minimum;
261    }
262
263    /**
264     * Sets the minimum Date to the specified value.
265     *
266     * @param minimum
267     *            the minimum Date permitted
268     */
269    public void setMinimum( Date minimum )
270    {
271        m_minimum = minimum;
272    }
273
274    /**
275     * Returns whether the specified Date is later than the maximum date.
276     *
277     * @param date
278     *            the Date to evaluate
279     *
280     * @return boolean whether <code>date</code> is earlier than the maximum
281     *         date
282     */
283    private boolean isDateLater( Date date )
284    {
285        return ( m_maximum != null ) && ( date.getTime() > m_maximum.getTime() );
286    }
287
288    /**
289     * Returns whether the specified Date is earlier than the minimum date.
290     *
291     * @param date the Date to evaluate
292     * @return boolean whether <code>date</code> is earlier than the minimum
293     *         date
294     */
295    private boolean isDateEarlier( Date date )
296    {
297        return ( m_minimum != null ) && ( date.getTime() < m_minimum.getTime() );
298    }
299
300    /**
301     * Sets the date format permitted.
302     *
303     * @param format
304     *              the format to use
305     */
306    public void setFormat( final DateFormat format )
307    {
308        setFormats( new DateFormat[]{format} );
309    }
310
311    /**
312     * Sets the date formats permitted.
313     *
314     * @param formats
315     *               the List of DateFormats to use
316     */
317    public void setFormats( final List formats )
318    {
319        setFormats( (DateFormat[]) formats.toArray( new DateFormat[formats.size()] ) );
320    }
321
322    /**
323     * Sets the date formats permitted.
324     *
325     * @param formats the array of DateFormats to use
326     */
327    public void setFormats( final DateFormat[] formats )
328    {
329        m_formats = formats;
330        setLeniant( m_isLenient );
331    }
332
333    /**
334     * Gets the date formats permitted.
335     *
336     * @return the permitted formats
337     */
338    public DateFormat[] getFormats()
339    {
340        return m_formats;
341    }
342}