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