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     */
017    package net.dpml.cli.validation;
018    
019    import java.text.DateFormat;
020    import java.text.ParsePosition;
021    
022    import java.util.Date;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.ListIterator;
026    
027    import net.dpml.cli.resource.ResourceConstants;
028    import 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="http://www.dpml.net">Digital Product Meta Library</a>
061     * @version 1.0.0
062     * @see java.text.DateFormat
063     */
064    public 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    }