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 * <tools>
43 * <toolbox scope="application">
44 * <tool class="org.apache.velocity.tools.generic.ConversionTool"
45 * dateFormat="yyyy-MM-dd"/>
46 * </toolbox>
47 * </tools>
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 }