001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.configuration; 019 020import java.awt.Color; 021import java.lang.reflect.Array; 022import java.lang.reflect.Constructor; 023import java.lang.reflect.InvocationTargetException; 024import java.math.BigDecimal; 025import java.math.BigInteger; 026import java.net.InetAddress; 027import java.net.MalformedURLException; 028import java.net.URL; 029import java.net.UnknownHostException; 030import java.text.ParseException; 031import java.text.SimpleDateFormat; 032import java.util.ArrayList; 033import java.util.Calendar; 034import java.util.Collection; 035import java.util.Date; 036import java.util.Iterator; 037import java.util.LinkedList; 038import java.util.List; 039import java.util.Locale; 040 041import org.apache.commons.lang.BooleanUtils; 042import org.apache.commons.lang.StringUtils; 043 044/** 045 * A utility class to convert the configuration properties into any type. 046 * 047 * @author Emmanuel Bourg 048 * @version $Id: PropertyConverter.java 1534376 2013-10-21 21:14:18Z henning $ 049 * @since 1.1 050 */ 051public final class PropertyConverter 052{ 053 /** Constant for the list delimiter as char.*/ 054 static final char LIST_ESC_CHAR = '\\'; 055 056 /** Constant for the list delimiter escaping character as string.*/ 057 static final String LIST_ESCAPE = String.valueOf(LIST_ESC_CHAR); 058 059 /** Constant for the prefix of hex numbers.*/ 060 private static final String HEX_PREFIX = "0x"; 061 062 /** Constant for the radix of hex numbers.*/ 063 private static final int HEX_RADIX = 16; 064 065 /** Constant for the prefix of binary numbers.*/ 066 private static final String BIN_PREFIX = "0b"; 067 068 /** Constant for the radix of binary numbers.*/ 069 private static final int BIN_RADIX = 2; 070 071 /** Constant for the argument classes of the Number constructor that takes a String. */ 072 private static final Class<?>[] CONSTR_ARGS = {String.class}; 073 074 /** The fully qualified name of {@link javax.mail.internet.InternetAddress} */ 075 private static final String INTERNET_ADDRESS_CLASSNAME = "javax.mail.internet.InternetAddress"; 076 077 /** 078 * Private constructor prevents instances from being created. 079 */ 080 private PropertyConverter() 081 { 082 // to prevent instantiation... 083 } 084 085 /** 086 * Converts the specified value to the target class. If the class is a 087 * primitive type (Integer.TYPE, Boolean.TYPE, etc) the value returned 088 * will use the wrapper type (Integer.class, Boolean.class, etc). 089 * 090 * @param cls the target class of the converted value 091 * @param value the value to convert 092 * @param params optional parameters used for the conversion 093 * @return the converted value 094 * @throws ConversionException if the value is not compatible with the requested type 095 * 096 * @since 1.5 097 */ 098 static Object to(Class<?> cls, Object value, Object[] params) throws ConversionException 099 { 100 if (cls.isInstance(value)) 101 { 102 return value; // no conversion needed 103 } 104 105 if (Boolean.class.equals(cls) || Boolean.TYPE.equals(cls)) 106 { 107 return toBoolean(value); 108 } 109 else if (Character.class.equals(cls) || Character.TYPE.equals(cls)) 110 { 111 return toCharacter(value); 112 } 113 else if (Number.class.isAssignableFrom(cls) || cls.isPrimitive()) 114 { 115 if (Integer.class.equals(cls) || Integer.TYPE.equals(cls)) 116 { 117 return toInteger(value); 118 } 119 else if (Long.class.equals(cls) || Long.TYPE.equals(cls)) 120 { 121 return toLong(value); 122 } 123 else if (Byte.class.equals(cls) || Byte.TYPE.equals(cls)) 124 { 125 return toByte(value); 126 } 127 else if (Short.class.equals(cls) || Short.TYPE.equals(cls)) 128 { 129 return toShort(value); 130 } 131 else if (Float.class.equals(cls) || Float.TYPE.equals(cls)) 132 { 133 return toFloat(value); 134 } 135 else if (Double.class.equals(cls) || Double.TYPE.equals(cls)) 136 { 137 return toDouble(value); 138 } 139 else if (BigInteger.class.equals(cls)) 140 { 141 return toBigInteger(value); 142 } 143 else if (BigDecimal.class.equals(cls)) 144 { 145 return toBigDecimal(value); 146 } 147 } 148 else if (Date.class.equals(cls)) 149 { 150 return toDate(value, (String) params[0]); 151 } 152 else if (Calendar.class.equals(cls)) 153 { 154 return toCalendar(value, (String) params[0]); 155 } 156 else if (URL.class.equals(cls)) 157 { 158 return toURL(value); 159 } 160 else if (Locale.class.equals(cls)) 161 { 162 return toLocale(value); 163 } 164 else if (isEnum(cls)) 165 { 166 return convertToEnum(cls, value); 167 } 168 else if (Color.class.equals(cls)) 169 { 170 return toColor(value); 171 } 172 else if (cls.getName().equals(INTERNET_ADDRESS_CLASSNAME)) 173 { 174 return toInternetAddress(value); 175 } 176 else if (InetAddress.class.isAssignableFrom(cls)) 177 { 178 return toInetAddress(value); 179 } 180 181 throw new ConversionException("The value '" + value + "' (" + value.getClass() + ")" 182 + " can't be converted to a " + cls.getName() + " object"); 183 } 184 185 /** 186 * Convert the specified object into a Boolean. Internally the 187 * {@code org.apache.commons.lang.BooleanUtils} class from the 188 * <a href="http://commons.apache.org/lang/">Commons Lang</a> 189 * project is used to perform this conversion. This class accepts some more 190 * tokens for the boolean value of <b>true</b>, e.g. {@code yes} and 191 * {@code on}. Please refer to the documentation of this class for more 192 * details. 193 * 194 * @param value the value to convert 195 * @return the converted value 196 * @throws ConversionException thrown if the value cannot be converted to a boolean 197 */ 198 public static Boolean toBoolean(Object value) throws ConversionException 199 { 200 if (value instanceof Boolean) 201 { 202 return (Boolean) value; 203 } 204 else if (value instanceof String) 205 { 206 Boolean b = BooleanUtils.toBooleanObject((String) value); 207 if (b == null) 208 { 209 throw new ConversionException("The value " + value + " can't be converted to a Boolean object"); 210 } 211 return b; 212 } 213 else 214 { 215 throw new ConversionException("The value " + value + " can't be converted to a Boolean object"); 216 } 217 } 218 219 /** 220 * Converts the specified value object to a {@code Character}. This method 221 * converts the passed in object to a string. If the string has exactly one 222 * character, this character is returned as result. Otherwise, conversion 223 * fails. 224 * 225 * @param value the value to be converted 226 * @return the resulting {@code Character} object 227 * @throws ConversionException if the conversion is not possible 228 */ 229 public static Character toCharacter(Object value) throws ConversionException 230 { 231 String strValue = String.valueOf(value); 232 if (strValue.length() == 1) 233 { 234 return Character.valueOf(strValue.charAt(0)); 235 } 236 else 237 { 238 throw new ConversionException( 239 String.format( 240 "The value '%s' cannot be converted to a Character object!", 241 strValue)); 242 } 243 } 244 245 /** 246 * Convert the specified object into a Byte. 247 * 248 * @param value the value to convert 249 * @return the converted value 250 * @throws ConversionException thrown if the value cannot be converted to a byte 251 */ 252 public static Byte toByte(Object value) throws ConversionException 253 { 254 Number n = toNumber(value, Byte.class); 255 if (n instanceof Byte) 256 { 257 return (Byte) n; 258 } 259 else 260 { 261 return new Byte(n.byteValue()); 262 } 263 } 264 265 /** 266 * Convert the specified object into a Short. 267 * 268 * @param value the value to convert 269 * @return the converted value 270 * @throws ConversionException thrown if the value cannot be converted to a short 271 */ 272 public static Short toShort(Object value) throws ConversionException 273 { 274 Number n = toNumber(value, Short.class); 275 if (n instanceof Short) 276 { 277 return (Short) n; 278 } 279 else 280 { 281 return new Short(n.shortValue()); 282 } 283 } 284 285 /** 286 * Convert the specified object into an Integer. 287 * 288 * @param value the value to convert 289 * @return the converted value 290 * @throws ConversionException thrown if the value cannot be converted to an integer 291 */ 292 public static Integer toInteger(Object value) throws ConversionException 293 { 294 Number n = toNumber(value, Integer.class); 295 if (n instanceof Integer) 296 { 297 return (Integer) n; 298 } 299 else 300 { 301 return new Integer(n.intValue()); 302 } 303 } 304 305 /** 306 * Convert the specified object into a Long. 307 * 308 * @param value the value to convert 309 * @return the converted value 310 * @throws ConversionException thrown if the value cannot be converted to a Long 311 */ 312 public static Long toLong(Object value) throws ConversionException 313 { 314 Number n = toNumber(value, Long.class); 315 if (n instanceof Long) 316 { 317 return (Long) n; 318 } 319 else 320 { 321 return new Long(n.longValue()); 322 } 323 } 324 325 /** 326 * Convert the specified object into a Float. 327 * 328 * @param value the value to convert 329 * @return the converted value 330 * @throws ConversionException thrown if the value cannot be converted to a Float 331 */ 332 public static Float toFloat(Object value) throws ConversionException 333 { 334 Number n = toNumber(value, Float.class); 335 if (n instanceof Float) 336 { 337 return (Float) n; 338 } 339 else 340 { 341 return new Float(n.floatValue()); 342 } 343 } 344 345 /** 346 * Convert the specified object into a Double. 347 * 348 * @param value the value to convert 349 * @return the converted value 350 * @throws ConversionException thrown if the value cannot be converted to a Double 351 */ 352 public static Double toDouble(Object value) throws ConversionException 353 { 354 Number n = toNumber(value, Double.class); 355 if (n instanceof Double) 356 { 357 return (Double) n; 358 } 359 else 360 { 361 return new Double(n.doubleValue()); 362 } 363 } 364 365 /** 366 * Convert the specified object into a BigInteger. 367 * 368 * @param value the value to convert 369 * @return the converted value 370 * @throws ConversionException thrown if the value cannot be converted to a BigInteger 371 */ 372 public static BigInteger toBigInteger(Object value) throws ConversionException 373 { 374 Number n = toNumber(value, BigInteger.class); 375 if (n instanceof BigInteger) 376 { 377 return (BigInteger) n; 378 } 379 else 380 { 381 return BigInteger.valueOf(n.longValue()); 382 } 383 } 384 385 /** 386 * Convert the specified object into a BigDecimal. 387 * 388 * @param value the value to convert 389 * @return the converted value 390 * @throws ConversionException thrown if the value cannot be converted to a BigDecimal 391 */ 392 public static BigDecimal toBigDecimal(Object value) throws ConversionException 393 { 394 Number n = toNumber(value, BigDecimal.class); 395 if (n instanceof BigDecimal) 396 { 397 return (BigDecimal) n; 398 } 399 else 400 { 401 return new BigDecimal(n.doubleValue()); 402 } 403 } 404 405 /** 406 * Tries to convert the specified object into a number object. This method 407 * is used by the conversion methods for number types. Note that the return 408 * value is not in always of the specified target class, but only if a new 409 * object has to be created. 410 * 411 * @param value the value to be converted (must not be <b>null</b>) 412 * @param targetClass the target class of the conversion (must be derived 413 * from {@code java.lang.Number}) 414 * @return the converted number 415 * @throws ConversionException if the object cannot be converted 416 */ 417 static Number toNumber(Object value, Class<?> targetClass) throws ConversionException 418 { 419 if (value instanceof Number) 420 { 421 return (Number) value; 422 } 423 else 424 { 425 String str = value.toString(); 426 if (str.startsWith(HEX_PREFIX)) 427 { 428 try 429 { 430 return new BigInteger(str.substring(HEX_PREFIX.length()), HEX_RADIX); 431 } 432 catch (NumberFormatException nex) 433 { 434 throw new ConversionException("Could not convert " + str 435 + " to " + targetClass.getName() 436 + "! Invalid hex number.", nex); 437 } 438 } 439 440 if (str.startsWith(BIN_PREFIX)) 441 { 442 try 443 { 444 return new BigInteger(str.substring(BIN_PREFIX.length()), BIN_RADIX); 445 } 446 catch (NumberFormatException nex) 447 { 448 throw new ConversionException("Could not convert " + str 449 + " to " + targetClass.getName() 450 + "! Invalid binary number.", nex); 451 } 452 } 453 454 try 455 { 456 Constructor<?> constr = targetClass.getConstructor(CONSTR_ARGS); 457 return (Number) constr.newInstance(new Object[]{str}); 458 } 459 catch (InvocationTargetException itex) 460 { 461 throw new ConversionException("Could not convert " + str 462 + " to " + targetClass.getName(), itex 463 .getTargetException()); 464 } 465 catch (Exception ex) 466 { 467 // Treat all possible exceptions the same way 468 throw new ConversionException( 469 "Conversion error when trying to convert " + str 470 + " to " + targetClass.getName(), ex); 471 } 472 } 473 } 474 475 /** 476 * Convert the specified object into an URL. 477 * 478 * @param value the value to convert 479 * @return the converted value 480 * @throws ConversionException thrown if the value cannot be converted to an URL 481 */ 482 public static URL toURL(Object value) throws ConversionException 483 { 484 if (value instanceof URL) 485 { 486 return (URL) value; 487 } 488 else if (value instanceof String) 489 { 490 try 491 { 492 return new URL((String) value); 493 } 494 catch (MalformedURLException e) 495 { 496 throw new ConversionException("The value " + value + " can't be converted to an URL", e); 497 } 498 } 499 else 500 { 501 throw new ConversionException("The value " + value + " can't be converted to an URL"); 502 } 503 } 504 505 /** 506 * Convert the specified object into a Locale. 507 * 508 * @param value the value to convert 509 * @return the converted value 510 * @throws ConversionException thrown if the value cannot be converted to a Locale 511 */ 512 public static Locale toLocale(Object value) throws ConversionException 513 { 514 if (value instanceof Locale) 515 { 516 return (Locale) value; 517 } 518 else if (value instanceof String) 519 { 520 List<String> elements = split((String) value, '_'); 521 int size = elements.size(); 522 523 if (size >= 1 && ((elements.get(0)).length() == 2 || (elements.get(0)).length() == 0)) 524 { 525 String language = elements.get(0); 526 String country = (size >= 2) ? elements.get(1) : ""; 527 String variant = (size >= 3) ? elements.get(2) : ""; 528 529 return new Locale(language, country, variant); 530 } 531 else 532 { 533 throw new ConversionException("The value " + value + " can't be converted to a Locale"); 534 } 535 } 536 else 537 { 538 throw new ConversionException("The value " + value + " can't be converted to a Locale"); 539 } 540 } 541 542 /** 543 * Split a string on the specified delimiter. To be removed when 544 * commons-lang has a better replacement available (Tokenizer?). 545 * 546 * todo: replace with a commons-lang equivalent 547 * 548 * @param s the string to split 549 * @param delimiter the delimiter 550 * @param trim a flag whether the single elements should be trimmed 551 * @return a list with the single tokens 552 */ 553 public static List<String> split(String s, char delimiter, boolean trim) 554 { 555 if (s == null) 556 { 557 return new ArrayList<String>(); 558 } 559 560 List<String> list = new ArrayList<String>(); 561 562 StringBuilder token = new StringBuilder(); 563 int begin = 0; 564 boolean inEscape = false; 565 566 while (begin < s.length()) 567 { 568 char c = s.charAt(begin); 569 if (inEscape) 570 { 571 // last character was the escape marker 572 // can current character be escaped? 573 if (c != delimiter && c != LIST_ESC_CHAR) 574 { 575 // no, also add escape character 576 token.append(LIST_ESC_CHAR); 577 } 578 token.append(c); 579 inEscape = false; 580 } 581 582 else 583 { 584 if (c == delimiter) 585 { 586 // found a list delimiter -> add token and resetDefaultFileSystem buffer 587 String t = token.toString(); 588 if (trim) 589 { 590 t = t.trim(); 591 } 592 list.add(t); 593 token = new StringBuilder(); 594 } 595 else if (c == LIST_ESC_CHAR) 596 { 597 // eventually escape next character 598 inEscape = true; 599 } 600 else 601 { 602 token.append(c); 603 } 604 } 605 606 begin++; 607 } 608 609 // Trailing delimiter? 610 if (inEscape) 611 { 612 token.append(LIST_ESC_CHAR); 613 } 614 // Add last token 615 String t = token.toString(); 616 if (trim) 617 { 618 t = t.trim(); 619 } 620 list.add(t); 621 622 return list; 623 } 624 625 /** 626 * Split a string on the specified delimiter always trimming the elements. 627 * This is a shortcut for {@code split(s, delimiter, true)}. 628 * 629 * @param s the string to split 630 * @param delimiter the delimiter 631 * @return a list with the single tokens 632 */ 633 public static List<String> split(String s, char delimiter) 634 { 635 return split(s, delimiter, true); 636 } 637 638 /** 639 * Escapes the delimiters that might be contained in the given string. This 640 * method works like {@link #escapeListDelimiter(String, char)}. In addition, 641 * a single backslash will also be escaped. 642 * 643 * @param s the string with the value 644 * @param delimiter the list delimiter to use 645 * @return the correctly escaped string 646 */ 647 public static String escapeDelimiters(String s, char delimiter) 648 { 649 String s1 = StringUtils.replace(s, LIST_ESCAPE, LIST_ESCAPE + LIST_ESCAPE); 650 return escapeListDelimiter(s1, delimiter); 651 } 652 653 /** 654 * Escapes the list delimiter if it is contained in the given string. This 655 * method ensures that list delimiter characters that are part of a 656 * property's value are correctly escaped when a configuration is saved to a 657 * file. Otherwise when loaded again the property will be treated as a list 658 * property. 659 * 660 * @param s the string with the value 661 * @param delimiter the list delimiter to use 662 * @return the escaped string 663 * @since 1.7 664 */ 665 public static String escapeListDelimiter(String s, char delimiter) 666 { 667 return StringUtils.replace(s, String.valueOf(delimiter), LIST_ESCAPE 668 + delimiter); 669 } 670 671 /** 672 * Convert the specified object into a Color. If the value is a String, 673 * the format allowed is (#)?[0-9A-F]{6}([0-9A-F]{2})?. Examples: 674 * <ul> 675 * <li>FF0000 (red)</li> 676 * <li>0000FFA0 (semi transparent blue)</li> 677 * <li>#CCCCCC (gray)</li> 678 * <li>#00FF00A0 (semi transparent green)</li> 679 * </ul> 680 * 681 * @param value the value to convert 682 * @return the converted value 683 * @throws ConversionException thrown if the value cannot be converted to a Color 684 */ 685 public static Color toColor(Object value) throws ConversionException 686 { 687 if (value instanceof Color) 688 { 689 return (Color) value; 690 } 691 else if (value instanceof String && !StringUtils.isBlank((String) value)) 692 { 693 String color = ((String) value).trim(); 694 695 int[] components = new int[3]; 696 697 // check the size of the string 698 int minlength = components.length * 2; 699 if (color.length() < minlength) 700 { 701 throw new ConversionException("The value " + value + " can't be converted to a Color"); 702 } 703 704 // remove the leading # 705 if (color.startsWith("#")) 706 { 707 color = color.substring(1); 708 } 709 710 try 711 { 712 // parse the components 713 for (int i = 0; i < components.length; i++) 714 { 715 components[i] = Integer.parseInt(color.substring(2 * i, 2 * i + 2), HEX_RADIX); 716 } 717 718 // parse the transparency 719 int alpha; 720 if (color.length() >= minlength + 2) 721 { 722 alpha = Integer.parseInt(color.substring(minlength, minlength + 2), HEX_RADIX); 723 } 724 else 725 { 726 alpha = Color.black.getAlpha(); 727 } 728 729 return new Color(components[0], components[1], components[2], alpha); 730 } 731 catch (Exception e) 732 { 733 throw new ConversionException("The value " + value + " can't be converted to a Color", e); 734 } 735 } 736 else 737 { 738 throw new ConversionException("The value " + value + " can't be converted to a Color"); 739 } 740 } 741 742 /** 743 * Convert the specified value into an internet address. 744 * 745 * @param value the value to convert 746 * @return the converted value 747 * @throws ConversionException thrown if the value cannot be converted to a InetAddress 748 * 749 * @since 1.5 750 */ 751 static InetAddress toInetAddress(Object value) throws ConversionException 752 { 753 if (value instanceof InetAddress) 754 { 755 return (InetAddress) value; 756 } 757 else if (value instanceof String) 758 { 759 try 760 { 761 return InetAddress.getByName((String) value); 762 } 763 catch (UnknownHostException e) 764 { 765 throw new ConversionException("The value " + value + " can't be converted to a InetAddress", e); 766 } 767 } 768 else 769 { 770 throw new ConversionException("The value " + value + " can't be converted to a InetAddress"); 771 } 772 } 773 774 /** 775 * Convert the specified value into an email address. 776 * 777 * @param value the value to convert 778 * @return the converted value 779 * @throws ConversionException thrown if the value cannot be converted to an email address 780 * 781 * @since 1.5 782 */ 783 static Object toInternetAddress(Object value) throws ConversionException 784 { 785 if (value.getClass().getName().equals(INTERNET_ADDRESS_CLASSNAME)) 786 { 787 return value; 788 } 789 else if (value instanceof String) 790 { 791 try 792 { 793 Constructor<?> ctor = Class.forName(INTERNET_ADDRESS_CLASSNAME) 794 .getConstructor(new Class[] {String.class}); 795 return ctor.newInstance(new Object[] {value}); 796 } 797 catch (Exception e) 798 { 799 throw new ConversionException("The value " + value + " can't be converted to a InternetAddress", e); 800 } 801 } 802 else 803 { 804 throw new ConversionException("The value " + value + " can't be converted to a InternetAddress"); 805 } 806 } 807 808 /** 809 * Calls Class.isEnum() on Java 5, returns false on older JRE. 810 */ 811 static boolean isEnum(Class<?> cls) 812 { 813 return cls.isEnum(); 814 } 815 816 /** 817 * Convert the specified value into a Java 5 enum. 818 * 819 * @param value the value to convert 820 * @param cls the type of the enumeration 821 * @return the converted value 822 * @throws ConversionException thrown if the value cannot be converted to an enumeration 823 * 824 * @since 1.5 825 */ 826 static <E extends Enum<E>> E toEnum(Object value, Class<E> cls) throws ConversionException 827 { 828 if (value.getClass().equals(cls)) 829 { 830 return cls.cast(value); 831 } 832 else if (value instanceof String) 833 { 834 try 835 { 836 return Enum.valueOf(cls, (String) value); 837 } 838 catch (Exception e) 839 { 840 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName()); 841 } 842 } 843 else if (value instanceof Number) 844 { 845 try 846 { 847 E[] enumConstants = cls.getEnumConstants(); 848 return enumConstants[((Number) value).intValue()]; 849 } 850 catch (Exception e) 851 { 852 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName()); 853 } 854 } 855 else 856 { 857 throw new ConversionException("The value " + value + " can't be converted to a " + cls.getName()); 858 } 859 } 860 861 /** 862 * Convert the specified object into a Date. 863 * 864 * @param value the value to convert 865 * @param format the DateFormat pattern to parse String values 866 * @return the converted value 867 * @throws ConversionException thrown if the value cannot be converted to a Calendar 868 */ 869 public static Date toDate(Object value, String format) throws ConversionException 870 { 871 if (value instanceof Date) 872 { 873 return (Date) value; 874 } 875 else if (value instanceof Calendar) 876 { 877 return ((Calendar) value).getTime(); 878 } 879 else if (value instanceof String) 880 { 881 try 882 { 883 return new SimpleDateFormat(format).parse((String) value); 884 } 885 catch (ParseException e) 886 { 887 throw new ConversionException("The value " + value + " can't be converted to a Date", e); 888 } 889 } 890 else 891 { 892 throw new ConversionException("The value " + value + " can't be converted to a Date"); 893 } 894 } 895 896 /** 897 * Convert the specified object into a Calendar. 898 * 899 * @param value the value to convert 900 * @param format the DateFormat pattern to parse String values 901 * @return the converted value 902 * @throws ConversionException thrown if the value cannot be converted to a Calendar 903 */ 904 public static Calendar toCalendar(Object value, String format) throws ConversionException 905 { 906 if (value instanceof Calendar) 907 { 908 return (Calendar) value; 909 } 910 else if (value instanceof Date) 911 { 912 Calendar calendar = Calendar.getInstance(); 913 calendar.setTime((Date) value); 914 return calendar; 915 } 916 else if (value instanceof String) 917 { 918 try 919 { 920 Calendar calendar = Calendar.getInstance(); 921 calendar.setTime(new SimpleDateFormat(format).parse((String) value)); 922 return calendar; 923 } 924 catch (ParseException e) 925 { 926 throw new ConversionException("The value " + value + " can't be converted to a Calendar", e); 927 } 928 } 929 else 930 { 931 throw new ConversionException("The value " + value + " can't be converted to a Calendar"); 932 } 933 } 934 935 /** 936 * Returns an iterator over the simple values of a composite value. This 937 * implementation calls {@link #flatten(Object, char)} and 938 * returns an iterator over the returned collection. 939 * 940 * @param value the value to "split" 941 * @param delimiter the delimiter for String values 942 * @return an iterator for accessing the single values 943 */ 944 public static Iterator<?> toIterator(Object value, char delimiter) 945 { 946 return flatten(value, delimiter).iterator(); 947 } 948 949 /** 950 * Returns a collection with all values contained in the specified object. 951 * This method is used for instance by the {@code addProperty()} 952 * implementation of the default configurations to gather all values of the 953 * property to add. Depending on the type of the passed in object the 954 * following things happen: 955 * <ul> 956 * <li>Strings are checked for delimiter characters and split if necessary.</li> 957 * <li>For objects implementing the {@code Iterable} interface, the 958 * corresponding {@code Iterator} is obtained, and contained elements 959 * are added to the resulting collection.</li> 960 * <li>Arrays are treated as {@code Iterable} objects.</li> 961 * <li>All other types are directly inserted.</li> 962 * <li>Recursive combinations are supported, e.g. a collection containing 963 * an array that contains strings: The resulting collection will only 964 * contain primitive objects (hence the name "flatten").</li> 965 * </ul> 966 * 967 * @param value the value to be processed 968 * @param delimiter the delimiter for String values 969 * @return a "flat" collection containing all primitive values of 970 * the passed in object 971 */ 972 private static Collection<?> flatten(Object value, char delimiter) 973 { 974 if (value instanceof String) 975 { 976 String s = (String) value; 977 if (s.indexOf(delimiter) > 0) 978 { 979 return split(s, delimiter); 980 } 981 } 982 983 Collection<Object> result = new LinkedList<Object>(); 984 if (value instanceof Iterable) 985 { 986 flattenIterator(result, ((Iterable<?>) value).iterator(), delimiter); 987 } 988 else if (value instanceof Iterator) 989 { 990 flattenIterator(result, (Iterator<?>) value, delimiter); 991 } 992 else if (value != null) 993 { 994 if (value.getClass().isArray()) 995 { 996 for (int len = Array.getLength(value), idx = 0; idx < len; idx++) 997 { 998 result.addAll(flatten(Array.get(value, idx), delimiter)); 999 } 1000 } 1001 else 1002 { 1003 result.add(value); 1004 } 1005 } 1006 1007 return result; 1008 } 1009 1010 /** 1011 * Flattens the given iterator. For each element in the iteration 1012 * {@code flatten()} will be called recursively. 1013 * 1014 * @param target the target collection 1015 * @param it the iterator to process 1016 * @param delimiter the delimiter for String values 1017 */ 1018 private static void flattenIterator(Collection<Object> target, Iterator<?> it, char delimiter) 1019 { 1020 while (it.hasNext()) 1021 { 1022 target.addAll(flatten(it.next(), delimiter)); 1023 } 1024 } 1025 1026 /** 1027 * Performs interpolation of the specified value. This method checks if the 1028 * given value contains variables of the form <code>${...}</code>. If 1029 * this is the case, all occurrences will be substituted by their current 1030 * values. 1031 * 1032 * @param value the value to be interpolated 1033 * @param config the current configuration object 1034 * @return the interpolated value 1035 */ 1036 public static Object interpolate(Object value, AbstractConfiguration config) 1037 { 1038 if (value instanceof String) 1039 { 1040 return config.getSubstitutor().replace((String) value); 1041 } 1042 else 1043 { 1044 return value; 1045 } 1046 } 1047 1048 /** 1049 * Helper method for converting a value to a constant of an enumeration 1050 * class. 1051 * 1052 * @param enumClass the enumeration class 1053 * @param value the value to be converted 1054 * @return the converted value 1055 */ 1056 @SuppressWarnings("unchecked") 1057 // conversion is safe because we know that the class is an Enum class 1058 private static Object convertToEnum(Class<?> enumClass, Object value) 1059 { 1060 return toEnum(value, enumClass.asSubclass(Enum.class)); 1061 } 1062}