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    *      https://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 <em>base</em> 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 <em>strict</em>,
53       * <em>time</em> and <em>date</em> style parameters.
54       *
55       * @param strict {@code true} if strict
56       *        {@code Format} 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 {@link Calendar} 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          return Integer.compare(value.get(field), compare.get(field));
79      }
80  
81      /**
82       * <p>Calculate the quarter for the specified Calendar.</p>
83       *
84       * @param calendar The Calendar value.
85       * @param monthOfFirstQuarter The  month that the first quarter starts.
86       * @return The calculated quarter.
87       */
88      private int calculateQuarter(final Calendar calendar, final int monthOfFirstQuarter) {
89          // Add Year
90          int year = calendar.get(Calendar.YEAR);
91  
92          final int month = calendar.get(Calendar.MONTH) + 1;
93          final int relativeMonth = month >= monthOfFirstQuarter
94                            ? month - monthOfFirstQuarter
95                            : month + 12 - monthOfFirstQuarter; // CHECKSTYLE IGNORE MagicNumber
96          final int quarter = relativeMonth / 3 + 1; // CHECKSTYLE IGNORE MagicNumber
97          // adjust the year if the quarter doesn't start in January
98          if (month < monthOfFirstQuarter) {
99              --year;
100         }
101         return year * 10 + quarter; // CHECKSTYLE IGNORE MagicNumber
102     }
103 
104     /**
105      * <p>Compares a calendar value to another, indicating whether it is
106      *    equal, less than or more than at a specified level.</p>
107      *
108      * @param value The Calendar value.
109      * @param compare The {@link Calendar} to check the value against.
110      * @param field The field <em>level</em> to compare to - for example, specifying
111      *        {@code Calendar.MONTH} will compare the year and month
112      *        portions of the calendar.
113      * @return Zero if the first value is equal to the second, -1
114      *         if it is less than the second or +1 if it is greater than the second.
115      */
116     protected int compare(final Calendar value, final Calendar compare, final int field) {
117 
118         int result;
119 
120         // Compare Year
121         result = calculateCompareResult(value, compare, Calendar.YEAR);
122         if (result != 0 || field == Calendar.YEAR) {
123             return result;
124         }
125 
126         // Compare Week of Year
127         if (field == Calendar.WEEK_OF_YEAR) {
128             return calculateCompareResult(value, compare, Calendar.WEEK_OF_YEAR);
129         }
130 
131         // Compare Day of the Year
132         if (field == Calendar.DAY_OF_YEAR) {
133             return calculateCompareResult(value, compare, Calendar.DAY_OF_YEAR);
134         }
135 
136         // Compare Month
137         result = calculateCompareResult(value, compare, Calendar.MONTH);
138         if (result != 0 || field == Calendar.MONTH) {
139             return result;
140         }
141 
142         // Compare Week of Month
143         if (field == Calendar.WEEK_OF_MONTH) {
144             return calculateCompareResult(value, compare, Calendar.WEEK_OF_MONTH);
145         }
146 
147         // Compare Date
148         result = calculateCompareResult(value, compare, Calendar.DATE);
149         if (result != 0 || field == Calendar.DATE ||
150                           field == Calendar.DAY_OF_WEEK ||
151                           field == Calendar.DAY_OF_WEEK_IN_MONTH) {
152             return result;
153         }
154 
155         // Compare Time fields
156         return compareTime(value, compare, field);
157 
158     }
159 
160     /**
161      * <p>Compares a calendar's quarter value to another, indicating whether it is
162      *    equal, less than or more than the specified quarter.</p>
163      *
164      * @param value The Calendar value.
165      * @param compare The {@link Calendar} to check the value against.
166      * @param monthOfFirstQuarter The  month that the first quarter starts.
167      * @return Zero if the first quarter is equal to the second, -1
168      *         if it is less than the second or +1 if it is greater than the second.
169      */
170     protected int compareQuarters(final Calendar value, final Calendar compare, final int monthOfFirstQuarter) {
171         final int valueQuarter = calculateQuarter(value, monthOfFirstQuarter);
172         final int compareQuarter = calculateQuarter(compare, monthOfFirstQuarter);
173         return Integer.compare(valueQuarter, compareQuarter);
174     }
175 
176     /**
177      * <p>Compares a calendar time value to another, indicating whether it is
178      *    equal, less than or more than at a specified level.</p>
179      *
180      * @param value The Calendar value.
181      * @param compare The {@link Calendar} to check the value against.
182      * @param field The field <em>level</em> to compare to - for example, specifying
183      *        {@code Calendar.MINUTE} will compare the hours and minutes
184      *        portions of the calendar.
185      * @return Zero if the first value is equal to the second, -1
186      *         if it is less than the second or +1 if it is greater than the second.
187      */
188     protected int compareTime(final Calendar value, final Calendar compare, final int field) {
189 
190         int result;
191 
192         // Compare Hour
193         result = calculateCompareResult(value, compare, Calendar.HOUR_OF_DAY);
194         if (result != 0 || field == Calendar.HOUR || field == Calendar.HOUR_OF_DAY) {
195             return result;
196         }
197 
198         // Compare Minute
199         result = calculateCompareResult(value, compare, Calendar.MINUTE);
200         if (result != 0 || field == Calendar.MINUTE) {
201             return result;
202         }
203 
204         // Compare Second
205         result = calculateCompareResult(value, compare, Calendar.SECOND);
206         if (result != 0 || field == Calendar.SECOND) {
207             return result;
208         }
209 
210         // Compare Milliseconds
211         if (field == Calendar.MILLISECOND) {
212             return calculateCompareResult(value, compare, Calendar.MILLISECOND);
213         }
214 
215         throw new IllegalArgumentException("Invalid field: " + field);
216 
217     }
218 
219     /**
220      * <p>Format a value with the specified {@code DateFormat}.</p>
221      *
222      * @param value The value to be formatted.
223      * @param formatter The Format to use.
224      * @return The formatted value.
225      */
226     @Override
227     protected String format(Object value, final Format formatter) {
228         if (value == null) {
229             return null;
230         }
231         if (value instanceof Calendar) {
232             value = ((Calendar) value).getTime();
233         }
234         return formatter.format(value);
235     }
236 
237     /**
238      * <p>Format an object into a {@link String} using
239      * the specified Locale.</p>
240      *
241      * @param value The value validation is being performed on.
242      * @param locale The locale to use for the Format.
243      * @param timeZone The Time Zone used to format the date,
244      *  system default if null unless value is a {@link Calendar}.
245      * @return The value formatted as a {@link String}.
246      */
247     public String format(final Object value, final Locale locale, final TimeZone timeZone) {
248         return format(value, (String) null, locale, timeZone);
249     }
250 
251     /**
252      * <p>Format an object using the specified pattern and/or
253      *    {@link Locale}.
254      *
255      * @param value The value validation is being performed on.
256      * @param pattern The pattern used to format the value.
257      * @param locale The locale to use for the Format.
258      * @return The value formatted as a {@link String}.
259      */
260     @Override
261     public String format(final Object value, final String pattern, final Locale locale) {
262         return format(value, pattern, locale, (TimeZone) null);
263     }
264 
265     /**
266      * <p>Format an object using the specified pattern and/or
267      *    {@link Locale}.
268      *
269      * @param value The value validation is being performed on.
270      * @param pattern The pattern used to format the value.
271      * @param locale The locale to use for the Format.
272      * @param timeZone The Time Zone used to format the date,
273      *  system default if null unless value is a {@link Calendar}.
274      * @return The value formatted as a {@link String}.
275      */
276     public String format(final Object value, final String pattern, final Locale locale, final TimeZone timeZone) {
277         final DateFormat formatter = (DateFormat) getFormat(pattern, locale);
278         if (timeZone != null) {
279             formatter.setTimeZone(timeZone);
280         } else if (value instanceof Calendar) {
281             formatter.setTimeZone(((Calendar) value).getTimeZone());
282         }
283         return format(value, formatter);
284     }
285 
286     /**
287      * <p>Format an object into a {@link String} using
288      * the specified pattern.</p>
289      *
290      * @param value The value validation is being performed on.
291      * @param pattern The pattern used to format the value.
292      * @param timeZone The Time Zone used to format the date,
293      *  system default if null unless value is a {@link Calendar}.
294      * @return The value formatted as a {@link String}.
295      */
296     public String format(final Object value, final String pattern, final TimeZone timeZone) {
297         return format(value, pattern, (Locale) null, timeZone);
298     }
299 
300     /**
301      * <p>Format an object into a {@link String} using
302      * the default Locale.</p>
303      *
304      * @param value The value validation is being performed on.
305      * @param timeZone The Time Zone used to format the date,
306      *  system default if null unless value is a {@link Calendar}.
307      * @return The value formatted as a {@link String}.
308      */
309     public String format(final Object value, final TimeZone timeZone) {
310         return format(value, (String) null, (Locale) null, timeZone);
311     }
312 
313     /**
314      * <p>Returns a {@code DateFormat} for the specified Locale.</p>
315      *
316      * @param locale The locale a {@code DateFormat} is required for,
317      *        system default if null.
318      * @return The {@code DateFormat} to created.
319      */
320     protected Format getFormat(final Locale locale) {
321         final DateFormat formatter;
322         if (dateStyle >= 0 && timeStyle >= 0) {
323             if (locale == null) {
324                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
325             } else {
326                 formatter = DateFormat.getDateTimeInstance(dateStyle, timeStyle, locale);
327             }
328         } else if (timeStyle >= 0) {
329             if (locale == null) {
330                 formatter = DateFormat.getTimeInstance(timeStyle);
331             } else {
332                 formatter = DateFormat.getTimeInstance(timeStyle, locale);
333             }
334         } else {
335             final int useDateStyle = dateStyle >= 0 ? dateStyle : DateFormat.SHORT;
336             if (locale == null) {
337                 formatter = DateFormat.getDateInstance(useDateStyle);
338             } else {
339                 formatter = DateFormat.getDateInstance(useDateStyle, locale);
340             }
341         }
342         formatter.setLenient(false);
343         return formatter;
344     }
345 
346     /**
347      * <p>Returns a {@code DateFormat} for the specified <em>pattern</em>
348      *    and/or {@link Locale}.</p>
349      *
350      * @param pattern The pattern used to validate the value against or
351      *        {@code null} to use the default for the {@link Locale}.
352      * @param locale The locale to use for the currency format, system default if null.
353      * @return The {@code DateFormat} to created.
354      */
355     @Override
356     protected Format getFormat(final String pattern, final Locale locale) {
357         final DateFormat formatter;
358         final boolean usePattern = !GenericValidator.isBlankOrNull(pattern);
359         if (!usePattern) {
360             formatter = (DateFormat) getFormat(locale);
361         } else if (locale == null) {
362             formatter = new SimpleDateFormat(pattern);
363         } else {
364             final DateFormatSymbols symbols = new DateFormatSymbols(locale);
365             formatter = new SimpleDateFormat(pattern, symbols);
366         }
367         formatter.setLenient(false);
368         return formatter;
369     }
370 
371     /**
372      * <p>Validate using the specified {@link Locale}.
373      *
374      * @param value The value validation is being performed on.
375      * @param pattern The pattern used to format the value.
376      * @param locale The locale to use for the Format, defaults to the default
377      * @return {@code true} if the value is valid.
378      */
379     @Override
380     public boolean isValid(final String value, final String pattern, final Locale locale) {
381         return parse(value, pattern, locale, (TimeZone) null) != null;
382     }
383 
384     /**
385      * <p>Checks if the value is valid against a specified pattern.</p>
386      *
387      * @param value The value validation is being performed on.
388      * @param pattern The pattern used to validate the value against, or the
389      *        default for the {@link Locale} if {@code null}.
390      * @param locale The locale to use for the date format, system default if null.
391      * @param timeZone The Time Zone used to parse the date, system default if null.
392      * @return The parsed value if valid or {@code null} if invalid.
393      */
394     protected Object parse(String value, final String pattern, final Locale locale, final TimeZone timeZone) {
395         value = value == null ? null : value.trim();
396         final String value1 = value;
397         if (GenericValidator.isBlankOrNull(value1)) {
398             return null;
399         }
400         final DateFormat formatter = (DateFormat) getFormat(pattern, locale);
401         if (timeZone != null) {
402             formatter.setTimeZone(timeZone);
403         }
404         return parse(value, formatter);
405 
406     }
407 
408     /**
409      * <p>Process the parsed value, performing any further validation
410      *    and type conversion required.</p>
411      *
412      * @param value The parsed object created.
413      * @param formatter The Format used to parse the value with.
414      * @return The parsed value converted to the appropriate type
415      *         if valid or {@code null} if invalid.
416      */
417     @Override
418     protected abstract Object processParsedValue(Object value, Format formatter);
419 }