View Javadoc

1   package org.apache.velocity.tools.generic;
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.lang.reflect.Array;
23  import java.util.Collection;
24  import java.util.Date;
25  import java.util.Calendar;
26  import java.util.Iterator;
27  import java.util.Locale;
28  import java.util.TimeZone;
29  import org.apache.velocity.tools.ConversionUtils;
30  import org.apache.velocity.tools.config.DefaultKey;
31  import org.apache.velocity.tools.config.SkipSetters;
32  
33  /**
34   * <p>Utility class for easy conversion of String values to richer types.</p>
35   * <p><pre>
36   * Template example(s):
37   *   $convert.toNumber('12.6')   ->  12.6
38   *   $convert.toInt('12.6')      ->  12
39   *   $convert.toNumbers('12.6,42')  ->  [12.6, 42]
40   *
41   * Toolbox configuration:
42   * &lt;tools&gt;
43   *   &lt;toolbox scope="application"&gt;
44   *     &lt;tool class="org.apache.velocity.tools.generic.ConversionTool"
45   *              dateFormat="yyyy-MM-dd"/&gt;
46   *   &lt;/toolbox&gt;
47   * &lt;/tools&gt;
48   * </pre></p>
49   *
50   * <p>This comes in very handy when parsing anything.</p>
51   *
52   * @author Nathan Bubna
53   * @version $Revision: 932578 $ $Date: 2007-02-26 11:24:39 -0800 (Mon, 26 Feb 2007) $
54   * @since VelocityTools 2.0
55   */
56  @DefaultKey("convert")
57  @SkipSetters
58  public class ConversionTool extends LocaleConfig
59  {
60      public static final String STRINGS_DELIMITER_FORMAT_KEY = "stringsDelimiter";
61      public static final String STRINGS_TRIM_KEY = "trimStrings";
62      public static final String DATE_FORMAT_KEY = "dateFormat";
63      public static final String NUMBER_FORMAT_KEY = "numberFormat";
64  
65      public static final String DEFAULT_STRINGS_DELIMITER = ",";
66      public static final boolean DEFAULT_STRINGS_TRIM = true;
67      public static final String DEFAULT_NUMBER_FORMAT = "default";
68      public static final String DEFAULT_DATE_FORMAT = "default";
69  
70      private String stringsDelimiter = DEFAULT_STRINGS_DELIMITER;
71      private boolean stringsTrim = DEFAULT_STRINGS_TRIM;
72      private String numberFormat = DEFAULT_NUMBER_FORMAT;
73      private String dateFormat = DEFAULT_DATE_FORMAT;
74  
75      /**
76       * Does the actual configuration. This is protected, so
77       * subclasses may share the same ValueParser and call configure
78       * at any time, while preventing templates from doing so when 
79       * configure(Map) is locked.
80       */
81      protected void configure(ValueParser values)
82      {
83          super.configure(values);
84  
85          String delimiter = values.getString(STRINGS_DELIMITER_FORMAT_KEY);
86          if (delimiter != null)
87          {
88              setStringsDelimiter(delimiter);
89          }
90  
91          String dateFormat = values.getString(DATE_FORMAT_KEY);
92          if (dateFormat != null)
93          {
94              setDateFormat(dateFormat);
95          }
96  
97          String numberFormat = values.getString(NUMBER_FORMAT_KEY);
98          if (numberFormat != null)
99          {
100             setNumberFormat(numberFormat);
101         }
102     }
103 
104     /**
105      * Sets the delimiter used for separating values in a single String value.
106      * The default string delimiter is a comma.
107      *
108      * @see #parseStringList
109      */
110     protected final void setStringsDelimiter(String stringsDelimiter)
111     {
112         this.stringsDelimiter = stringsDelimiter;
113     }
114 
115     public final String getStringsDelimiter()
116     {
117         return this.stringsDelimiter;
118     }
119 
120     /**
121      * Sets whether strings should be trimmed when separated from
122      * a delimited string value.
123      * The default is true.
124      *
125      * @see #parseStringList
126      */
127     protected final void setStringsTrim(boolean stringsTrim)
128     {
129         this.stringsTrim = stringsTrim;
130     }
131 
132     public final boolean getStringsTrim()
133     {
134         return this.stringsTrim;
135     }
136 
137     protected final void setNumberFormat(String format)
138     {
139         this.numberFormat = format;
140     }
141 
142     public final String getNumberFormat()
143     {
144         return this.numberFormat;
145     }
146 
147     protected final void setDateFormat(String format)
148     {
149         this.dateFormat = format;
150     }
151 
152     public final String getDateFormat()
153     {
154         return this.dateFormat;
155     }
156 
157     // ----------------- public parsing methods --------------------------
158 
159     /**
160      * Converts objects to String in a more Tools-ish way than
161      * String.valueOf(Object), especially with nulls, Arrays and Collections.
162      * Null returns null, Arrays and Collections return the toString(Object)
163      * of their first value, or null if they have no values.
164      *
165      * @param value the object to be turned into a String
166      * @return the string value of the object or null if the value is null
167      *         or it is an array or collection whose first value is null
168      */
169     public String toString(Object value)
170     {
171         return ConversionUtils.toString(value);
172     }
173 
174     /**
175      * @param value the object to be converted
176      * @return a {@link Boolean} object for the specified value or
177      *         <code>null</code> if the value is null or the conversion failed
178      */
179     public Boolean toBoolean(Object value)
180     {
181         if (value instanceof Boolean)
182         {
183             return (Boolean)value;
184         }
185 
186         String s = toString(value);
187         return (s != null) ? parseBoolean(s) : null;
188     }
189 
190     /**
191      * @param value the object to be converted
192      * @return a {@link Integer} for the specified value or
193      *         <code>null</code> if the value is null or the conversion failed
194      */
195     public Integer toInteger(Object value)
196     {
197         if (value == null || value instanceof Integer)
198         {
199             return (Integer)value;
200         }
201         Number num = toNumber(value);
202         return Integer.valueOf(num.intValue());
203     }
204 
205     /**
206      * @param value the object to be converted
207      * @return a {@link Double} for the specified value or
208      *         <code>null</code> if the value is null or the conversion failed
209      */
210     public Double toDouble(Object value)
211     {
212         if (value == null || value instanceof Double)
213         {
214             return (Double)value;
215         }
216         Number num = toNumber(value);
217         return new Double(num.doubleValue());
218     }
219 
220     /**
221      * @param value the object to be converted
222      * @return a {@link Number} for the specified value or
223      *         <code>null</code> if the value is null or the conversion failed
224      */
225     public Number toNumber(Object value)
226     {
227         // don't do string conversion yet
228         Number number = ConversionUtils.toNumber(value, false);
229         if (number != null)
230         {
231             return number;
232         }
233 
234         String s = toString(value);
235         if (s == null || s.length() == 0)
236         {
237             return null;
238         }
239         return parseNumber(s);
240     }
241 
242     /**
243      * @param value the object to be converted
244      * @return a {@link Locale} for the specified value or
245      *         <code>null</code> if the value is null or the conversion failed
246      */
247     public Locale toLocale(Object value)
248     {
249         if (value instanceof Locale)
250         {
251             return (Locale)value;
252         }
253         String s = toString(value);
254         if (s == null || s.length() == 0)
255         {
256             return null;
257         }
258         return parseLocale(s);
259     }
260 
261     /**
262      * Converts an object to an instance of {@link Date}, when necessary
263      * using the configured date parsing format, the configured default
264      * {@link Locale}, and the system's default {@link TimeZone} to parse
265      * the string value of the specified object.
266      *
267      * @param value the date to convert
268      * @return the object as a {@link Date} or <code>null</code> if no
269      *         conversion is possible
270      */
271     public Date toDate(Object value)
272     {
273         Date d = ConversionUtils.toDate(value);
274         if (d != null)
275         {
276             return d;
277         }
278         String s = toString(value);
279         if (s == null || s.length() == 0)
280         {
281             return null;
282         }
283         return parseDate(s);
284     }
285 
286     public Calendar toCalendar(Object value)
287     {
288         if (value == null)
289         {
290             return null;
291         }
292         if (value instanceof Calendar)
293         {
294             return (Calendar)value;
295         }
296 
297         Date date = toDate(value);
298         if (date == null)
299         {
300             return null;
301         }
302 
303         //convert the date to a calendar
304         return ConversionUtils.toCalendar(date, getLocale());
305     }
306 
307 
308     /**
309      * @param value the value to be converted
310      * @return an array of String objects containing all of the values
311      *         derived from the specified array, Collection, or delimited String
312      */
313     public String[] toStrings(Object value)
314     {
315         if (value == null)
316         {
317             return null;
318         }
319         if (value instanceof String[])
320         {
321             return (String[])value;
322         }
323 
324         String[] strings = null;
325         if (value instanceof Collection)
326         {
327             Collection values = (Collection)value;
328             if (!values.isEmpty())
329             {
330                 strings = new String[values.size()];
331                 int index = 0;
332                 for (Iterator i = values.iterator(); i.hasNext(); )
333                 {
334                     strings[index++] = toString(i.next());
335                 }
336             }
337         }
338         else if (value.getClass().isArray())
339         {
340             strings = new String[Array.getLength(value)];
341             for (int i=0; i < strings.length; i++)
342             {
343                 strings[i] = toString(Array.get(value, i));
344             }
345         }
346         else
347         {
348             strings = parseStringList(toString(value));
349         }
350         return strings;
351     }
352 
353 
354 
355     /**
356      * @param value the value to be converted
357      * @return an array of Boolean objects derived from the specified value,
358      *         or <code>null</code>.
359      */
360     public Boolean[] toBooleans(Object value)
361     {
362         if (value != null && !value.getClass().isArray())
363         {
364             value = toStrings(value);
365         }
366         if (value == null)
367         {
368             return null;
369         }
370 
371         Boolean[] bools = new Boolean[Array.getLength(value)];
372         for (int i=0; i < bools.length; i++)
373         {
374             bools[i] = toBoolean(Array.get(value, i));
375         }
376         return bools;
377     }
378 
379     /**
380      * @param values the collection of values to be converted
381      * @return an array of Boolean objects derived from the specified values,
382      *         or <code>null</code>.
383      */
384     public Boolean[] toBooleans(Collection values)
385     {
386         if (values == null || !values.isEmpty())
387         {
388             return null;
389         }
390         Boolean[] bools = new Boolean[values.size()];
391         int index = 0;
392         for (Object val : values)
393         {
394             bools[index++] = toBoolean(val);
395         }
396         return bools;
397     }
398 
399 
400     /**
401      * @param value the value to be converted
402      * @return an array of Number objects derived from the specified value,
403      *         or <code>null</code>.
404      */
405     public Number[] toNumbers(Object value)
406     {
407         if (value != null && !value.getClass().isArray())
408         {
409             value = toStrings(value);
410         }
411         if (value == null)
412         {
413             return null;
414         }
415 
416         Number[] numbers = new Number[Array.getLength(value)];
417         for (int i=0; i < numbers.length; i++)
418         {
419             numbers[i] = toNumber(Array.get(value, i));
420         }
421         return numbers;
422     }
423 
424     /**
425      * @param values the collection of values to be converted
426      * @return an array of Number objects derived from the specified values,
427      *         or <code>null</code>.
428      */
429     public Number[] toNumbers(Collection values)
430     {
431         if (values == null || !values.isEmpty())
432         {
433             return null;
434         }
435         Number[] numbers = new Number[values.size()];
436         int index = 0;
437         for (Object val : values)
438         {
439             numbers[index++] = toNumber(val);
440         }
441         return numbers;
442     }
443 
444     /**
445      * @param value the value to be converted
446      * @return an array of int values derived from the specified value,
447      *         or <code>null</code>.
448      */
449     public int[] toInts(Object value)
450     {
451         Number[] numbers = toNumbers(value);
452         if (numbers == null)
453         {
454             return null;
455         }
456 
457         int[] ints = new int[numbers.length];
458         for (int i=0; i<ints.length; i++)
459         {
460             if (numbers[i] != null)
461             {
462                 ints[i] = numbers[i].intValue();
463             }
464         }
465         return ints;
466     }
467 
468     /**
469      * @param value the value to be converted
470      * @return an array of int values derived from the specified value,
471      *         or <code>null</code>.
472      */
473     public int[] toIntegers(Object value)
474     {
475       return toInts(value);
476     }
477 
478     /**
479      * @param value the value to be converted
480      * @return an array of double values derived from the specified value,
481      *         or <code>null</code>.
482      */
483     public double[] toDoubles(Object value)
484     {
485         Number[] numbers = toNumbers(value);
486         if (numbers == null)
487         {
488             return null;
489         }
490 
491         double[] doubles = new double[numbers.length];
492         for (int i=0; i<doubles.length; i++)
493         {
494             if (numbers[i] != null)
495             {
496                 doubles[i] = numbers[i].doubleValue();
497             }
498         }
499         return doubles;
500     }
501 
502     /**
503      * @param value the value to be converted
504      * @return an array of Locale objects derived from the specified value,
505      *         or <code>null</code>.
506      */
507     public Locale[] toLocales(Object value)
508     {
509         if (value != null && !value.getClass().isArray())
510         {
511             value = toStrings(value);
512         }
513         if (value == null)
514         {
515             return null;
516         }
517 
518         Locale[] locales = new Locale[Array.getLength(value)];
519         for (int i=0; i < locales.length; i++)
520         {
521             locales[i] = toLocale(Array.get(value, i));
522         }
523         return locales;
524     }
525 
526     /**
527      * @param values the collection of values to be converted
528      * @return an array of Locale objects derived from the specified values,
529      *         or <code>null</code>.
530      */
531     public Locale[] toLocales(Collection values)
532     {
533         if (values == null || !values.isEmpty())
534         {
535             return null;
536         }
537         Locale[] locales = new Locale[values.size()];
538         int index = 0;
539         for (Object val : values)
540         {
541             locales[index++] = toLocale(val);
542         }
543         return locales;
544     }
545 
546 
547     /**
548      * @param value the value to be converted
549      * @return an array of Date objects derived from the specified value,
550      *         or <code>null</code>.
551      */
552     public Date[] toDates(Object value)
553     {
554         if (value != null && !value.getClass().isArray())
555         {
556             value = toStrings(value);
557         }
558         if (value == null)
559         {
560             return null;
561         }
562 
563         Date[] dates = new Date[Array.getLength(value)];
564         for (int i=0; i < dates.length; i++)
565         {
566             dates[i] = toDate(Array.get(value, i));
567         }
568         return dates;
569     }
570 
571     /**
572      * @param values the collection of values to be converted
573      * @return an array of Date objects derived from the specified values,
574      *         or <code>null</code>.
575      */
576     public Date[] toDates(Collection values)
577     {
578         if (values == null || !values.isEmpty())
579         {
580             return null;
581         }
582         Date[] dates = new Date[values.size()];
583         int index = 0;
584         for (Object val : values)
585         {
586             dates[index++] = toDate(val);
587         }
588         return dates;
589     }
590 
591     /**
592      * @param value the value to be converted
593      * @return an array of Calendar objects derived from the specified value,
594      *         or <code>null</code>.
595      */
596     public Calendar[] toCalendars(Object value)
597     {
598         if (value != null && !value.getClass().isArray())
599         {
600             value = toStrings(value);
601         }
602         if (value == null)
603         {
604             return null;
605         }
606 
607         Calendar[] calendars = new Calendar[Array.getLength(value)];
608         for (int i=0; i < calendars.length; i++)
609         {
610             calendars[i] = toCalendar(Array.get(value, i));
611         }
612         return calendars;
613     }
614 
615     /**
616      * @param values the collection of values to be converted
617      * @return an array of Calendar objects derived from the specified values,
618      *         or <code>null</code>.
619      */
620     public Calendar[] toCalendars(Collection values)
621     {
622         if (values == null || !values.isEmpty())
623         {
624             return null;
625         }
626         Calendar[] calendars = new Calendar[values.size()];
627         int index = 0;
628         for (Object val : values)
629         {
630             calendars[index++] = toCalendar(val);
631         }
632         return calendars;
633     }
634 
635 
636     // --------------------- basic string parsing methods --------------
637 
638     /**
639      * Converts a parameter value into a {@link Boolean}
640      * Sub-classes can override to allow for customized boolean parsing.
641      * (e.g. to handle "Yes/No" or "T/F")
642      *
643      * @param value the string to be parsed
644      * @return the value as a {@link Boolean}
645      */
646     protected Boolean parseBoolean(String value)
647     {
648         return Boolean.valueOf(value);
649     }
650 
651     /**
652      * Converts a single String value into an array of Strings by splitting
653      * it on the tool's set stringsDelimiter.  The default stringsDelimiter is a comma,
654      * and by default, all strings parsed out are trimmed before returning.
655      */
656     protected String[] parseStringList(String value)
657     {
658         String[] values;
659         if (value.indexOf(this.stringsDelimiter) < 0)
660         {
661             values = new String[] { value };
662         }
663         else
664         {
665             values = value.split(this.stringsDelimiter);
666         }
667         if (this.stringsTrim)
668         {
669             for (int i=0,l=values.length; i < l; i++)
670             {
671                 values[i] = values[i].trim();
672             }
673         }
674         return values;
675     }
676 
677     /**
678      * Converts a String value into a Locale.
679      *
680      */
681     protected Locale parseLocale(String value)
682     {
683         return ConversionUtils.toLocale(value);
684     }
685 
686 
687     // ------------------------- number parsing methods ---------------
688 
689     /**
690      * Converts an object to an instance of {@link Number} using the
691      * format returned by {@link #getNumberFormat()} and the default {@link Locale}
692      * if the object is not already an instance of Number.
693      *
694      * @param value the string to parse
695      * @return the string as a {@link Number} or <code>null</code> if no
696      *         conversion is possible
697      */
698     public Number parseNumber(String value)
699     {
700         return parseNumber(value, this.numberFormat);
701     }
702 
703     /**
704      * Converts an object to an instance of {@link Number} using the
705      * specified format and the {@link Locale} returned by
706      * {@link #getLocale()}.
707      *
708      * @param value - the string to parse
709      * @param format - the format the number is in
710      * @return the string as a {@link Number} or <code>null</code> if no
711      *         conversion is possible
712      * @see #parseNumber(String value, String format, Object locale)
713      */
714     public Number parseNumber(String value, String format)
715     {
716         return parseNumber(value, format, getLocale());
717     }
718 
719     /**
720      * Converts an object to an instance of {@link Number} using the
721      * configured number format and the specified {@link Locale}.
722      *
723      * @param value - the string to parse
724      * @param locale - the Locale to use
725      * @return the string as a {@link Number} or <code>null</code> if no
726      *         conversion is possible
727      * @see java.text.NumberFormat#parse
728      */
729     public Number parseNumber(String value, Object locale)
730     {
731         return parseNumber(value, this.numberFormat, locale);
732     }
733 
734     /**
735      * Converts an object to an instance of {@link Number} using the
736      * specified format and {@link Locale}.
737      *
738      * @param value - the string to parse
739      * @param format - the format the number is in
740      * @param locale - the Locale to use
741      * @return the string as a {@link Number} or <code>null</code> if no
742      *         conversion is possible
743      * @see java.text.NumberFormat#parse
744      */
745     public Number parseNumber(String value, String format, Object locale)
746     {
747         Locale lcl = toLocale(locale);
748         if (lcl == null && locale != null)
749         {
750             // then they gave a broken locale value; fail, to inform them
751             return null;
752         }
753         return ConversionUtils.toNumber(value, format, lcl);
754     }
755 
756     // ----------------- date parsing methods ---------------
757 
758     /**
759      * Converts a string to an instance of {@link Date},
760      * using the configured date parsing format, the configured default
761      * {@link Locale}, and the system's default {@link TimeZone} to parse it.
762      *
763      * @param value the date to convert
764      * @return the object as a {@link Date} or <code>null</code> if no
765      *         conversion is possible
766      */
767     public Date parseDate(String value)
768     {
769         return parseDate(value, this.dateFormat);
770     }
771 
772     /**
773      * Converts a string to an instance of {@link Date} using the
774      * specified format,the configured default {@link Locale},
775      * and the system's default {@link TimeZone} to parse it.
776      *
777      * @param value - the date to convert
778      * @param format - the format the date is in
779      * @return the string as a {@link Date} or <code>null</code> if no
780      *         conversion is possible
781      * @see ConversionUtils#toDate(String str, String format, Locale locale, TimeZone timezone)
782      */
783     public Date parseDate(String value, String format)
784     {
785         return parseDate(value, format, getLocale());
786     }
787 
788     /**
789      * Converts a string to an instance of {@link Date} using the
790      * configured date format and specified {@link Locale} to parse it.
791      *
792      * @param value - the date to convert
793      * @param locale - the Locale to use
794      * @return the string as a {@link Date} or <code>null</code> if no
795      *         conversion is possible
796      * @see java.text.SimpleDateFormat#parse
797      */
798     public Date parseDate(String value, Object locale)
799     {
800         return parseDate(value, this.dateFormat, locale);
801     }
802 
803     /**
804      * Converts a string to an instance of {@link Date} using the
805      * specified format and {@link Locale} to parse it.
806      *
807      * @param value - the date to convert
808      * @param format - the format the date is in
809      * @param locale - the Locale to use
810      * @return the string as a {@link Date} or <code>null</code> if no
811      *         conversion is possible
812      * @see java.text.SimpleDateFormat#parse
813      */
814     public Date parseDate(String value, String format, Object locale)
815     {
816         return parseDate(value, format, locale, TimeZone.getDefault());
817     }
818 
819     /**
820      * Converts a string to an instance of {@link Date} using the
821      * specified format, {@link Locale}, and {@link TimeZone}.
822      *
823      * @param value - the date to convert
824      * @param format - the format the date is in
825      * @param locale - the Locale to use
826      * @param timezone - the {@link TimeZone}
827      * @return the string as a {@link Date} or <code>null</code> if no
828      *         conversion is possible
829      * @see #getDateFormat
830      * @see java.text.SimpleDateFormat#parse
831      */
832     public Date parseDate(String value, String format,
833                           Object locale, TimeZone timezone)
834     {
835         Locale lcl = toLocale(locale);
836         if (lcl == null && locale != null)
837         {
838             // the 'locale' passed in was broken, so don't pretend it worked
839             return null;
840         }
841         return ConversionUtils.toDate(value, format, lcl, timezone);
842     }
843 
844 }