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