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 */
017package org.apache.commons.cli2.validation;
018
019import java.text.DateFormat;
020import java.text.ParsePosition;
021
022import java.util.Date;
023import java.util.List;
024import java.util.ListIterator;
025
026import org.apache.commons.cli2.resource.ResourceConstants;
027import 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 */
063public 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}