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  
18  package org.apache.commons.beanutils.converters;
19  
20  import java.text.DateFormat;
21  import java.text.SimpleDateFormat;
22  import java.util.Calendar;
23  import java.util.Date;
24  import java.util.GregorianCalendar;
25  import java.util.Locale;
26  
27  import junit.framework.TestCase;
28  
29  import org.apache.commons.beanutils.ConversionException;
30  import org.apache.commons.beanutils.Converter;
31  
32  /**
33   * Abstract base for <Date>Converter classes.
34   *
35   * @version $Id$
36   */
37  
38  public abstract class DateConverterTestBase extends TestCase {
39  
40      // ------------------------------------------------------------------------
41  
42      /**
43       * Construct a new test case.
44       * @param name Name of the test
45       */
46      public DateConverterTestBase(final String name) {
47          super(name);
48      }
49  
50      // ------------------------------------------------------------------------
51  
52      /**
53       * Create the Converter with no default value.
54       * @return A new Converter
55       */
56      protected abstract DateTimeConverter makeConverter();
57  
58      /**
59       * Create the Converter with a default value.
60       * @param defaultValue The default value
61       * @return A new Converter
62       */
63      protected abstract DateTimeConverter makeConverter(Object defaultValue);
64  
65      /**
66       * Return the expected type
67       * @return The expected type
68       */
69      protected abstract Class<?> getExpectedType();
70  
71      /**
72       * Convert from a Calendar to the appropriate Date type
73       *
74       * @param value The Calendar value to convert
75       * @return The converted value
76       */
77      protected abstract Object toType(Calendar value);
78  
79      // ------------------------------------------------------------------------
80  
81      /**
82       * Assumes ConversionException in response to covert(getExpectedType(), null).
83       */
84      public void testConvertNull() {
85          try {
86              makeConverter().convert(getExpectedType(), null);
87              fail("Expected ConversionException");
88          } catch(final ConversionException e) {
89              // expected
90          }
91      }
92  
93      /**
94       * Assumes convert() returns some non-null
95       * instance of getExpectedType().
96       */
97      public void testConvertDate() {
98          final String[] message= {
99              "from Date",
100             "from Calendar",
101             "from SQL Date",
102             "from SQL Time",
103             "from SQL Timestamp"
104         };
105 
106         final long now = System.currentTimeMillis();
107 
108         final Object[] date = {
109             new Date(now),
110             new java.util.GregorianCalendar(),
111             new java.sql.Date(now),
112             new java.sql.Time(now),
113             new java.sql.Timestamp(now)
114         };
115 
116         // Initialize calendar also with same ms to avoid a failing test in a new time slice
117         ((GregorianCalendar)date[1]).setTime(new Date(now));
118 
119         for (int i = 0; i < date.length; i++) {
120             final Object val = makeConverter().convert(getExpectedType(), date[i]);
121             assertNotNull("Convert " + message[i] + " should not be null", val);
122             assertTrue("Convert " + message[i] + " should return a " + getExpectedType().getName(),
123                        getExpectedType().isInstance(val));
124             assertEquals("Convert " + message[i] + " should return a " + date[0],
125                          now, getTimeInMillis(val));
126         }
127     }
128 
129     /**
130      * Test Default Type conversion (i.e. don't specify target type)
131      */
132     public void testDefaultType() {
133         final String pattern = "yyyy-MM-dd";
134 
135         // Create & Configure the Converter
136         final DateTimeConverter converter = makeConverter();
137         converter.setPattern(pattern);
138 
139         // Valid String --> Type Conversion
140         final String testString = "2006-10-29";
141         final Calendar calendar = toCalendar(testString, pattern, null);
142         final Object expected   = toType(calendar);
143 
144         final Object result = converter.convert(null, testString);
145         if (getExpectedType().equals(Calendar.class)) {
146             assertTrue("TYPE ", getExpectedType().isAssignableFrom(result.getClass()));
147         } else {
148             assertEquals("TYPE ", getExpectedType(), result.getClass());
149         }
150         assertEquals("VALUE ", expected, result);
151     }
152 
153     /**
154      * Test default String to type conversion
155      *
156      * N.B. This method is overridden by test case
157      * implementations for java.sql.Date/Time/Timestamp
158      */
159     public void testDefaultStringToTypeConvert() {
160 
161         // Create & Configure the Converter
162         final DateTimeConverter converter = makeConverter();
163         converter.setUseLocaleFormat(false);
164         try {
165             converter.convert(getExpectedType(), "2006-10-23");
166             fail("Expected Conversion exception");
167         } catch (final ConversionException e) {
168             // expected result
169         }
170 
171     }
172 
173     /**
174      * Test Conversion to String
175      */
176     public void testStringConversion() {
177 
178         final String pattern = "yyyy-MM-dd";
179 
180         // Create & Configure the Converter
181         final DateTimeConverter converter = makeConverter();
182         converter.setPattern(pattern);
183 
184         // Create Values
185         final String expected = "2006-10-29";
186         final Calendar calendar = toCalendar(expected, pattern, null);
187 
188         // Type --> String Conversion
189         stringConversion(converter, expected, toType(calendar));
190 
191         // Calendar --> String Conversion
192         stringConversion(converter, expected, calendar);
193 
194         // java.util.Date --> String Conversion
195         stringConversion(converter, expected, toDate(calendar));
196 
197         // java.sql.Date --> String Conversion
198         stringConversion(converter, expected, toSqlDate(calendar));
199 
200         // java.sql.Timestamp --> String Conversion
201         stringConversion(converter, expected, toSqlTimestamp(calendar));
202 
203         // java.sql.Time --> String Conversion
204         stringConversion(converter, expected, toSqlTime(calendar));
205 
206         stringConversion(converter, null, null);
207         stringConversion(converter, "", "");
208 
209     }
210 
211     /**
212      * Test Converter with no default value
213      */
214     public void testPatternNoDefault() {
215 
216         final String pattern = "yyyy-MM-dd";
217 
218         // Create & Configure the Converter
219         final DateTimeConverter converter = makeConverter();
220         converter.setPattern(pattern);
221 
222         // Valid String --> Type Conversion
223         final String testString = "2006-10-29";
224         final Calendar calendar = toCalendar(testString, pattern, null);
225         final Object expected   = toType(calendar);
226         validConversion(converter, expected, testString);
227 
228         // Valid java.util.Date --> Type Conversion
229         validConversion(converter, expected, calendar);
230 
231         // Valid Calendar --> Type Conversion
232         validConversion(converter, expected, toDate(calendar));
233 
234         // Test java.sql.Date --> Type Conversion
235         validConversion(converter, expected, toSqlDate(calendar));
236 
237         // java.sql.Timestamp --> String Conversion
238         validConversion(converter, expected, toSqlTimestamp(calendar));
239 
240         // java.sql.Time --> String Conversion
241         validConversion(converter, expected, toSqlTime(calendar));
242 
243         // Invalid Conversions
244         invalidConversion(converter, null);
245         invalidConversion(converter, "");
246         invalidConversion(converter, "2006-10-2X");
247         invalidConversion(converter, "2006/10/01");
248         invalidConversion(converter, "02/10/2006");
249         invalidConversion(converter, "02/10/06");
250         invalidConversion(converter, new Integer(2));
251 
252     }
253 
254     /**
255      * Test Converter with no default value
256      */
257     public void testPatternDefault() {
258 
259         final String pattern = "yyyy-MM-dd";
260 
261         // Create & Configure the Converter
262         final Object defaultValue = toType("2000-01-01", pattern, null);
263         assertNotNull("Check default date", defaultValue);
264         final DateTimeConverter converter = makeConverter(defaultValue);
265         converter.setPattern(pattern);
266 
267         // Valid String --> Type Conversion
268         final String testString = "2006-10-29";
269         final Object expected = toType(testString, pattern, null);
270         validConversion(converter, expected, testString);
271 
272         // Invalid Values, expect default value
273         validConversion(converter, defaultValue, null);
274         validConversion(converter, defaultValue, "");
275         validConversion(converter, defaultValue, "2006-10-2X");
276         validConversion(converter, defaultValue, "2006/10/01");
277         validConversion(converter, defaultValue, "02/10/06");
278         validConversion(converter, defaultValue, new Integer(2));
279 
280     }
281 
282     /**
283      * Test Converter with no default value
284      */
285     public void testPatternNullDefault() {
286 
287         final String pattern = "yyyy-MM-dd";
288 
289         // Create & Configure the Converter
290         final Object defaultValue = null;
291         final DateTimeConverter converter = makeConverter(defaultValue);
292         converter.setPattern(pattern);
293 
294         // Valid String --> Type Conversion
295         final String testString = "2006-10-29";
296         final Object expected = toType(testString, pattern, null);
297         validConversion(converter, expected, testString);
298 
299         // Invalid Values, expect default --> null
300         validConversion(converter, defaultValue, null);
301         validConversion(converter, defaultValue, "");
302         validConversion(converter, defaultValue, "2006-10-2X");
303         validConversion(converter, defaultValue, "2006/10/01");
304         validConversion(converter, defaultValue, "02/10/06");
305         validConversion(converter, defaultValue, new Integer(2));
306 
307     }
308 
309     /**
310      * Test Converter with multiple patterns
311      */
312     public void testMultiplePatterns() {
313         String testString = null;
314         Object expected = null;
315 
316         // Create & Configure the Converter
317         final String[] patterns = new String[] {"yyyy-MM-dd", "yyyy/MM/dd"};
318         final DateTimeConverter converter = makeConverter();
319         converter.setPatterns(patterns);
320 
321         // First Pattern
322         testString = "2006-10-28";
323         expected = toType(testString, patterns[0], null);
324         validConversion(converter, expected, testString);
325 
326         // Second pattern
327         testString = "2006/10/18";
328         expected = toType(testString, patterns[1], null);
329         validConversion(converter, expected, testString);
330 
331         // Invalid Conversion
332         invalidConversion(converter, "17/03/2006");
333         invalidConversion(converter, "17.03.2006");
334 
335     }
336 
337     /**
338      * Test Date Converter with no default value
339      */
340     public void testLocale() {
341 
342         // Re-set the default Locale to Locale.US
343         final Locale defaultLocale = Locale.getDefault();
344         Locale.setDefault(Locale.US);
345 
346         final String pattern = "M/d/yy"; // SHORT style date format for US Locale
347 
348         // Create & Configure the Converter
349         final DateTimeConverter converter = makeConverter();
350         converter.setUseLocaleFormat(true);
351 
352         // Valid String --> Type Conversion
353         final String testString = "10/28/06";
354         final Object expected = toType(testString, pattern, null);
355         validConversion(converter, expected, testString);
356 
357         // Invalid Conversions
358         invalidConversion(converter, null);
359         invalidConversion(converter, "");
360         invalidConversion(converter, "2006-10-2X");
361         invalidConversion(converter, "10.28.06");
362         invalidConversion(converter, "10-28-06");
363         invalidConversion(converter, new Integer(2));
364 
365         // Restore the default Locale
366         Locale.setDefault(defaultLocale);
367 
368     }
369 
370     /**
371      * Test Converter with types it can't handle
372      */
373     public void testInvalidType() {
374 
375         // Create & Configure the Converter
376         final DateTimeConverter converter = makeConverter();
377 
378         // Invalid Class Type
379         try {
380             converter.convert(Character.class, new Date());
381             fail("Requested Character.class conversion, expected ConversionException");
382         } catch (final ConversionException e) {
383             // Expected result
384         }
385     }
386 
387     /**
388      * Test Conversion to the required type
389      * @param converter The converter to use
390      * @param expected The expected result
391      * @param value The value to convert
392      */
393     void validConversion(final Converter converter, final Object expected, final Object value) {
394         final String valueType = (value == null ? "null" : value.getClass().getName());
395         final String msg = "Converting '" + valueType + "' value '" + value + "'";
396         try {
397             final Object result = converter.convert(getExpectedType(), value);
398             final Class<?> resultType = (result   == null ? null : result.getClass());
399             final Class<?> expectType = (expected == null ? null : expected.getClass());
400             assertEquals("TYPE "  + msg, expectType, resultType);
401             assertEquals("VALUE " + msg, expected, result);
402         } catch (final Exception ex) {
403             fail(msg + " threw " + ex.toString());
404         }
405     }
406 
407     /**
408      * Test Conversion to String
409      * @param converter The converter to use
410      * @param expected The expected result
411      * @param value The value to convert
412      */
413     void stringConversion(final Converter converter, final String expected, final Object value) {
414         final String valueType = (value == null ? "null" : value.getClass().getName());
415         final String msg = "Converting '" + valueType + "' value '" + value + "' to String";
416         try {
417             final Object result = converter.convert(String.class, value);
418             final Class<?> resultType = (result   == null ? null : result.getClass());
419             final Class<?> expectType = (expected == null ? null : expected.getClass());
420             assertEquals("TYPE "  + msg, expectType, resultType);
421             assertEquals("VALUE " + msg, expected, result);
422         } catch (final Exception ex) {
423             fail(msg + " threw " + ex.toString());
424         }
425     }
426 
427     /**
428      * Test Conversion Error
429      * @param converter The converter to use
430      * @param value The value to convert
431      */
432     void invalidConversion(final Converter converter, final Object value) {
433         final String valueType = (value == null ? "null" : value.getClass().getName());
434         final String msg = "Converting '" + valueType + "' value '" + value + "'";
435         try {
436             final Object result = converter.convert(getExpectedType(), value);
437             fail(msg + ", expected ConversionException, but result = '" + result + "'");
438         } catch (final ConversionException ex) {
439             // Expected Result
440         }
441     }
442 
443     /**
444      * Parse a String value to the required type
445      * @param value The String value to parse
446      * @param pattern The date pattern
447      * @param locale The locale to use (or null)
448      * @return parsed Calendar value
449      */
450     Object toType(final String value, final String pattern, final Locale locale) {
451         final Calendar calendar = toCalendar(value, pattern, locale);
452         return toType(calendar);
453     }
454 
455     /**
456      * Parse a String value to a Calendar
457      * @param value The String value to parse
458      * @param pattern The date pattern
459      * @param locale The locale to use (or null)
460      * @return parsed Calendar value
461      */
462     Calendar toCalendar(final String value, final String pattern, final Locale locale) {
463         Calendar calendar = null;
464         try {
465             final DateFormat format = (locale == null)
466                            ? new SimpleDateFormat(pattern)
467                            : new SimpleDateFormat(pattern, locale);
468             format.setLenient(false);
469             format.parse(value);
470             calendar = format.getCalendar();
471         } catch (final Exception e) {
472             fail("Error creating Calendar value ='"
473                     + value + ", pattern='" + pattern + "' " + e.toString());
474         }
475         return calendar;
476     }
477 
478     /**
479      * Convert a Calendar to a java.util.Date
480      * @param calendar The calendar object to convert
481      * @return The converted java.util.Date
482      */
483     Date toDate(final Calendar calendar) {
484         return calendar.getTime();
485     }
486 
487     /**
488      * Convert a Calendar to a java.sql.Date
489      * @param calendar The calendar object to convert
490      * @return The converted java.sql.Date
491      */
492     java.sql.Date toSqlDate(final Calendar calendar) {
493         return new java.sql.Date(getTimeInMillis(calendar));
494     }
495 
496     /**
497      * Convert a Calendar to a java.sql.Time
498      * @param calendar The calendar object to convert
499      * @return The converted java.sql.Time
500      */
501     java.sql.Time toSqlTime(final Calendar calendar) {
502         return new java.sql.Time(getTimeInMillis(calendar));
503     }
504 
505     /**
506      * Convert a Calendar to a java.sql.Timestamp
507      * @param calendar The calendar object to convert
508      * @return The converted java.sql.Timestamp
509      */
510     java.sql.Timestamp toSqlTimestamp(final Calendar calendar) {
511         return new java.sql.Timestamp(getTimeInMillis(calendar));
512     }
513 
514     /**
515      * Convert a Date or Calendar objects to the time in millisconds
516      * @param date The date or calendar object
517      * @return The time in milliseconds
518      */
519     long getTimeInMillis(final Object date) {
520 
521         if (date instanceof java.sql.Timestamp) {
522             // ---------------------- JDK 1.3 Fix ----------------------
523             // N.B. Prior to JDK 1.4 the Timestamp's getTime() method
524             //      didn't include the milliseconds. The following code
525             //      ensures it works consistently accross JDK versions
526             final java.sql.Timestamp timestamp = (java.sql.Timestamp)date;
527             long timeInMillis = ((timestamp.getTime() / 1000) * 1000);
528             timeInMillis += timestamp.getNanos() / 1000000;
529             return timeInMillis;
530         }
531 
532         if (date instanceof Calendar) {
533             return ((Calendar)date).getTime().getTime();
534         } else {
535             return ((Date)date).getTime();
536         }
537     }
538 }