View Javadoc

1   package org.apache.velocity.tools;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.lang.reflect.Array;
24  import java.net.URL;
25  import java.text.DateFormat;
26  import java.text.DecimalFormat;
27  import java.text.DecimalFormatSymbols;
28  import java.text.NumberFormat;
29  import java.text.SimpleDateFormat;
30  import java.util.Collection;
31  import java.util.Date;
32  import java.util.Calendar;
33  import java.util.Locale;
34  import java.util.TimeZone;
35  
36  /**
37   * Utility methods for parsing or otherwise converting between types.
38   * Current supported types are Number, Date, Calendar, 
39   * String, Boolean, Locale and URL
40   *
41   * @author Nathan Bubna
42   */
43  public class ConversionUtils
44  {
45      public static final ConversionUtils INSTANCE = new ConversionUtils();
46  
47      private static final int STYLE_NUMBER       = 0;
48      private static final int STYLE_CURRENCY     = 1;
49      private static final int STYLE_PERCENT      = 2;
50      //NOTE: '3' belongs to a non-public "scientific" style
51      private static final int STYLE_INTEGER      = 4;
52  
53      private ConversionUtils() {}
54  
55      public ConversionUtils getInstance()
56      {
57          return INSTANCE;
58      }
59  
60  
61      /**
62       * Returns a {@link NumberFormat} instance for the specified
63       * format and {@link Locale}.  If the format specified is a standard
64       * style pattern, then a number instance
65       * will be returned with the number style set to the
66       * specified style.  If it is a custom format, then a customized
67       * {@link NumberFormat} will be returned.
68       *
69       * @param format the custom or standard formatting pattern to be used
70       * @param locale the {@link Locale} to be used
71       * @return an instance of {@link NumberFormat}
72       * @see NumberFormat
73       */
74      public static NumberFormat getNumberFormat(String format, Locale locale)
75      {
76          if (format == null || locale == null)
77          {
78              return null;
79          }
80  
81          NumberFormat nf = null;
82          int style = getNumberStyleAsInt(format);
83          if (style < 0)
84          {
85              // we have a custom format
86              nf = new DecimalFormat(format, new DecimalFormatSymbols(locale));
87          }
88          else
89          {
90              // we have a standard format
91              nf = getNumberFormat(style, locale);
92          }
93          return nf;
94      }
95  
96      /**
97       * Returns a {@link NumberFormat} instance for the specified
98       * number style and {@link Locale}.
99       *
100      * @param numberStyle the number style (number will be ignored if this is
101      *        less than zero or the number style is not recognized)
102      * @param locale the {@link Locale} to be used
103      * @return an instance of {@link NumberFormat} or <code>null</code>
104      *         if an instance cannot be constructed with the given
105      *         parameters
106      */
107     public static NumberFormat getNumberFormat(int numberStyle, Locale locale)
108     {
109         try
110         {
111             NumberFormat nf;
112             switch (numberStyle)
113             {
114                 case STYLE_NUMBER:
115                     nf = NumberFormat.getNumberInstance(locale);
116                     break;
117                 case STYLE_CURRENCY:
118                     nf = NumberFormat.getCurrencyInstance(locale);
119                     break;
120                 case STYLE_PERCENT:
121                     nf = NumberFormat.getPercentInstance(locale);
122                     break;
123                 case STYLE_INTEGER:
124                     nf = NumberFormat.getIntegerInstance(locale);
125                     break;
126                 default:
127                     // invalid style was specified, return null
128                     nf = null;
129             }
130             return nf;
131         }
132         catch (Exception suppressed)
133         {
134             // let it go...
135             return null;
136         }
137     }
138 
139     /**
140      * Checks a string to see if it matches one of the standard
141      * NumberFormat style patterns:
142      *      number, currency, percent, integer, or default.
143      * if it does it will return the integer constant for that pattern.
144      * if not, it will return -1.
145      *
146      * @see NumberFormat
147      * @param style the string to be checked
148      * @return the int identifying the style pattern
149      */
150     public static int getNumberStyleAsInt(String style)
151     {
152         // avoid needlessly running through all the string comparisons
153         if (style == null || style.length() < 6 || style.length() > 8) {
154             return -1;
155         }
156         if (style.equalsIgnoreCase("default"))
157         {
158             //NOTE: java.text.NumberFormat returns "number" instances
159             //      as the default (at least in Java 1.3 and 1.4).
160             return STYLE_NUMBER;
161         }
162         if (style.equalsIgnoreCase("number"))
163         {
164             return STYLE_NUMBER;
165         }
166         if (style.equalsIgnoreCase("currency"))
167         {
168             return STYLE_CURRENCY;
169         }
170         if (style.equalsIgnoreCase("percent"))
171         {
172             return STYLE_PERCENT;
173         }
174         if (style.equalsIgnoreCase("integer"))
175         {
176             return STYLE_INTEGER;
177         }
178         // ok, it's not any of the standard patterns
179         return -1;
180     }
181 
182 
183     // ----------------- number conversion methods ---------------
184 
185     /**
186      * Attempts to convert an unidentified {@link Object} into a {@link Number},
187      * just short of turning it into a string and parsing it.  In other words,
188      * this will convert to {@link Number} from a {@link Number}, {@link Calendar},
189      * or {@link Date}.  If it can't do that, it will get the string value and have 
190      * {@link #toNumber(String,String,Locale)} try to parse it using the
191      * default Locale and format.
192      
193      * @param obj - the object to convert
194      */
195     public static Number toNumber(Object obj)
196     {
197         return toNumber(obj, true);
198     }
199 
200     /**
201      * Just like {@link #toNumber(Object)} except that you can tell
202      * this to attempt parsing the object as a String by passing {@code true}
203      * as the second parameter.  If you do so, then it will have
204      * {@link #toNumber(String,String,Locale)} try to parse it using the
205      * default Locale and format.
206      */
207     public static Number toNumber(Object obj, boolean handleStrings)
208     {
209         if (obj == null)
210         {
211             return null;
212         }
213         if (obj instanceof Number)
214         {
215             return (Number)obj;
216         }
217         if (obj instanceof Date)
218         {
219             return Long.valueOf(((Date)obj).getTime());
220         }
221         if (obj instanceof Calendar)
222         {
223             Date date = ((Calendar)obj).getTime();
224             return Long.valueOf(date.getTime());
225         }
226         if (handleStrings)
227         {
228             // try parsing with default format and locale
229             return toNumber(obj.toString(), "default", Locale.getDefault());
230         }
231         return null;
232     }
233 
234     /**
235      * Converts a string to an instance of {@link Number} using the
236      * specified format and {@link Locale} to parse it.
237      *
238      * @param value - the string to convert
239      * @param format - the format the number is in
240      * @param locale - the {@link Locale}
241      * @return the string as a {@link Number} or <code>null</code> if no
242      *         conversion is possible
243      * @see NumberFormat#parse
244      */
245     public static Number toNumber(String value, String format, Locale locale)
246     {
247         if (value == null || format == null || locale == null)
248         {
249             return null;
250         }
251         try
252         {
253             NumberFormat parser = getNumberFormat(format, locale);
254             return parser.parse(value);
255         }
256         catch (Exception e)
257         {
258             return null;
259         }
260     }
261 
262     /**
263      * Converts an object to an instance of {@link Number} using the
264      * specified format and {@link Locale} to parse it, if necessary.
265      *
266      * @param value - the object to convert
267      * @param format - the format the number is in
268      * @param locale - the {@link Locale}
269      * @return the object as a {@link Number} or <code>null</code> if no
270      *         conversion is possible
271      * @see NumberFormat#parse
272      */
273     public static Number toNumber(Object value, String format, Locale locale)
274     {
275         // first try the easy stuff
276         Number number = toNumber(value, false);
277         if (number != null)
278         {
279             return number;
280         }
281 
282         // turn it into a string and try parsing it
283         return toNumber(String.valueOf(value), format, locale);
284     }
285 
286 
287     // -------------------------- DateFormat creation methods --------------
288 
289     /**
290      * Returns a {@link DateFormat} instance for the specified
291      * format, {@link Locale}, and {@link TimeZone}.  If the format
292      * specified is a standard style pattern, then a date-time instance
293      * will be returned with both the date and time styles set to the
294      * specified style.  If it is a custom format, then a customized
295      * {@link SimpleDateFormat} will be returned.
296      *
297      * @param format the custom or standard formatting pattern to be used
298      * @param locale the {@link Locale} to be used
299      * @param timezone the {@link TimeZone} to be used
300      * @return an instance of {@link DateFormat}
301      * @see SimpleDateFormat
302      * @see DateFormat
303      */
304     public static DateFormat getDateFormat(String format, Locale locale,
305                                            TimeZone timezone)
306     {
307         if (format == null)
308         {
309             return null;
310         }
311 
312         DateFormat df = null;
313         // do they want a date instance
314         if (format.endsWith("_date"))
315         {
316             String fmt = format.substring(0, format.length() - 5);
317             int style = getDateStyleAsInt(fmt);
318             df = getDateFormat(style, -1, locale, timezone);
319         }
320         // do they want a time instance?
321         else if (format.endsWith("_time"))
322         {
323             String fmt = format.substring(0, format.length() - 5);
324             int style = getDateStyleAsInt(fmt);
325             df = getDateFormat(-1, style, locale, timezone);
326         }
327         // ok, they either want a custom or date-time instance
328         else
329         {
330             int style = getDateStyleAsInt(format);
331             if (style < 0)
332             {
333                 // we have a custom format
334                 df = new SimpleDateFormat(format, locale);
335                 df.setTimeZone(timezone);
336             }
337             else
338             {
339                 // they want a date-time instance
340                 df = getDateFormat(style, style, locale, timezone);
341             }
342         }
343         return df;
344     }
345 
346     /**
347      * Returns a {@link DateFormat} instance for the specified
348      * date style, time style, {@link Locale}, and {@link TimeZone}.
349      *
350      * @param dateStyle the date style
351      * @param timeStyle the time style
352      * @param locale the {@link Locale} to be used
353      * @param timezone the {@link TimeZone} to be used
354      * @return an instance of {@link DateFormat}
355      * @see #getDateFormat(int timeStyle, int dateStyle, Locale locale, TimeZone timezone)
356      */
357     public static DateFormat getDateFormat(String dateStyle, String timeStyle,
358                                            Locale locale, TimeZone timezone)
359     {
360         int ds = getDateStyleAsInt(dateStyle);
361         int ts = getDateStyleAsInt(timeStyle);
362         return getDateFormat(ds, ts, locale, timezone);
363     }
364 
365     /**
366      * Returns a {@link DateFormat} instance for the specified
367      * time style, date style, {@link Locale}, and {@link TimeZone}.
368      *
369      * @param dateStyle the date style (date will be ignored if this is
370      *        less than zero and the date style is not)
371      * @param timeStyle the time style (time will be ignored if this is
372      *        less than zero and the date style is not)
373      * @param locale the {@link Locale} to be used
374      * @param timezone the {@link TimeZone} to be used
375      * @return an instance of {@link DateFormat} or <code>null</code>
376      *         if an instance cannot be constructed with the given
377      *         parameters
378      */
379     public static DateFormat getDateFormat(int dateStyle, int timeStyle,
380                                            Locale locale, TimeZone timezone)
381     {
382         try
383         {
384             DateFormat df;
385             if (dateStyle < 0 && timeStyle < 0)
386             {
387                 // no style was specified, use default instance
388                 df = DateFormat.getInstance();
389             }
390             else if (timeStyle < 0)
391             {
392                 // only a date style was specified
393                 df = DateFormat.getDateInstance(dateStyle, locale);
394             }
395             else if (dateStyle < 0)
396             {
397                 // only a time style was specified
398                 df = DateFormat.getTimeInstance(timeStyle, locale);
399             }
400             else
401             {
402                 df = DateFormat.getDateTimeInstance(dateStyle, timeStyle,
403                                                     locale);
404             }
405             df.setTimeZone(timezone);
406             return df;
407         }
408         catch (Exception suppressed)
409         {
410             // let it go...
411             return null;
412         }
413     }
414 
415     /**
416      * Checks a string to see if it matches one of the standard DateFormat
417      * style patterns: full, long, medium, short, or default.  If it does,
418      * it will return the integer constant for that pattern.  If not, it
419      * will return -1.
420      *
421      * @see DateFormat
422      * @param style the string to be checked
423      * @return the int identifying the style pattern
424      */
425     public static int getDateStyleAsInt(String style)
426     {
427         // avoid needlessly running through all the string comparisons
428         if (style == null || style.length() < 4 || style.length() > 7) {
429             return -1;
430         }
431         if (style.equalsIgnoreCase("full"))
432         {
433             return DateFormat.FULL;
434         }
435         if (style.equalsIgnoreCase("long"))
436         {
437             return DateFormat.LONG;
438         }
439         if (style.equalsIgnoreCase("medium"))
440         {
441             return DateFormat.MEDIUM;
442         }
443         if (style.equalsIgnoreCase("short"))
444         {
445             return DateFormat.SHORT;
446         }
447         if (style.equalsIgnoreCase("default"))
448         {
449             return DateFormat.DEFAULT;
450         }
451         // ok, it's not any of the standard patterns
452         return -1;
453     }
454 
455 
456     // ----------------- date conversion methods ---------------
457 
458     /**
459      * Attempts to convert an unidentified {@link Object} into a {@link Date},
460      * just short of turning it into a string and parsing it.  In other words,
461      * this will convert to {@link Date} from a {@link Date}, {@link Calendar},
462      * or {@link Number}.  If it can't do that, it will return {@code null}.
463      *
464      * @param obj - the object to convert
465      */
466     public static Date toDate(Object obj)
467     {
468         if (obj == null)
469         {
470             return null;
471         }
472         if (obj instanceof Date)
473         {
474             return (Date)obj;
475         }
476         if (obj instanceof Calendar)
477         {
478             return ((Calendar)obj).getTime();
479         }
480         if (obj instanceof Number)
481         {
482             Date d = new Date();
483             d.setTime(((Number)obj).longValue());
484             return d;
485         }
486         return null;
487     }
488 
489     /**
490      * Converts an object to an instance of {@link Date} using the
491      * specified format, {@link Locale}, and {@link TimeZone} if the
492      * object is not already an instance of Date, Calendar, or Long.
493      *
494      * @param obj - the date to convert
495      * @param format - the format the date is in
496      * @param locale - the {@link Locale}
497      * @param timezone - the {@link TimeZone}
498      * @return the object as a {@link Date} or <code>null</code> if no
499      *         conversion is possible
500      * @see #getDateFormat
501      * @see SimpleDateFormat#parse
502      */
503     public static Date toDate(Object obj, String format,
504                               Locale locale, TimeZone timezone)
505     {
506         // first try the easy stuff
507         Date date = toDate(obj);
508         if (date != null)
509         {
510             return date;
511         }
512 
513         // turn it into a string and try parsing it
514         return toDate(String.valueOf(obj), format, locale, timezone);
515     }
516 
517     /**
518      * Converts an object to an instance of {@link Date} using the
519      * specified format, {@link Locale}, and {@link TimeZone} if the
520      * object is not already an instance of Date, Calendar, or Long.
521      *
522      * @param str - the string to parse
523      * @param format - the format the date is in
524      * @param locale - the {@link Locale}
525      * @param timezone - the {@link TimeZone}
526      * @return the string as a {@link Date} or <code>null</code> if the
527      *         parsing fails
528      * @see #getDateFormat
529      * @see SimpleDateFormat#parse
530      */
531     public static Date toDate(String str, String format,
532                               Locale locale, TimeZone timezone)
533     {
534         try
535         {
536             //try parsing w/a customized SimpleDateFormat
537             DateFormat parser = getDateFormat(format, locale, timezone);
538             return parser.parse(str);
539         }
540         catch (Exception e)
541         {
542             return null;
543         }
544     }
545 
546     public static Calendar toCalendar(Date date, Locale locale)
547     {
548         if (date == null)
549         {
550             return null;
551         }
552 
553         Calendar cal;
554         if (locale == null)
555         {
556             cal = Calendar.getInstance();
557         }
558         else
559         {
560             cal = Calendar.getInstance(locale);
561         }
562         cal.setTime(date);
563         // HACK: Force all fields to update. see link for explanation of this.
564         //http://java.sun.com/j2se/1.4/docs/api/java/util/Calendar.html
565         cal.getTime();
566         return cal;
567     }
568 
569 
570     // ----------------- misc conversion methods ---------------
571 
572     /**
573      * Converts objects to String in a more Tools-ish way than
574      * String.valueOf(Object), especially with nulls, Arrays and Collections.
575      * Null returns null, Arrays and Collections return their first value,
576      * or null if they have no values.
577      * 
578      * @param value the object to be turned into a String
579      * @return the string value of the object or null if the value is null
580      *         or it is an array whose first value is null
581      */
582     public static String toString(Object value)
583     {
584         if (value instanceof String)
585         {
586             return (String)value;
587         }
588         if (value == null)
589         {
590             return null;
591         }
592         if (value.getClass().isArray())
593         {
594             if (Array.getLength(value) > 0)
595             {
596                 // recurse on the first value
597                 return toString(Array.get(value, 0));
598             }
599             return null;
600         }
601         return String.valueOf(value);
602     }
603 
604     /**
605      * Returns the first value as a String, if any; otherwise returns null.
606      *
607      * @param values the Collection to be turned into a string
608      * @return the string value of the first object in the collection
609      *         or null if the collection is empty
610      */
611     public static String toString(Collection values)
612     {
613         if (values != null && !values.isEmpty())
614         {
615             // recurse on the first value
616             return toString(values.iterator().next());
617         }
618         return null;
619     }
620 
621     /**
622      * Converts any Object to a boolean using {@link #toString(Object)}
623      * and {@link Boolean#valueOf(String)}. 
624      *
625      * @param value the object to be converted
626      * @return a {@link Boolean} object for the specified value or
627      *         <code>null</code> if the value is null or the conversion failed
628      */
629     public static Boolean toBoolean(Object value)
630     {
631         if (value instanceof Boolean)
632         {
633             return (Boolean)value;
634         }
635 
636         String s = toString(value);
637         return (s != null) ? Boolean.valueOf(s) : null;
638     }
639 
640     /**
641      * Converts a string to a {@link Locale}
642      *
643      * @param value - the string to parse
644      * @return the {@link Locale} or <code>null</code> if the
645      *         parsing fails
646      */
647     public static Locale toLocale(String value)
648     {
649         if (value.indexOf('_') < 0)
650         {
651             return new Locale(value);
652         }
653 
654         String[] params = value.split("_");
655         if (params.length == 2)
656         {
657             return new Locale(params[0], params[1]);
658         }
659         else if (params.length == 3)
660         {
661             return new Locale(params[0], params[1], params[2]);
662         }
663         else
664         {
665             // there's only 3 possible params, so this must be invalid
666             return null;
667         }
668     }
669 
670     /**
671      * Converts a string to a {@link URL}.  It will first try to
672      * treat the string as a File name, then a classpath resource,
673      * then finally as a literal URL.  If none of these work, then
674      * this will return {@code null}.
675      *
676      * @param value - the string to parse
677      * @return the {@link URL} form of the string or {@code null}
678      * @see File
679      * @see ClassUtils#getResource(String,Object)
680      * @see URL
681      */
682     public static URL toURL(String value)
683     {
684         return toURL(value, ConversionUtils.class);
685     }
686 
687     /**
688      * Converts a string to a {@link URL}.  It will first try to
689      * treat the string as a File name, then a classpath resource,
690      * then finally as a literal URL.  If none of these work, then
691      * this will return {@code null}.
692      *
693      * @param value - the string to parse
694      * @param caller - the object or Class seeking the url
695      * @return the {@link URL} form of the string or {@code null}
696      * @see File
697      * @see ClassUtils#getResource(String,Object)
698      * @see URL
699      */
700     public static URL toURL(String value, Object caller)
701     {
702         try
703         {
704             File file = new File(value);
705             if (file.exists())
706             {
707                 return file.toURI().toURL();
708             }
709         }
710         catch (Exception e) {}
711         try
712         {
713             URL url = ClassUtils.getResource(value, caller);
714             if (url != null)
715             {
716                 return url;
717             }
718         }
719         catch (Exception e) {}
720         try
721         {
722             return new URL(value);
723         }
724         catch (Exception e) {}
725         return null;
726     }
727 
728 }