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