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