001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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 org.apache.commons.cli2.validation;
018    
019    import java.text.DateFormat;
020    import java.text.ParsePosition;
021    
022    import java.util.Date;
023    import java.util.List;
024    import java.util.ListIterator;
025    
026    import org.apache.commons.cli2.resource.ResourceConstants;
027    import org.apache.commons.cli2.resource.ResourceHelper;
028    
029    /**
030     * The <code>DateValidator</code> validates the argument values
031     * are date or time value(s).
032     *
033     * The following example shows how to validate that
034     * an argument value(s) is a Date of the following
035     * type: d/M/yy (see {@link java.text.DateFormat}).
036     *
037     * <pre>
038     * DateFormat date = new SimpleDateFormat("d/M/yy");
039     * ...
040     * ArgumentBuilder builder = new ArgumentBuilder();
041     * Argument dateFormat =
042     *     builder.withName("date");
043     *            .withValidator(new DateValidator(dateFormat));
044     * </pre>
045     *
046     * The following example shows how to validate that
047     * an argument value(s) is a time of the following
048     * type: HH:mm:ss (see {@link java.text.DateFormat}).
049     *
050     * <pre>
051     * DateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
052     * ...
053     * ArgumentBuilder builder = new ArgumentBuilder();
054     * Argument time =
055     *     builder.withName("time");
056     *            .withValidator(new DateValidator(timeFormat));
057     * </pre>
058     *
059     * @author John Keyes
060     *
061     * @see java.text.DateFormat
062     */
063    public class DateValidator implements Validator {
064        /** i18n */
065        private static final ResourceHelper resources = ResourceHelper.getResourceHelper();
066    
067        /** an array of permitted DateFormats */
068        private DateFormat[] formats;
069    
070        /** minimum Date allowed i.e: a valid date occurs later than this date */
071        private Date minimum;
072    
073        /** maximum Date allowed i.e: a valid date occurs earlier than this date */
074        private Date maximum;
075    
076        /** leniant parsing */
077        private boolean isLenient;
078    
079        /**
080         * Creates a Validator for the default date/time format
081         */
082        public DateValidator() {
083            this(DateFormat.getInstance());
084        }
085    
086        /**
087         * Creates a Validator for the specified DateFormat.
088         *
089         * @param format
090         *            a DateFormat which dates must conform to
091         */
092        public DateValidator(final DateFormat format) {
093            setFormat(format);
094        }
095    
096        /**
097         * Creates a Validator for the List of specified DateFormats.
098         *
099         * @param formats
100         *            a List of DateFormats which dates must conform to
101         */
102        public DateValidator(final List formats) {
103            setFormats(formats);
104        }
105    
106        /**
107         * Creates a Validator for dates.
108         *
109         * @return DateValidator a Validator for dates
110         */
111        public static DateValidator getDateInstance() {
112            return new DateValidator(DateFormat.getDateInstance());
113        }
114    
115        /**
116         * Creates a Validator for times.
117         *
118         * @return DateValidator a Validator for times
119         */
120        public static DateValidator getTimeInstance() {
121            return new DateValidator(DateFormat.getTimeInstance());
122        }
123    
124        /**
125         * Creates a Validator for date/times
126         *
127         * @return DateValidator a Validator for date/times
128         */
129        public static DateValidator getDateTimeInstance() {
130            return new DateValidator(DateFormat.getDateTimeInstance());
131        }
132    
133        /**
134         * Validate each String value in the specified List against this instances
135         * permitted DateFormats.
136         *
137         * If a value is valid then it's <code>String</code> value in the list is
138         * replaced with it's <code>Date</code> value.
139         *
140         * @see org.apache.commons.cli2.validation.Validator#validate(java.util.List)
141         */
142        public void validate(final List values)
143            throws InvalidArgumentException {
144            // for each value
145            for (final ListIterator i = values.listIterator(); i.hasNext();) {
146                final String value = (String) i.next();
147    
148                Date date = null;
149    
150                // create a resuable ParsePosition instance
151                final ParsePosition pp = new ParsePosition(0);
152    
153                // for each permitted DateFormat
154                for (int f = 0; (f < this.formats.length) && (date == null); ++f) {
155                    // reset the parse position
156                    pp.setIndex(0);
157                    date = this.formats[f].parse(value, pp);
158    
159                    // if the wrong number of characters have been parsed
160                    if (pp.getIndex() < value.length()) {
161                        date = null;
162                    }
163                }
164    
165                // if date has not been set throw an InvalidArgumentException
166                if (date == null) {
167                    throw new InvalidArgumentException(value);
168                }
169    
170                // if the date is outside the bounds
171                if (isDateEarlier(date) || isDateLater(date)) {
172                    throw new InvalidArgumentException(resources.getMessage(ResourceConstants.DATEVALIDATOR_DATE_OUTOFRANGE,
173                                                                            value));
174                }
175    
176                // replace the value in the list with the actual Date
177                i.set(date);
178            }
179        }
180    
181        /**
182         * Sets whether this validator uses lenient parsing.
183         *
184         * @param lenient whether this validator uses lenient parsing
185         */
186        public void setLenient(final boolean lenient) {
187            for (int i = 0; i < this.formats.length; i++) {
188                this.formats[i].setLenient(lenient);
189            }
190    
191            this.isLenient = lenient;
192        }
193    
194        /**
195         * Returns whether this validator uses lenient parsing.
196         *
197         * @return whether this validator uses lenient parsing
198         */
199        public boolean isLenient() {
200            return this.isLenient;
201        }
202    
203        /**
204         * Returns the maximum date permitted.
205         *
206         * @return Date the maximum date permitted. If no maximum date has been
207         *         specified then return <code>null</code>.
208         */
209        public Date getMaximum() {
210            return maximum;
211        }
212    
213        /**
214         * Sets the maximum Date to the specified value.
215         *
216         * @param maximum
217         *            the maximum Date permitted
218         */
219        public void setMaximum(final Date maximum) {
220            this.maximum = maximum;
221        }
222    
223        /**
224         * Returns the minimum date permitted.
225         *
226         * @return Date the minimum date permitted. If no minimum date has been
227         *         specified then return <code>null</code>.
228         */
229        public Date getMinimum() {
230            return minimum;
231        }
232    
233        /**
234         * Sets the minimum Date to the specified value.
235         *
236         * @param minimum
237         *            the minimum Date permitted
238         */
239        public void setMinimum(Date minimum) {
240            this.minimum = minimum;
241        }
242    
243        /**
244         * Returns whether the specified Date is later than the maximum date.
245         *
246         * @param date
247         *            the Date to evaluate
248         *
249         * @return boolean whether <code>date</code> is earlier than the maximum
250         *         date
251         */
252        private boolean isDateLater(Date date) {
253            return (maximum != null) && (date.getTime() > maximum.getTime());
254        }
255    
256        /**
257         * Returns whether the specified Date is earlier than the minimum date.
258         *
259         * @param date
260         *            the Date to evaluate
261         *
262         * @return boolean whether <code>date</code> is earlier than the minimum
263         *         date
264         */
265        private boolean isDateEarlier(Date date) {
266            return (minimum != null) && (date.getTime() < minimum.getTime());
267        }
268    
269        /**
270         * Sets the date format permitted.
271         *
272         * @param format
273         *              the format to use
274         */
275        public void setFormat(final DateFormat format) {
276            setFormats(new DateFormat[] { format });
277        }
278    
279        /**
280         * Sets the date formats permitted.
281         *
282         * @param formats
283         *               the List of DateFormats to use
284         */
285        public void setFormats(final List formats) {
286            setFormats((DateFormat[]) formats.toArray(new DateFormat[formats.size()]));
287        }
288    
289        /**
290         * Sets the date formats permitted.
291         *
292         * @param formats
293         *               the array of DateFormats to use
294         */
295        public void setFormats(final DateFormat[] formats) {
296            this.formats = formats;
297            setLenient(this.isLenient);
298        }
299    
300        /**
301         * Gets the date formats permitted.
302         *
303         * @return the permitted formats
304         */
305        public DateFormat[] getFormats() {
306            return this.formats;
307        }
308    }