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