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 018 package org.apache.commons.configuration; 019 020 import java.lang.reflect.Array; 021 import java.math.BigDecimal; 022 import java.math.BigInteger; 023 import java.util.ArrayList; 024 import java.util.Arrays; 025 import java.util.Collection; 026 import java.util.Collections; 027 import java.util.Iterator; 028 import java.util.List; 029 import java.util.NoSuchElementException; 030 import java.util.Properties; 031 032 import org.apache.commons.configuration.event.ConfigurationErrorEvent; 033 import org.apache.commons.configuration.event.ConfigurationErrorListener; 034 import org.apache.commons.configuration.event.EventSource; 035 import org.apache.commons.configuration.interpol.ConfigurationInterpolator; 036 import org.apache.commons.lang.BooleanUtils; 037 import org.apache.commons.lang.ClassUtils; 038 import org.apache.commons.lang.ObjectUtils; 039 import org.apache.commons.lang.text.StrLookup; 040 import org.apache.commons.lang.text.StrSubstitutor; 041 import org.apache.commons.logging.Log; 042 import org.apache.commons.logging.impl.NoOpLog; 043 044 /** 045 * <p>Abstract configuration class. Provides basic functionality but does not 046 * store any data.</p> 047 * <p>If you want to write your own Configuration class then you should 048 * implement only abstract methods from this class. A lot of functionality 049 * needed by typical implementations of the {@code Configuration} 050 * interface is already provided by this base class. Following is a list of 051 * features implemented here: 052 * <ul><li>Data conversion support. The various data types required by the 053 * {@code Configuration} interface are already handled by this base class. 054 * A concrete sub class only needs to provide a generic {@code getProperty()} 055 * method.</li> 056 * <li>Support for variable interpolation. Property values containing special 057 * variable tokens (like <code>${var}</code>) will be replaced by their 058 * corresponding values.</li> 059 * <li>Support for string lists. The values of properties to be added to this 060 * configuration are checked whether they contain a list delimiter character. If 061 * this is the case and if list splitting is enabled, the string is split and 062 * multiple values are added for this property. (With the 063 * {@code setListDelimiter()} method the delimiter character can be 064 * specified; per default a comma is used. The 065 * {@code setDelimiterParsingDisabled()} method can be used to disable 066 * list splitting completely.)</li> 067 * <li>Allows to specify how missing properties are treated. Per default the 068 * get methods returning an object will return <b>null</b> if the searched 069 * property key is not found (and no default value is provided). With the 070 * {@code setThrowExceptionOnMissing()} method this behavior can be 071 * changed to throw an exception when a requested property cannot be found.</li> 072 * <li>Basic event support. Whenever this configuration is modified registered 073 * event listeners are notified. Refer to the various {@code EVENT_XXX} 074 * constants to get an impression about which event types are supported.</li> 075 * </ul></p> 076 * 077 * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov </a> 078 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen </a> 079 * @version $Id: AbstractConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $ 080 */ 081 public abstract class AbstractConfiguration extends EventSource implements Configuration 082 { 083 /** 084 * Constant for the add property event type. 085 * @since 1.3 086 */ 087 public static final int EVENT_ADD_PROPERTY = 1; 088 089 /** 090 * Constant for the clear property event type. 091 * @since 1.3 092 */ 093 public static final int EVENT_CLEAR_PROPERTY = 2; 094 095 /** 096 * Constant for the set property event type. 097 * @since 1.3 098 */ 099 public static final int EVENT_SET_PROPERTY = 3; 100 101 /** 102 * Constant for the clear configuration event type. 103 * @since 1.3 104 */ 105 public static final int EVENT_CLEAR = 4; 106 107 /** 108 * Constant for the get property event type. This event type is used for 109 * error events. 110 * @since 1.4 111 */ 112 public static final int EVENT_READ_PROPERTY = 5; 113 114 /** start token */ 115 protected static final String START_TOKEN = "${"; 116 117 /** end token */ 118 protected static final String END_TOKEN = "}"; 119 120 /** 121 * Constant for the disabled list delimiter. This character is passed to the 122 * list parsing methods if delimiter parsing is disabled. So this character 123 * should not occur in string property values. 124 */ 125 private static final char DISABLED_DELIMITER = '\0'; 126 127 /** The default value for listDelimiter */ 128 private static char defaultListDelimiter = ','; 129 130 /** Delimiter used to convert single values to lists */ 131 private char listDelimiter = defaultListDelimiter; 132 133 /** 134 * When set to true the given configuration delimiter will not be used 135 * while parsing for this configuration. 136 */ 137 private boolean delimiterParsingDisabled; 138 139 /** 140 * Whether the configuration should throw NoSuchElementExceptions or simply 141 * return null when a property does not exist. Defaults to return null. 142 */ 143 private boolean throwExceptionOnMissing; 144 145 /** Stores a reference to the object that handles variable interpolation.*/ 146 private StrSubstitutor substitutor; 147 148 /** Stores the logger.*/ 149 private Log log; 150 151 /** 152 * Creates a new instance of {@code AbstractConfiguration}. 153 */ 154 public AbstractConfiguration() 155 { 156 setLogger(null); 157 } 158 159 /** 160 * For configurations extending AbstractConfiguration, allow them to change 161 * the listDelimiter from the default comma (","). This value will be used 162 * only when creating new configurations. Those already created will not be 163 * affected by this change 164 * 165 * @param delimiter The new listDelimiter 166 */ 167 public static void setDefaultListDelimiter(char delimiter) 168 { 169 AbstractConfiguration.defaultListDelimiter = delimiter; 170 } 171 172 /** 173 * Sets the default list delimiter. 174 * 175 * @param delimiter the delimiter character 176 * @deprecated Use AbstractConfiguration.setDefaultListDelimiter(char) 177 * instead 178 */ 179 @Deprecated 180 public static void setDelimiter(char delimiter) 181 { 182 setDefaultListDelimiter(delimiter); 183 } 184 185 /** 186 * Retrieve the current delimiter. By default this is a comma (","). 187 * 188 * @return The delimiter in use 189 */ 190 public static char getDefaultListDelimiter() 191 { 192 return AbstractConfiguration.defaultListDelimiter; 193 } 194 195 /** 196 * Returns the default list delimiter. 197 * 198 * @return the default list delimiter 199 * @deprecated Use AbstractConfiguration.getDefaultListDelimiter() instead 200 */ 201 @Deprecated 202 public static char getDelimiter() 203 { 204 return getDefaultListDelimiter(); 205 } 206 207 /** 208 * Change the list delimiter for this configuration. 209 * 210 * Note: this change will only be effective for new parsings. If you 211 * want it to take effect for all loaded properties use the no arg constructor 212 * and call this method before setting the source. 213 * 214 * @param listDelimiter The new listDelimiter 215 */ 216 public void setListDelimiter(char listDelimiter) 217 { 218 this.listDelimiter = listDelimiter; 219 } 220 221 /** 222 * Retrieve the delimiter for this configuration. The default 223 * is the value of defaultListDelimiter. 224 * 225 * @return The listDelimiter in use 226 */ 227 public char getListDelimiter() 228 { 229 return listDelimiter; 230 } 231 232 /** 233 * Determine if this configuration is using delimiters when parsing 234 * property values to convert them to lists of values. Defaults to false 235 * @return true if delimiters are not being used 236 */ 237 public boolean isDelimiterParsingDisabled() 238 { 239 return delimiterParsingDisabled; 240 } 241 242 /** 243 * Set whether this configuration should use delimiters when parsing 244 * property values to convert them to lists of values. By default delimiter 245 * parsing is enabled 246 * 247 * Note: this change will only be effective for new parsings. If you 248 * want it to take effect for all loaded properties use the no arg constructor 249 * and call this method before setting source. 250 * @param delimiterParsingDisabled a flag whether delimiter parsing should 251 * be disabled 252 */ 253 public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled) 254 { 255 this.delimiterParsingDisabled = delimiterParsingDisabled; 256 } 257 258 /** 259 * Allows to set the {@code throwExceptionOnMissing} flag. This 260 * flag controls the behavior of property getter methods that return 261 * objects if the requested property is missing. If the flag is set to 262 * <b>false</b> (which is the default value), these methods will return 263 * <b>null</b>. If set to <b>true</b>, they will throw a 264 * {@code NoSuchElementException} exception. Note that getter methods 265 * for primitive data types are not affected by this flag. 266 * 267 * @param throwExceptionOnMissing The new value for the property 268 */ 269 public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing) 270 { 271 this.throwExceptionOnMissing = throwExceptionOnMissing; 272 } 273 274 /** 275 * Returns true if missing values throw Exceptions. 276 * 277 * @return true if missing values throw Exceptions 278 */ 279 public boolean isThrowExceptionOnMissing() 280 { 281 return throwExceptionOnMissing; 282 } 283 284 /** 285 * Returns the object that is responsible for variable interpolation. 286 * 287 * @return the object responsible for variable interpolation 288 * @since 1.4 289 */ 290 public synchronized StrSubstitutor getSubstitutor() 291 { 292 if (substitutor == null) 293 { 294 substitutor = new StrSubstitutor(createInterpolator()); 295 } 296 return substitutor; 297 } 298 299 /** 300 * Returns the {@code ConfigurationInterpolator} object that manages 301 * the lookup objects for resolving variables. <em>Note:</em> If this 302 * object is manipulated (e.g. new lookup objects added), synchronization 303 * has to be manually ensured. Because 304 * {@code ConfigurationInterpolator} is not thread-safe concurrent 305 * access to properties of this configuration instance (which causes the 306 * interpolator to be invoked) may cause race conditions. 307 * 308 * @return the {@code ConfigurationInterpolator} associated with this 309 * configuration 310 * @since 1.4 311 */ 312 public ConfigurationInterpolator getInterpolator() 313 { 314 return (ConfigurationInterpolator) getSubstitutor() 315 .getVariableResolver(); 316 } 317 318 /** 319 * Creates the interpolator object that is responsible for variable 320 * interpolation. This method is invoked on first access of the 321 * interpolation features. It creates a new instance of 322 * {@code ConfigurationInterpolator} and sets the default lookup 323 * object to an implementation that queries this configuration. 324 * 325 * @return the newly created interpolator object 326 * @since 1.4 327 */ 328 protected ConfigurationInterpolator createInterpolator() 329 { 330 ConfigurationInterpolator interpol = new ConfigurationInterpolator(); 331 interpol.setDefaultLookup(new StrLookup() 332 { 333 @Override 334 public String lookup(String var) 335 { 336 Object prop = resolveContainerStore(var); 337 return (prop != null) ? prop.toString() : null; 338 } 339 }); 340 return interpol; 341 } 342 343 /** 344 * Returns the logger used by this configuration object. 345 * 346 * @return the logger 347 * @since 1.4 348 */ 349 public Log getLogger() 350 { 351 return log; 352 } 353 354 /** 355 * Allows to set the logger to be used by this configuration object. This 356 * method makes it possible for clients to exactly control logging behavior. 357 * Per default a logger is set that will ignore all log messages. Derived 358 * classes that want to enable logging should call this method during their 359 * initialization with the logger to be used. 360 * 361 * @param log the new logger 362 * @since 1.4 363 */ 364 public void setLogger(Log log) 365 { 366 this.log = (log != null) ? log : new NoOpLog(); 367 } 368 369 /** 370 * Adds a special 371 * {@link org.apache.commons.configuration.event.ConfigurationErrorListener} 372 * object to this configuration that will log all internal errors. This 373 * method is intended to be used by certain derived classes, for which it is 374 * known that they can fail on property access (e.g. 375 * {@code DatabaseConfiguration}). 376 * 377 * @since 1.4 378 */ 379 public void addErrorLogListener() 380 { 381 addErrorListener(new ConfigurationErrorListener() 382 { 383 public void configurationError(ConfigurationErrorEvent event) 384 { 385 getLogger().warn("Internal error", event.getCause()); 386 } 387 }); 388 } 389 390 public void addProperty(String key, Object value) 391 { 392 fireEvent(EVENT_ADD_PROPERTY, key, value, true); 393 addPropertyValues(key, value, 394 isDelimiterParsingDisabled() ? DISABLED_DELIMITER 395 : getListDelimiter()); 396 fireEvent(EVENT_ADD_PROPERTY, key, value, false); 397 } 398 399 /** 400 * Adds a key/value pair to the Configuration. Override this method to 401 * provide write access to underlying Configuration store. 402 * 403 * @param key key to use for mapping 404 * @param value object to store 405 */ 406 protected abstract void addPropertyDirect(String key, Object value); 407 408 /** 409 * Adds the specified value for the given property. This method supports 410 * single values and containers (e.g. collections or arrays) as well. In the 411 * latter case, {@code addPropertyDirect()} will be called for each 412 * element. 413 * 414 * @param key the property key 415 * @param value the value object 416 * @param delimiter the list delimiter character 417 */ 418 private void addPropertyValues(String key, Object value, char delimiter) 419 { 420 Iterator<?> it = PropertyConverter.toIterator(value, delimiter); 421 while (it.hasNext()) 422 { 423 addPropertyDirect(key, it.next()); 424 } 425 } 426 427 /** 428 * interpolate key names to handle ${key} stuff 429 * 430 * @param base string to interpolate 431 * 432 * @return returns the key name with the ${key} substituted 433 */ 434 protected String interpolate(String base) 435 { 436 Object result = interpolate((Object) base); 437 return (result == null) ? null : result.toString(); 438 } 439 440 /** 441 * Returns the interpolated value. Non String values are returned without change. 442 * 443 * @param value the value to interpolate 444 * 445 * @return returns the value with variables substituted 446 */ 447 protected Object interpolate(Object value) 448 { 449 return PropertyConverter.interpolate(value, this); 450 } 451 452 /** 453 * Recursive handler for multple levels of interpolation. 454 * 455 * When called the first time, priorVariables should be null. 456 * 457 * @param base string with the ${key} variables 458 * @param priorVariables serves two purposes: to allow checking for loops, 459 * and creating a meaningful exception message should a loop occur. It's 460 * 0'th element will be set to the value of base from the first call. All 461 * subsequent interpolated variables are added afterward. 462 * 463 * @return the string with the interpolation taken care of 464 * @deprecated Interpolation is now handled by 465 * {@link PropertyConverter}; this method will no longer be 466 * called 467 */ 468 @Deprecated 469 protected String interpolateHelper(String base, List<?> priorVariables) 470 { 471 return base; // just a dummy implementation 472 } 473 474 public Configuration subset(String prefix) 475 { 476 return new SubsetConfiguration(this, prefix, "."); 477 } 478 479 public void setProperty(String key, Object value) 480 { 481 fireEvent(EVENT_SET_PROPERTY, key, value, true); 482 setDetailEvents(false); 483 try 484 { 485 clearProperty(key); 486 addProperty(key, value); 487 } 488 finally 489 { 490 setDetailEvents(true); 491 } 492 fireEvent(EVENT_SET_PROPERTY, key, value, false); 493 } 494 495 /** 496 * Removes the specified property from this configuration. This 497 * implementation performs some preparations and then delegates to 498 * {@code clearPropertyDirect()}, which will do the real work. 499 * 500 * @param key the key to be removed 501 */ 502 public void clearProperty(String key) 503 { 504 fireEvent(EVENT_CLEAR_PROPERTY, key, null, true); 505 clearPropertyDirect(key); 506 fireEvent(EVENT_CLEAR_PROPERTY, key, null, false); 507 } 508 509 /** 510 * Removes the specified property from this configuration. This method is 511 * called by {@code clearProperty()} after it has done some 512 * preparations. It should be overridden in sub classes. This base 513 * implementation is just left empty. 514 * 515 * @param key the key to be removed 516 */ 517 protected void clearPropertyDirect(String key) 518 { 519 // override in sub classes 520 } 521 522 public void clear() 523 { 524 fireEvent(EVENT_CLEAR, null, null, true); 525 setDetailEvents(false); 526 boolean useIterator = true; 527 try 528 { 529 Iterator<String> it = getKeys(); 530 while (it.hasNext()) 531 { 532 String key = it.next(); 533 if (useIterator) 534 { 535 try 536 { 537 it.remove(); 538 } 539 catch (UnsupportedOperationException usoex) 540 { 541 useIterator = false; 542 } 543 } 544 545 if (useIterator && containsKey(key)) 546 { 547 useIterator = false; 548 } 549 550 if (!useIterator) 551 { 552 // workaround for Iterators that do not remove the property 553 // on calling remove() or do not support remove() at all 554 clearProperty(key); 555 } 556 } 557 } 558 finally 559 { 560 setDetailEvents(true); 561 } 562 fireEvent(EVENT_CLEAR, null, null, false); 563 } 564 565 /** 566 * {@inheritDoc} This implementation returns keys that either match the 567 * prefix or start with the prefix followed by a dot ('.'). So the call 568 * {@code getKeys("db");} will find the keys {@code db}, 569 * {@code db.user}, or {@code db.password}, but not the key 570 * {@code dbdriver}. 571 */ 572 public Iterator<String> getKeys(String prefix) 573 { 574 return new PrefixedKeysIterator(getKeys(), prefix); 575 } 576 577 public Properties getProperties(String key) 578 { 579 return getProperties(key, null); 580 } 581 582 /** 583 * Get a list of properties associated with the given configuration key. 584 * 585 * @param key The configuration key. 586 * @param defaults Any default values for the returned 587 * {@code Properties} object. Ignored if {@code null}. 588 * 589 * @return The associated properties if key is found. 590 * 591 * @throws ConversionException is thrown if the key maps to an object that 592 * is not a String/List of Strings. 593 * 594 * @throws IllegalArgumentException if one of the tokens is malformed (does 595 * not contain an equals sign). 596 */ 597 public Properties getProperties(String key, Properties defaults) 598 { 599 /* 600 * Grab an array of the tokens for this key. 601 */ 602 String[] tokens = getStringArray(key); 603 604 /* 605 * Each token is of the form 'key=value'. 606 */ 607 Properties props = defaults == null ? new Properties() : new Properties(defaults); 608 for (String token : tokens) 609 { 610 int equalSign = token.indexOf('='); 611 if (equalSign > 0) 612 { 613 String pkey = token.substring(0, equalSign).trim(); 614 String pvalue = token.substring(equalSign + 1).trim(); 615 props.put(pkey, pvalue); 616 } 617 else if (tokens.length == 1 && "".equals(token)) 618 { 619 // Semantically equivalent to an empty Properties 620 // object. 621 break; 622 } 623 else 624 { 625 throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign"); 626 } 627 } 628 return props; 629 } 630 631 /** 632 * {@inheritDoc} 633 * @see PropertyConverter#toBoolean(Object) 634 */ 635 public boolean getBoolean(String key) 636 { 637 Boolean b = getBoolean(key, null); 638 if (b != null) 639 { 640 return b.booleanValue(); 641 } 642 else 643 { 644 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 645 } 646 } 647 648 /** 649 * {@inheritDoc} 650 * @see PropertyConverter#toBoolean(Object) 651 */ 652 public boolean getBoolean(String key, boolean defaultValue) 653 { 654 return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue)).booleanValue(); 655 } 656 657 /** 658 * Obtains the value of the specified key and tries to convert it into a 659 * {@code Boolean} object. If the property has no value, the passed 660 * in default value will be used. 661 * 662 * @param key the key of the property 663 * @param defaultValue the default value 664 * @return the value of this key converted to a {@code Boolean} 665 * @throws ConversionException if the value cannot be converted to a 666 * {@code Boolean} 667 * @see PropertyConverter#toBoolean(Object) 668 */ 669 public Boolean getBoolean(String key, Boolean defaultValue) 670 { 671 Object value = resolveContainerStore(key); 672 673 if (value == null) 674 { 675 return defaultValue; 676 } 677 else 678 { 679 try 680 { 681 return PropertyConverter.toBoolean(interpolate(value)); 682 } 683 catch (ConversionException e) 684 { 685 throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e); 686 } 687 } 688 } 689 690 public byte getByte(String key) 691 { 692 Byte b = getByte(key, null); 693 if (b != null) 694 { 695 return b.byteValue(); 696 } 697 else 698 { 699 throw new NoSuchElementException('\'' + key + " doesn't map to an existing object"); 700 } 701 } 702 703 public byte getByte(String key, byte defaultValue) 704 { 705 return getByte(key, new Byte(defaultValue)).byteValue(); 706 } 707 708 public Byte getByte(String key, Byte defaultValue) 709 { 710 Object value = resolveContainerStore(key); 711 712 if (value == null) 713 { 714 return defaultValue; 715 } 716 else 717 { 718 try 719 { 720 return PropertyConverter.toByte(interpolate(value)); 721 } 722 catch (ConversionException e) 723 { 724 throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e); 725 } 726 } 727 } 728 729 public double getDouble(String key) 730 { 731 Double d = getDouble(key, null); 732 if (d != null) 733 { 734 return d.doubleValue(); 735 } 736 else 737 { 738 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 739 } 740 } 741 742 public double getDouble(String key, double defaultValue) 743 { 744 return getDouble(key, new Double(defaultValue)).doubleValue(); 745 } 746 747 public Double getDouble(String key, Double defaultValue) 748 { 749 Object value = resolveContainerStore(key); 750 751 if (value == null) 752 { 753 return defaultValue; 754 } 755 else 756 { 757 try 758 { 759 return PropertyConverter.toDouble(interpolate(value)); 760 } 761 catch (ConversionException e) 762 { 763 throw new ConversionException('\'' + key + "' doesn't map to a Double object", e); 764 } 765 } 766 } 767 768 public float getFloat(String key) 769 { 770 Float f = getFloat(key, null); 771 if (f != null) 772 { 773 return f.floatValue(); 774 } 775 else 776 { 777 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 778 } 779 } 780 781 public float getFloat(String key, float defaultValue) 782 { 783 return getFloat(key, new Float(defaultValue)).floatValue(); 784 } 785 786 public Float getFloat(String key, Float defaultValue) 787 { 788 Object value = resolveContainerStore(key); 789 790 if (value == null) 791 { 792 return defaultValue; 793 } 794 else 795 { 796 try 797 { 798 return PropertyConverter.toFloat(interpolate(value)); 799 } 800 catch (ConversionException e) 801 { 802 throw new ConversionException('\'' + key + "' doesn't map to a Float object", e); 803 } 804 } 805 } 806 807 public int getInt(String key) 808 { 809 Integer i = getInteger(key, null); 810 if (i != null) 811 { 812 return i.intValue(); 813 } 814 else 815 { 816 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 817 } 818 } 819 820 public int getInt(String key, int defaultValue) 821 { 822 Integer i = getInteger(key, null); 823 824 if (i == null) 825 { 826 return defaultValue; 827 } 828 829 return i.intValue(); 830 } 831 832 public Integer getInteger(String key, Integer defaultValue) 833 { 834 Object value = resolveContainerStore(key); 835 836 if (value == null) 837 { 838 return defaultValue; 839 } 840 else 841 { 842 try 843 { 844 return PropertyConverter.toInteger(interpolate(value)); 845 } 846 catch (ConversionException e) 847 { 848 throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e); 849 } 850 } 851 } 852 853 public long getLong(String key) 854 { 855 Long l = getLong(key, null); 856 if (l != null) 857 { 858 return l.longValue(); 859 } 860 else 861 { 862 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 863 } 864 } 865 866 public long getLong(String key, long defaultValue) 867 { 868 return getLong(key, new Long(defaultValue)).longValue(); 869 } 870 871 public Long getLong(String key, Long defaultValue) 872 { 873 Object value = resolveContainerStore(key); 874 875 if (value == null) 876 { 877 return defaultValue; 878 } 879 else 880 { 881 try 882 { 883 return PropertyConverter.toLong(interpolate(value)); 884 } 885 catch (ConversionException e) 886 { 887 throw new ConversionException('\'' + key + "' doesn't map to a Long object", e); 888 } 889 } 890 } 891 892 public short getShort(String key) 893 { 894 Short s = getShort(key, null); 895 if (s != null) 896 { 897 return s.shortValue(); 898 } 899 else 900 { 901 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 902 } 903 } 904 905 public short getShort(String key, short defaultValue) 906 { 907 return getShort(key, new Short(defaultValue)).shortValue(); 908 } 909 910 public Short getShort(String key, Short defaultValue) 911 { 912 Object value = resolveContainerStore(key); 913 914 if (value == null) 915 { 916 return defaultValue; 917 } 918 else 919 { 920 try 921 { 922 return PropertyConverter.toShort(interpolate(value)); 923 } 924 catch (ConversionException e) 925 { 926 throw new ConversionException('\'' + key + "' doesn't map to a Short object", e); 927 } 928 } 929 } 930 931 /** 932 * {@inheritDoc} 933 * @see #setThrowExceptionOnMissing(boolean) 934 */ 935 public BigDecimal getBigDecimal(String key) 936 { 937 BigDecimal number = getBigDecimal(key, null); 938 if (number != null) 939 { 940 return number; 941 } 942 else if (isThrowExceptionOnMissing()) 943 { 944 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 945 } 946 else 947 { 948 return null; 949 } 950 } 951 952 public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) 953 { 954 Object value = resolveContainerStore(key); 955 956 if (value == null) 957 { 958 return defaultValue; 959 } 960 else 961 { 962 try 963 { 964 return PropertyConverter.toBigDecimal(interpolate(value)); 965 } 966 catch (ConversionException e) 967 { 968 throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e); 969 } 970 } 971 } 972 973 /** 974 * {@inheritDoc} 975 * @see #setThrowExceptionOnMissing(boolean) 976 */ 977 public BigInteger getBigInteger(String key) 978 { 979 BigInteger number = getBigInteger(key, null); 980 if (number != null) 981 { 982 return number; 983 } 984 else if (isThrowExceptionOnMissing()) 985 { 986 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 987 } 988 else 989 { 990 return null; 991 } 992 } 993 994 public BigInteger getBigInteger(String key, BigInteger defaultValue) 995 { 996 Object value = resolveContainerStore(key); 997 998 if (value == null) 999 { 1000 return defaultValue; 1001 } 1002 else 1003 { 1004 try 1005 { 1006 return PropertyConverter.toBigInteger(interpolate(value)); 1007 } 1008 catch (ConversionException e) 1009 { 1010 throw new ConversionException('\'' + key + "' doesn't map to a BigInteger object", e); 1011 } 1012 } 1013 } 1014 1015 /** 1016 * {@inheritDoc} 1017 * @see #setThrowExceptionOnMissing(boolean) 1018 */ 1019 public String getString(String key) 1020 { 1021 String s = getString(key, null); 1022 if (s != null) 1023 { 1024 return s; 1025 } 1026 else if (isThrowExceptionOnMissing()) 1027 { 1028 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); 1029 } 1030 else 1031 { 1032 return null; 1033 } 1034 } 1035 1036 public String getString(String key, String defaultValue) 1037 { 1038 Object value = resolveContainerStore(key); 1039 1040 if (value instanceof String) 1041 { 1042 return interpolate((String) value); 1043 } 1044 else if (value == null) 1045 { 1046 return interpolate(defaultValue); 1047 } 1048 else 1049 { 1050 throw new ConversionException('\'' + key + "' doesn't map to a String object"); 1051 } 1052 } 1053 1054 /** 1055 * Get an array of strings associated with the given configuration key. 1056 * If the key doesn't map to an existing object, an empty array is returned. 1057 * If a property is added to a configuration, it is checked whether it 1058 * contains multiple values. This is obvious if the added object is a list 1059 * or an array. For strings it is checked whether the string contains the 1060 * list delimiter character that can be specified using the 1061 * {@code setListDelimiter()} method. If this is the case, the string 1062 * is split at these positions resulting in a property with multiple 1063 * values. 1064 * 1065 * @param key The configuration key. 1066 * @return The associated string array if key is found. 1067 * 1068 * @throws ConversionException is thrown if the key maps to an 1069 * object that is not a String/List of Strings. 1070 * @see #setListDelimiter(char) 1071 * @see #setDelimiterParsingDisabled(boolean) 1072 */ 1073 public String[] getStringArray(String key) 1074 { 1075 Object value = getProperty(key); 1076 1077 String[] array; 1078 1079 if (value instanceof String) 1080 { 1081 array = new String[1]; 1082 1083 array[0] = interpolate((String) value); 1084 } 1085 else if (value instanceof List) 1086 { 1087 List<?> list = (List<?>) value; 1088 array = new String[list.size()]; 1089 1090 for (int i = 0; i < array.length; i++) 1091 { 1092 array[i] = interpolate(ObjectUtils.toString(list.get(i), null)); 1093 } 1094 } 1095 else if (value == null) 1096 { 1097 array = new String[0]; 1098 } 1099 else if (isScalarValue(value)) 1100 { 1101 array = new String[1]; 1102 array[0] = value.toString(); 1103 } 1104 else 1105 { 1106 throw new ConversionException('\'' + key + "' doesn't map to a String/List object"); 1107 } 1108 return array; 1109 } 1110 1111 /** 1112 * {@inheritDoc} 1113 * @see #getStringArray(String) 1114 */ 1115 public List<Object> getList(String key) 1116 { 1117 return getList(key, new ArrayList<Object>()); 1118 } 1119 1120 public List<Object> getList(String key, List<Object> defaultValue) 1121 { 1122 Object value = getProperty(key); 1123 List<Object> list; 1124 1125 if (value instanceof String) 1126 { 1127 list = new ArrayList<Object>(1); 1128 list.add(interpolate((String) value)); 1129 } 1130 else if (value instanceof List) 1131 { 1132 list = new ArrayList<Object>(); 1133 List<?> l = (List<?>) value; 1134 1135 // add the interpolated elements in the new list 1136 for (Object elem : l) 1137 { 1138 list.add(interpolate(elem)); 1139 } 1140 } 1141 else if (value == null) 1142 { 1143 list = defaultValue; 1144 } 1145 else if (value.getClass().isArray()) 1146 { 1147 return Arrays.asList((Object[]) value); 1148 } 1149 else if (isScalarValue(value)) 1150 { 1151 return Collections.singletonList((Object) value.toString()); 1152 } 1153 else 1154 { 1155 throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a " 1156 + value.getClass().getName()); 1157 } 1158 return list; 1159 } 1160 1161 /** 1162 * Returns an object from the store described by the key. If the value is a 1163 * Collection object, replace it with the first object in the collection. 1164 * 1165 * @param key The property key. 1166 * 1167 * @return value Value, transparently resolving a possible collection dependency. 1168 */ 1169 protected Object resolveContainerStore(String key) 1170 { 1171 Object value = getProperty(key); 1172 if (value != null) 1173 { 1174 if (value instanceof Collection) 1175 { 1176 Collection<?> collection = (Collection<?>) value; 1177 value = collection.isEmpty() ? null : collection.iterator().next(); 1178 } 1179 else if (value.getClass().isArray() && Array.getLength(value) > 0) 1180 { 1181 value = Array.get(value, 0); 1182 } 1183 } 1184 1185 return value; 1186 } 1187 1188 /** 1189 * Checks whether the specified object is a scalar value. This method is 1190 * called by {@code getList()} and {@code getStringArray()} if the 1191 * property requested is not a string, a list, or an array. If it returns 1192 * <b>true</b>, the calling method transforms the value to a string and 1193 * returns a list or an array with this single element. This implementation 1194 * returns <b>true</b> if the value is of a wrapper type for a primitive 1195 * type. 1196 * 1197 * @param value the value to be checked 1198 * @return a flag whether the value is a scalar 1199 * @since 1.7 1200 */ 1201 protected boolean isScalarValue(Object value) 1202 { 1203 return ClassUtils.wrapperToPrimitive(value.getClass()) != null; 1204 } 1205 1206 /** 1207 * Copies the content of the specified configuration into this 1208 * configuration. If the specified configuration contains a key that is also 1209 * present in this configuration, the value of this key will be replaced by 1210 * the new value. <em>Note:</em> This method won't work well when copying 1211 * hierarchical configurations because it is not able to copy information 1212 * about the properties' structure (i.e. the parent-child-relationships will 1213 * get lost). So when dealing with hierarchical configuration objects their 1214 * {@link HierarchicalConfiguration#clone() clone()} methods 1215 * should be used. 1216 * 1217 * @param c the configuration to copy (can be <b>null</b>, then this 1218 * operation will have no effect) 1219 * @since 1.5 1220 */ 1221 public void copy(Configuration c) 1222 { 1223 if (c != null) 1224 { 1225 for (Iterator<String> it = c.getKeys(); it.hasNext();) 1226 { 1227 String key = it.next(); 1228 Object value = c.getProperty(key); 1229 fireEvent(EVENT_SET_PROPERTY, key, value, true); 1230 setDetailEvents(false); 1231 try 1232 { 1233 clearProperty(key); 1234 addPropertyValues(key, value, DISABLED_DELIMITER); 1235 } 1236 finally 1237 { 1238 setDetailEvents(true); 1239 } 1240 fireEvent(EVENT_SET_PROPERTY, key, value, false); 1241 } 1242 } 1243 } 1244 1245 /** 1246 * Appends the content of the specified configuration to this configuration. 1247 * The values of all properties contained in the specified configuration 1248 * will be appended to this configuration. So if a property is already 1249 * present in this configuration, its new value will be a union of the 1250 * values in both configurations. <em>Note:</em> This method won't work 1251 * well when appending hierarchical configurations because it is not able to 1252 * copy information about the properties' structure (i.e. the 1253 * parent-child-relationships will get lost). So when dealing with 1254 * hierarchical configuration objects their 1255 * {@link HierarchicalConfiguration#clone() clone()} methods 1256 * should be used. 1257 * 1258 * @param c the configuration to be appended (can be <b>null</b>, then this 1259 * operation will have no effect) 1260 * @since 1.5 1261 */ 1262 public void append(Configuration c) 1263 { 1264 if (c != null) 1265 { 1266 for (Iterator<String> it = c.getKeys(); it.hasNext();) 1267 { 1268 String key = it.next(); 1269 Object value = c.getProperty(key); 1270 fireEvent(EVENT_ADD_PROPERTY, key, value, true); 1271 addPropertyValues(key, value, DISABLED_DELIMITER); 1272 fireEvent(EVENT_ADD_PROPERTY, key, value, false); 1273 } 1274 } 1275 } 1276 1277 /** 1278 * Returns a configuration with the same content as this configuration, but 1279 * with all variables replaced by their actual values. This method tries to 1280 * clone the configuration and then perform interpolation on all properties. 1281 * So property values of the form <code>${var}</code> will be resolved as 1282 * far as possible (if a variable cannot be resolved, it remains unchanged). 1283 * This operation is useful if the content of a configuration is to be 1284 * exported or processed by an external component that does not support 1285 * variable interpolation. 1286 * 1287 * @return a configuration with all variables interpolated 1288 * @throws ConfigurationRuntimeException if this configuration cannot be 1289 * cloned 1290 * @since 1.5 1291 */ 1292 public Configuration interpolatedConfiguration() 1293 { 1294 // first clone this configuration 1295 AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils 1296 .cloneConfiguration(this); 1297 1298 // now perform interpolation 1299 c.setDelimiterParsingDisabled(true); 1300 for (Iterator<String> it = getKeys(); it.hasNext();) 1301 { 1302 String key = it.next(); 1303 c.setProperty(key, getList(key)); 1304 } 1305 1306 c.setDelimiterParsingDisabled(isDelimiterParsingDisabled()); 1307 return c; 1308 } 1309 }