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 }