View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.validator.routines;
18  
19  import java.text.DateFormat;
20  import java.text.DateFormatSymbols;
21  import java.text.Format;
22  import java.text.SimpleDateFormat;
23  import java.util.Calendar;
24  import java.util.Locale;
25  import java.util.TimeZone;
26  
27  import org.apache.commons.validator.GenericValidator;
28  
29  /**
30   * <p>Abstract class for Date/Time/Calendar validation.</p>
31   *
32   * <p>This is a <i>base</i> class for building Date / Time
33   *    Validators using format parsing.</p>
34   *
35   * @since 1.3.0
36   */
37  public abstract class AbstractCalendarValidator extends AbstractFormatValidator {
38  
39      private static final long serialVersionUID = -1410008585975827379L;
40  
41      /**
42       * The date style to use for Locale validation.
43       */
44      private final int dateStyle;
45  
46      /**
47       * The time style to use for Locale validation.
48       */
49      private final int timeStyle;
50  
51      /**
52       * Constructs an instance with the specified <i>strict</i>,
53       * <i>time</i> and <i>date</i> style parameters.
54       *
55       * @param strict {@code true} if strict
56       *        <code>Format</code> parsing should be used.
57       * @param dateStyle the date style to use for Locale validation.
58       * @param timeStyle the time style to use for Locale validation.
59       */
60      public AbstractCalendarValidator(final boolean strict, final int dateStyle, final int timeStyle) {
61          super(strict);
62          this.dateStyle = dateStyle;
63          this.timeStyle = timeStyle;
64      }
65  
66      /**
67       * <p>Compares the field from two calendars indicating whether the field for the
68       *    first calendar is equal to, less than or greater than the field from the
69       *    second calendar.
70       *
71       * @param value The Calendar value.
72       * @param compare The <code>Calendar</code> to check the value against.
73       * @param field The field to compare for the calendars.
74       * @return Zero if the first calendar's field is equal to the seconds, -1
75       *         if it is less than the seconds or +1 if it is greater than the seconds.
76       */
77      private int calculateCompareResult(final Calendar value, final Calendar compare, final int field) {
78          final int difference = value.get(field) - compare.get(field);
79          if (difference < 0) {
80              return -1;
81          }
82          if (difference > 0) {
83              return 1;
84          }
85          return 0;
86      }
87  
88      /**
89       * <p>Calculate the quarter for the specified Calendar.</p>
90       *
91       * @param calendar The Calendar value.
92       * @param monthOfFirstQuarter The  month that the first quarter starts.
93       * @return The calculated quarter.
94       */
95      private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) {
96          // Add Year
97          int year = calendar.get(Calendar.YEAR);
98  
99          final int month = calendar.get(Calendar.MONTH) + 1;
100         final int relativeMonth = month >= monthOfFirstQuarter
101                           ? month - monthOfFirstQuarter
102                           : month + 12 - monthOfFirstQuarter; // CHECKSTYLE IGNORE MagicNumber
103         final int quarter = relativeMonth / 3 + 1; // CHECKSTYLE IGNORE MagicNumber
104         // adjust the year if the quarter doesn't start in January
105         if (month < monthOfFirstQuarter) {
106             --year;
107         }
108         return year * 10 + quarter; // CHECKSTYLE IGNORE MagicNumber
109     }
110 
111     /**
112      * <p>Compares a calendar value to another, indicating whether it is
113      *    equal, less then or more than at a specified level.</p>
114      *
115      * @param value The Calendar value.
116      * @param compare The <code>Calendar</code> to check the value against.
117      * @param field The field <i>level</i> to compare to - e.g. specifying
118      *        <code>Calendar.MONTH</code> will compare the year and month
119      *        portions of the calendar.
120      * @return Zero if the first value is equal to the second, -1
121      *         if it is less than the second or +1 if it is greater than the second.
122      */
123     protected int compare(final Calendar value, final Calendar compare, final int field) {
124 
125         int result;
126 
127         // Compare Year
128         result = calculateCompareResult(value, compare, Calendar.YEAR);
129         if (result != 0 || field == Calendar.YEAR) {
130             return result;
131         }
132 
133         // Compare Week of Year
134         if (field == Calendar.WEEK_OF_YEAR) {
135             return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
136         }
137 
138         // Compare Day of the Year
139         if (field == Calendar.DAY_OF_YEAR) {
140             return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
141         }
142 
143         // Compare Month
144         result = calculateCompareResult(value, compare, Calendar.MONTH);
145         if (result != 0 || field == Calendar.MONTH) {
146             return result;
147         }
148 
149         // Compare Week of Month
150         if (field == Calendar.WEEK_OF_MONTH) {
151             return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
152         }
153 
154         // Compare Date
155         result = calculateCompareResult(value, compare, Calendar.DATE);
156         if (result != 0 || field == Calendar.DATE ||
157                           field == Calendar.DAY_OF_WEEK ||
158                           field == Calendar.DAY_OF_WEEK_IN_MONTH) {
159             return result;
160         }
161 
162         // Compare Time fields
163         return compareTime(value, compare, field);
164 
165     }
166 
167     /**
168      * <p>Compares a calendar's quarter value to another, indicating whether it is
169      *    equal, less then or more than the specified quarter.</p>
170      *
171      * @param value The Calendar value.
172      * @param compare The <code>Calendar</code> to check the value against.
173      * @param monthOfFirstQuarter The  month that the first quarter starts.
174      * @return Zero if the first quarter is equal to the second, -1
175      *         if it is less than the second or +1 if it is greater than the second.
176      */
177     protected int compareQuarters(final Calendar value, final Calendar compare, final int monthOfFirstQuarter) {
178         final int valueQuarter = calculateQuarter(value, monthOfFirstQuarter);
179         final int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
180         if (valueQuarter < compareQuarter) {
181             return -1;
182         }
183         if (valueQuarter > compareQuarter) {
184             return 1;
185         }
186         return 0;
187     }
188 
189     /**
190      * <p>Compares a calendar time value to another, indicating whether it is
191      *    equal, less then or more than at a specified level.</p>
192      *
193      * @param value The Calendar value.
194      * @param compare The <code>Calendar</code> to check the value against.
195      * @param field The field <i>level</i> to compare to - e.g. specifying
196      *        <code>Calendar.MINUTE</code> will compare the hours and minutes
197      *        portions of the calendar.
198      * @return Zero if the first value is equal to the second, -1
199      *         if it is less than the second or +1 if it is greater than the second.
200      */
201     protected int compareTime(final Calendar value, final Calendar compare, final int field) {
202 
203         int result;
204 
205         // Compare Hour
206         result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
207         if (result != 0 || field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY) {
208             return result;
209         }
210 
211         // Compare Minute
212         result = calculateCompareResult(value, compare, Calendar.MINUTE);
213         if (result != 0 || field == Calendar.MINUTE) {
214             return result;
215         }
216 
217         // Compare Second
218         result = calculateCompareResult(value, compare, Calendar.SECOND);
219         if (result != 0 || field == Calendar.SECOND) {
220             return result;
221         }
222 
223         // Compare Milliseconds
224         if (field == Calendar.MILLISECOND) {
225             return calculateCompareResult(value, compare, Calendar.MILLISECOND);
226         }
227 
228         throw new IllegalArgumentException("Invalid field: " + field);
229 
230     }
231 
232     /**
233      * <p>Format a value with the specified <code>DateFormat</code>.</p>
234      *
235      * @param value The value to be formatted.
236      * @param formatter The Format to use.
237      * @return The formatted value.
238      */
239     @Override
240     protected String format(Object value, final Format formatter) {
241         if (value == null) {
242             return null;
243         }
244         if (value instanceof Calendar) {
245             value = ((Calendar) value).getTime();
246         }
247         return formatter.format(value);
248     }
249 
250     /**
251      * <p>Format an object into a <code>String</code> using
252      * the specified Locale.</p>
253      *
254      * @param value The value validation is being performed on.
255      * @param locale The locale to use for the Format.
256      * @param timeZone The Time Zone used to format the date,
257      *  system default if null (unless value is a <code>Calendar</code>.
258      * @return The value formatted as a <code>String</code>.
259      */
260     public String format(final Object value, final Locale locale, final TimeZone timeZone) {
261         return format(value, (String) null, locale, timeZone);
262     }
263 
264     /**
265      * <p>Format an object using the specified pattern and/or
266      *    <code>Locale</code>.
267      *
268      * @param value The value validation is being performed on.
269      * @param pattern The pattern used to format the value.
270      * @param locale The locale to use for the Format.
271      * @return The value formatted as a <code>String</code>.
272      */
273     @Override
274     public String format(final Object value, final String pattern, final Locale locale) {
275         return format(value, pattern, locale, (TimeZone) null);
276     }
277 
278     /**
279      * <p>Format an object using the specified pattern and/or
280      *    <code>Locale</code>.
281      *
282      * @param value The value validation is being performed on.
283      * @param pattern The pattern used to format the value.
284      * @param locale The locale to use for the Format.
285      * @param timeZone The Time Zone used to format the date,
286      *  system default if null (unless value is a <code>Calendar</code>.
287      * @return The value formatted as a <code>String</code>.
288      */
289     public String format(final Object value, final String pattern, final Locale locale, final TimeZone timeZone) {
290         final DateFormat formatter = (DateFormat) getFormat(pattern, locale);
291         if (timeZone != null) {
292             formatter.setTimeZone(timeZone);
293         } else if (value instanceof Calendar) {
294             formatter.setTimeZone(((Calendar) value).getTimeZone());
295         }
296         return format(value, formatter);
297     }
298 
299     /**
300      * <p>Format an object into a <code>String</code> using
301      * the specified pattern.</p>
302      *
303      * @param value The value validation is being performed on.
304      * @param pattern The pattern used to format the value.
305      * @param timeZone The Time Zone used to format the date,
306      *  system default if null (unless value is a <code>Calendar</code>.
307      * @return The value formatted as a <code>String</code>.
308      */
309     public String format(final Object value, final String pattern, final TimeZone timeZone) {
310         return format(value, pattern, (Locale) null, timeZone);
311     }
312 
313     /**
314      * <p>Format an object into a <code>String</code> using
315      * the default Locale.</p>
316      *
317      * @param value The value validation is being performed on.
318      * @param timeZone The Time Zone used to format the date,
319      *  system default if null (unless value is a <code>Calendar</code>.
320      * @return The value formatted as a <code>String</code>.
321      */
322     public String format(final Object value, final TimeZone timeZone) {
323         return format(value, (String) null, (Locale) null, timeZone);
324     }
325 
326     /**
327      * <p>Returns a <code>DateFormat</code> for the specified Locale.</p>
328      *
329      * @param locale The locale a <code>DateFormat</code> is required for,
330      *        system default if null.
331      * @return The <code>DateFormat</code> to created.
332      */
333     protected Format getFormat(final Locale locale) {
334         DateFormat formatter;
335         if (dateStyle >= 0 && timeStyle >= 0) {
336             if (locale == null) {
337                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
338             } else {
339                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
340             }
341         } else if (timeStyle >= 0) {
342             if (locale == null) {
343                 formatter = DateFormat.getTimeInstance(timeStyle);
344             } else {
345                 formatter = DateFormat.getTimeInstance(timeStyle, locale);
346             }
347         } else {
348             final int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
349             if (locale == null) {
350                 formatter = DateFormat.getDateInstance(useDateStyle);
351             } else {
352                 formatter = DateFormat.getDateInstance(useDateStyle, locale);
353             }
354         }
355         formatter.setLenient(false);
356         return formatter;
357 
358     }
359 
360     /**
361      * <p>Returns a <code>DateFormat</code> for the specified <i>pattern</i>
362      *    and/or <code>Locale</code>.</p>
363      *
364      * @param pattern The pattern used to validate the value against or
365      *        {@code null} to use the default for the <code>Locale</code>.
366      * @param locale The locale to use for the currency format, system default if null.
367      * @return The <code>DateFormat</code> to created.
368      */
369     @Override
370     protected Format getFormat(final String pattern, final Locale locale) {
371         DateFormat formatter;
372         final boolean usePattern = !GenericValidator.isBlankOrNull(pattern);
373         if (!usePattern) {
374             formatter = (DateFormat) getFormat(locale);
375         } else if (locale == null) {
376             formatter = new SimpleDateFormat(pattern);
377         } else {
378             final DateFormatSymbols symbols = new DateFormatSymbols(locale);
379             formatter = new SimpleDateFormat(pattern, symbols);
380         }
381         formatter.setLenient(false);
382         return formatter;
383     }
384 
385     /**
386      * <p>Validate using the specified <code>Locale</code>.
387      *
388      * @param value The value validation is being performed on.
389      * @param pattern The pattern used to format the value.
390      * @param locale The locale to use for the Format, defaults to the default
391      * @return {@code true} if the value is valid.
392      */
393     @Override
394     public boolean isValid(final String value, final String pattern, final Locale locale) {
395         return parse(value, pattern, locale, (TimeZone) null) != null;
396     }
397 
398     /**
399      * <p>Checks if the value is valid against a specified pattern.</p>
400      *
401      * @param value The value validation is being performed on.
402      * @param pattern The pattern used to validate the value against, or the
403      *        default for the <code>Locale</code> if {@code null}.
404      * @param locale The locale to use for the date format, system default if null.
405      * @param timeZone The Time Zone used to parse the date, system default if null.
406      * @return The parsed value if valid or {@code null} if invalid.
407      */
408     protected Object parse(String value, final String pattern, final Locale locale, final TimeZone timeZone) {
409         value = value == null ? null : value.trim();
410         final String value1 = value;
411         if (GenericValidator.isBlankOrNull(value1)) {
412             return null;
413         }
414         final DateFormat formatter = (DateFormat) getFormat(pattern, locale);
415         if (timeZone != null) {
416             formatter.setTimeZone(timeZone);
417         }
418         return parse(value, formatter);
419 
420     }
421 
422     /**
423      * <p>Process the parsed value, performing any further validation
424      *    and type conversion required.</p>
425      *
426      * @param value The parsed object created.
427      * @param formatter The Format used to parse the value with.
428      * @return The parsed value converted to the appropriate type
429      *         if valid or {@code null} if invalid.
430      */
431     @Override
432     protected abstract Object processParsedValue(Object value, Format formatter);
433 }