Coverage Report - org.apache.commons.configuration.AbstractConfiguration
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractConfiguration
96%
261/273
100%
56/56
2,706
AbstractConfiguration$1
100%
3/3
100%
1/1
2,706
AbstractConfiguration$2
33%
1/3
N/A
2,706
AbstractConfiguration$3
100%
3/3
100%
1/1
2,706
 
 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.Arrays;
 25  
 import java.util.Collection;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 import java.util.NoSuchElementException;
 29  
 import java.util.Properties;
 30  
 
 31  
 import org.apache.commons.collections.Predicate;
 32  
 import org.apache.commons.collections.iterators.FilterIterator;
 33  
 import org.apache.commons.configuration.event.ConfigurationErrorEvent;
 34  
 import org.apache.commons.configuration.event.ConfigurationErrorListener;
 35  
 import org.apache.commons.configuration.event.EventSource;
 36  
 import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
 37  
 import org.apache.commons.lang.BooleanUtils;
 38  
 import org.apache.commons.lang.text.StrLookup;
 39  
 import org.apache.commons.lang.text.StrSubstitutor;
 40  
 import org.apache.commons.logging.Log;
 41  
 import org.apache.commons.logging.impl.NoOpLog;
 42  
 
 43  
 /**
 44  
  * <p>Abstract configuration class. Provides basic functionality but does not
 45  
  * store any data.</p>
 46  
  * <p>If you want to write your own Configuration class then you should
 47  
  * implement only abstract methods from this class. A lot of functionality
 48  
  * needed by typical implementations of the <code>Configuration</code>
 49  
  * interface is already provided by this base class. Following is a list of
 50  
  * features implemented here:
 51  
  * <ul><li>Data conversion support. The various data types required by the
 52  
  * <code>Configuration</code> interface are already handled by this base class.
 53  
  * A concrete sub class only needs to provide a generic <code>getProperty()</code>
 54  
  * method.</li>
 55  
  * <li>Support for variable interpolation. Property values containing special
 56  
  * variable tokens (like <code>${var}</code>) will be replaced by their
 57  
  * corresponding values.</li>
 58  
  * <li>Support for string lists. The values of properties to be added to this
 59  
  * configuration are checked whether they contain a list delimiter character. If
 60  
  * this is the case and if list splitting is enabled, the string is split and
 61  
  * multiple values are added for this property. (With the
 62  
  * <code>setListDelimiter()</code> method the delimiter character can be
 63  
  * specified; per default a comma is used. The
 64  
  * <code>setDelimiterParsingDisabled()</code> method can be used to disable
 65  
  * list splitting completely.)</li>
 66  
  * <li>Allows to specify how missing properties are treated. Per default the
 67  
  * get methods returning an object will return <b>null</b> if the searched
 68  
  * property key is not found (and no default value is provided). With the
 69  
  * <code>setThrowExceptionOnMissing()</code> method this behavior can be
 70  
  * changed to throw an exception when a requested property cannot be found.</li>
 71  
  * <li>Basic event support. Whenever this configuration is modified registered
 72  
  * event listeners are notified. Refer to the various <code>EVENT_XXX</code>
 73  
  * constants to get an impression about which event types are supported.</li>
 74  
  * </ul></p>
 75  
  *
 76  
  * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov </a>
 77  
  * @author Oliver Heger
 78  
  * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen </a>
 79  
  * @version $Id: AbstractConfiguration.java 652316 2008-04-30 10:59:58Z ebourg $
 80  
  */
 81  
 public abstract class AbstractConfiguration extends EventSource implements Configuration
 82  
 {
 83  
     /**
 84  
      * Constant for the add property event type.
 85  
      * @since 1.3
 86  
      */
 87  
     public static final int EVENT_ADD_PROPERTY = 1;
 88  
 
 89  
     /**
 90  
      * Constant for the clear property event type.
 91  
      * @since 1.3
 92  
      */
 93  
     public static final int EVENT_CLEAR_PROPERTY = 2;
 94  
 
 95  
     /**
 96  
      * Constant for the set property event type.
 97  
      * @since 1.3
 98  
      */
 99  
     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  66
     private static char defaultListDelimiter = ',';
 129  
 
 130  
     /** Delimiter used to convert single values to lists */
 131  3725
     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  3725
     {
 156  3725
         setLogger(null);
 157  3725
     }
 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  2
         AbstractConfiguration.defaultListDelimiter = delimiter;
 170  2
     }
 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  0
         setDefaultListDelimiter(delimiter);
 182  0
     }
 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  63
         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  0
         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  498
         this.listDelimiter = listDelimiter;
 217  498
     }
 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  167698
         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  159850
         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  668
         this.delimiterParsingDisabled = delimiterParsingDisabled;
 254  668
     }
 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  968
         this.throwExceptionOnMissing = throwExceptionOnMissing;
 270  968
     }
 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  995
         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  8653
         if (substitutor == null)
 291  
         {
 292  721
             substitutor = new StrSubstitutor(createInterpolator());
 293  
         }
 294  8653
         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  3
         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  721
         ConfigurationInterpolator interpol = new ConfigurationInterpolator();
 329  721
         interpol.setDefaultLookup(new StrLookup()
 330  
         {
 331  721
             public String lookup(String var)
 332  
             {
 333  2377
                 Object prop = resolveContainerStore(var);
 334  2377
                 return (prop != null) ? prop.toString() : null;
 335  
             }
 336  
         });
 337  721
         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  2127
         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  5218
         this.log = (log != null) ? log : new NoOpLog();
 364  5218
     }
 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  1487
         addErrorListener(new ConfigurationErrorListener()
 379  
         {
 380  1487
             public void configurationError(ConfigurationErrorEvent event)
 381  
             {
 382  0
                 getLogger().warn("Internal error", event.getCause());
 383  0
             }
 384  
         });
 385  1487
     }
 386  
 
 387  
     public void addProperty(String key, Object value)
 388  
     {
 389  131929
         fireEvent(EVENT_ADD_PROPERTY, key, value, true);
 390  131929
         addPropertyValues(key, value,
 391  
                 isDelimiterParsingDisabled() ? DISABLED_DELIMITER
 392  
                         : getListDelimiter());
 393  131926
         fireEvent(EVENT_ADD_PROPERTY, key, value, false);
 394  131926
     }
 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  134445
         Iterator it = PropertyConverter.toIterator(value, delimiter);
 418  278427
         while (it.hasNext())
 419  
         {
 420  143985
             addPropertyDirect(key, it.next());
 421  
         }
 422  134442
     }
 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  5550
         Object result = interpolate((Object) base);
 434  5547
         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  9514
         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  0
         return base; // just a dummy implementation
 468  
     }
 469  
 
 470  
     public Configuration subset(String prefix)
 471  
     {
 472  45
         return new SubsetConfiguration(this, prefix, ".");
 473  
     }
 474  
 
 475  
     public void setProperty(String key, Object value)
 476  
     {
 477  837
         fireEvent(EVENT_SET_PROPERTY, key, value, true);
 478  837
         setDetailEvents(false);
 479  
         try
 480  
         {
 481  837
             clearProperty(key);
 482  836
             addProperty(key, value);
 483  
         }
 484  
         finally
 485  
         {
 486  837
             setDetailEvents(true);
 487  836
         }
 488  836
         fireEvent(EVENT_SET_PROPERTY, key, value, false);
 489  836
     }
 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  958
         fireEvent(EVENT_CLEAR_PROPERTY, key, null, true);
 501  958
         clearPropertyDirect(key);
 502  958
         fireEvent(EVENT_CLEAR_PROPERTY, key, null, false);
 503  958
     }
 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  0
     }
 517  
 
 518  
     public void clear()
 519  
     {
 520  140
         fireEvent(EVENT_CLEAR, null, null, true);
 521  140
         setDetailEvents(false);
 522  140
         boolean useIterator = true;
 523  
         try
 524  
         {
 525  140
             Iterator it = getKeys();
 526  4493
             while (it.hasNext())
 527  
             {
 528  4353
                 String key = (String) it.next();
 529  4353
                 if (useIterator)
 530  
                 {
 531  
                     try
 532  
                     {
 533  140
                         it.remove();
 534  
                     }
 535  1
                     catch (UnsupportedOperationException usoex)
 536  
                     {
 537  1
                         useIterator = false;
 538  139
                     }
 539  
                 }
 540  
 
 541  4353
                 if (useIterator && containsKey(key))
 542  
                 {
 543  136
                     useIterator = false;
 544  
                 }
 545  
 
 546  4353
                 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  4350
                     clearProperty(key);
 551  
                 }
 552  
             }
 553  
         }
 554  
         finally
 555  
         {
 556  140
             setDetailEvents(true);
 557  140
         }
 558  140
         fireEvent(EVENT_CLEAR, null, null, false);
 559  140
     }
 560  
 
 561  
     public Iterator getKeys(final String prefix)
 562  
     {
 563  41
         return new FilterIterator(getKeys(), new Predicate()
 564  
         {
 565  41
             public boolean evaluate(Object obj)
 566  
             {
 567  466
                 String key = (String) obj;
 568  466
                 return key.startsWith(prefix + ".") || key.equals(prefix);
 569  
             }
 570  
         });
 571  
     }
 572  
 
 573  
     public Properties getProperties(String key)
 574  
     {
 575  4
         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  4
         String[] tokens = getStringArray(key);
 599  
 
 600  
         /*
 601  
          * Each token is of the form 'key=value'.
 602  
          */
 603  4
         Properties props = defaults == null ? new Properties() : new Properties(defaults);
 604  10
         for (int i = 0; i < tokens.length; i++)
 605  
         {
 606  8
             String token = tokens[i];
 607  8
             int equalSign = token.indexOf('=');
 608  8
             if (equalSign > 0)
 609  
             {
 610  6
                 String pkey = token.substring(0, equalSign).trim();
 611  6
                 String pvalue = token.substring(equalSign + 1).trim();
 612  6
                 props.put(pkey, pvalue);
 613  
             }
 614  2
             else if (tokens.length == 1 && "".equals(token))
 615  
             {
 616  
                 // Semantically equivalent to an empty Properties
 617  
                 // object.
 618  2
                 break;
 619  
             }
 620  
             else
 621  
             {
 622  0
                 throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign");
 623  
             }
 624  
         }
 625  4
         return props;
 626  
     }
 627  
 
 628  
     /**
 629  
      * {@inheritDoc}
 630  
      * @see PropertyConverter#toBoolean(Object)
 631  
      */
 632  
     public boolean getBoolean(String key)
 633  
     {
 634  1071
         Boolean b = getBoolean(key, null);
 635  1069
         if (b != null)
 636  
         {
 637  1067
             return b.booleanValue();
 638  
         }
 639  
         else
 640  
         {
 641  2
             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  24
         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  1131
         Object value = resolveContainerStore(key);
 669  
 
 670  1131
         if (value == null)
 671  
         {
 672  37
             return defaultValue;
 673  
         }
 674  
         else
 675  
         {
 676  
             try
 677  
             {
 678  1094
                 return PropertyConverter.toBoolean(interpolate(value));
 679  
             }
 680  3
             catch (ConversionException e)
 681  
             {
 682  3
                 throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e);
 683  
             }
 684  
         }
 685  
     }
 686  
 
 687  
     public byte getByte(String key)
 688  
     {
 689  15
         Byte b = getByte(key, null);
 690  13
         if (b != null)
 691  
         {
 692  11
             return b.byteValue();
 693  
         }
 694  
         else
 695  
         {
 696  2
             throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
 697  
         }
 698  
     }
 699  
 
 700  
     public byte getByte(String key, byte defaultValue)
 701  
     {
 702  4
         return getByte(key, new Byte(defaultValue)).byteValue();
 703  
     }
 704  
 
 705  
     public Byte getByte(String key, Byte defaultValue)
 706  
     {
 707  22
         Object value = resolveContainerStore(key);
 708  
 
 709  22
         if (value == null)
 710  
         {
 711  4
             return defaultValue;
 712  
         }
 713  
         else
 714  
         {
 715  
             try
 716  
             {
 717  18
                 return PropertyConverter.toByte(interpolate(value));
 718  
             }
 719  2
             catch (ConversionException e)
 720  
             {
 721  2
                 throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e);
 722  
             }
 723  
         }
 724  
     }
 725  
 
 726  
     public double getDouble(String key)
 727  
     {
 728  22
         Double d = getDouble(key, null);
 729  20
         if (d != null)
 730  
         {
 731  18
             return d.doubleValue();
 732  
         }
 733  
         else
 734  
         {
 735  2
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 736  
         }
 737  
     }
 738  
 
 739  
     public double getDouble(String key, double defaultValue)
 740  
     {
 741  11
         return getDouble(key, new Double(defaultValue)).doubleValue();
 742  
     }
 743  
 
 744  
     public Double getDouble(String key, Double defaultValue)
 745  
     {
 746  36
         Object value = resolveContainerStore(key);
 747  
 
 748  36
         if (value == null)
 749  
         {
 750  11
             return defaultValue;
 751  
         }
 752  
         else
 753  
         {
 754  
             try
 755  
             {
 756  25
                 return PropertyConverter.toDouble(interpolate(value));
 757  
             }
 758  2
             catch (ConversionException e)
 759  
             {
 760  2
                 throw new ConversionException('\'' + key + "' doesn't map to a Double object", e);
 761  
             }
 762  
         }
 763  
     }
 764  
 
 765  
     public float getFloat(String key)
 766  
     {
 767  14
         Float f = getFloat(key, null);
 768  12
         if (f != null)
 769  
         {
 770  10
             return f.floatValue();
 771  
         }
 772  
         else
 773  
         {
 774  2
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 775  
         }
 776  
     }
 777  
 
 778  
     public float getFloat(String key, float defaultValue)
 779  
     {
 780  7
         return getFloat(key, new Float(defaultValue)).floatValue();
 781  
     }
 782  
 
 783  
     public Float getFloat(String key, Float defaultValue)
 784  
     {
 785  24
         Object value = resolveContainerStore(key);
 786  
 
 787  24
         if (value == null)
 788  
         {
 789  7
             return defaultValue;
 790  
         }
 791  
         else
 792  
         {
 793  
             try
 794  
             {
 795  17
                 return PropertyConverter.toFloat(interpolate(value));
 796  
             }
 797  2
             catch (ConversionException e)
 798  
             {
 799  2
                 throw new ConversionException('\'' + key + "' doesn't map to a Float object", e);
 800  
             }
 801  
         }
 802  
     }
 803  
 
 804  
     public int getInt(String key)
 805  
     {
 806  1076
         Integer i = getInteger(key, null);
 807  1076
         if (i != null)
 808  
         {
 809  1076
             return i.intValue();
 810  
         }
 811  
         else
 812  
         {
 813  0
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 814  
         }
 815  
     }
 816  
 
 817  
     public int getInt(String key, int defaultValue)
 818  
     {
 819  3
         Integer i = getInteger(key, null);
 820  
 
 821  3
         if (i == null)
 822  
         {
 823  3
             return defaultValue;
 824  
         }
 825  
 
 826  0
         return i.intValue();
 827  
     }
 828  
 
 829  
     public Integer getInteger(String key, Integer defaultValue)
 830  
     {
 831  1080
         Object value = resolveContainerStore(key);
 832  
 
 833  1080
         if (value == null)
 834  
         {
 835  3
             return defaultValue;
 836  
         }
 837  
         else
 838  
         {
 839  
             try
 840  
             {
 841  1077
                 return PropertyConverter.toInteger(interpolate(value));
 842  
             }
 843  0
             catch (ConversionException e)
 844  
             {
 845  0
                 throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e);
 846  
             }
 847  
         }
 848  
     }
 849  
 
 850  
     public long getLong(String key)
 851  
     {
 852  16
         Long l = getLong(key, null);
 853  14
         if (l != null)
 854  
         {
 855  12
             return l.longValue();
 856  
         }
 857  
         else
 858  
         {
 859  2
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 860  
         }
 861  
     }
 862  
 
 863  
     public long getLong(String key, long defaultValue)
 864  
     {
 865  7
         return getLong(key, new Long(defaultValue)).longValue();
 866  
     }
 867  
 
 868  
     public Long getLong(String key, Long defaultValue)
 869  
     {
 870  27
         Object value = resolveContainerStore(key);
 871  
 
 872  27
         if (value == null)
 873  
         {
 874  7
             return defaultValue;
 875  
         }
 876  
         else
 877  
         {
 878  
             try
 879  
             {
 880  20
                 return PropertyConverter.toLong(interpolate(value));
 881  
             }
 882  2
             catch (ConversionException e)
 883  
             {
 884  2
                 throw new ConversionException('\'' + key + "' doesn't map to a Long object", e);
 885  
             }
 886  
         }
 887  
     }
 888  
 
 889  
     public short getShort(String key)
 890  
     {
 891  17
         Short s = getShort(key, null);
 892  15
         if (s != null)
 893  
         {
 894  13
             return s.shortValue();
 895  
         }
 896  
         else
 897  
         {
 898  2
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 899  
         }
 900  
     }
 901  
 
 902  
     public short getShort(String key, short defaultValue)
 903  
     {
 904  7
         return getShort(key, new Short(defaultValue)).shortValue();
 905  
     }
 906  
 
 907  
     public Short getShort(String key, Short defaultValue)
 908  
     {
 909  31
         Object value = resolveContainerStore(key);
 910  
 
 911  31
         if (value == null)
 912  
         {
 913  9
             return defaultValue;
 914  
         }
 915  
         else
 916  
         {
 917  
             try
 918  
             {
 919  22
                 return PropertyConverter.toShort(interpolate(value));
 920  
             }
 921  2
             catch (ConversionException e)
 922  
             {
 923  2
                 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  7
         BigDecimal number = getBigDecimal(key, null);
 935  5
         if (number != null)
 936  
         {
 937  3
             return number;
 938  
         }
 939  2
         else if (isThrowExceptionOnMissing())
 940  
         {
 941  1
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 942  
         }
 943  
         else
 944  
         {
 945  1
             return null;
 946  
         }
 947  
     }
 948  
 
 949  
     public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
 950  
     {
 951  12
         Object value = resolveContainerStore(key);
 952  
 
 953  12
         if (value == null)
 954  
         {
 955  4
             return defaultValue;
 956  
         }
 957  
         else
 958  
         {
 959  
             try
 960  
             {
 961  8
                 return PropertyConverter.toBigDecimal(interpolate(value));
 962  
             }
 963  2
             catch (ConversionException e)
 964  
             {
 965  2
                 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  8
         BigInteger number = getBigInteger(key, null);
 977  6
         if (number != null)
 978  
         {
 979  4
             return number;
 980  
         }
 981  2
         else if (isThrowExceptionOnMissing())
 982  
         {
 983  1
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 984  
         }
 985  
         else
 986  
         {
 987  1
             return null;
 988  
         }
 989  
     }
 990  
 
 991  
     public BigInteger getBigInteger(String key, BigInteger defaultValue)
 992  
     {
 993  13
         Object value = resolveContainerStore(key);
 994  
 
 995  13
         if (value == null)
 996  
         {
 997  4
             return defaultValue;
 998  
         }
 999  
         else
 1000  
         {
 1001  
             try
 1002  
             {
 1003  9
                 return PropertyConverter.toBigInteger(interpolate(value));
 1004  
             }
 1005  2
             catch (ConversionException e)
 1006  
             {
 1007  2
                 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  1007
         String s = getString(key, null);
 1019  1004
         if (s != null)
 1020  
         {
 1021  667
             return s;
 1022  
         }
 1023  337
         else if (isThrowExceptionOnMissing())
 1024  
         {
 1025  7
             throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
 1026  
         }
 1027  
         else
 1028  
         {
 1029  330
             return null;
 1030  
         }
 1031  
     }
 1032  
 
 1033  
     public String getString(String key, String defaultValue)
 1034  
     {
 1035  1087
         Object value = resolveContainerStore(key);
 1036  
 
 1037  1087
         if (value instanceof String)
 1038  
         {
 1039  742
             return interpolate((String) value);
 1040  
         }
 1041  345
         else if (value == null)
 1042  
         {
 1043  345
             return interpolate(defaultValue);
 1044  
         }
 1045  
         else
 1046  
         {
 1047  0
             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  22
         Object value = getProperty(key);
 1073  
 
 1074  
         String[] array;
 1075  
 
 1076  22
         if (value instanceof String)
 1077  
         {
 1078  12
             array = new String[1];
 1079  
 
 1080  12
             array[0] = interpolate((String) value);
 1081  
         }
 1082  10
         else if (value instanceof List)
 1083  
         {
 1084  8
             List list = (List) value;
 1085  8
             array = new String[list.size()];
 1086  
 
 1087  30
             for (int i = 0; i < array.length; i++)
 1088  
             {
 1089  22
                 array[i] = interpolate((String) list.get(i));
 1090  
             }
 1091  
         }
 1092  2
         else if (value == null)
 1093  
         {
 1094  2
             array = new String[0];
 1095  
         }
 1096  
         else
 1097  
         {
 1098  0
             throw new ConversionException('\'' + key + "' doesn't map to a String/List object");
 1099  
         }
 1100  22
         return array;
 1101  
     }
 1102  
 
 1103  
     /**
 1104  
      * {@inheritDoc}
 1105  
      * @see #getStringArray(String)
 1106  
      */
 1107  
     public List getList(String key)
 1108  
     {
 1109  260
         return getList(key, new ArrayList());
 1110  
     }
 1111  
 
 1112  
     public List getList(String key, List defaultValue)
 1113  
     {
 1114  215
         Object value = getProperty(key);
 1115  
         List list;
 1116  
 
 1117  215
         if (value instanceof String)
 1118  
         {
 1119  30
             list = new ArrayList(1);
 1120  30
             list.add(interpolate((String) value));
 1121  
         }
 1122  185
         else if (value instanceof List)
 1123  
         {
 1124  149
             list = new ArrayList();
 1125  149
             List l = (List) value;
 1126  
 
 1127  
             // add the interpolated elements in the new list
 1128  149
             Iterator it = l.iterator();
 1129  745
             while (it.hasNext())
 1130  
             {
 1131  596
                 list.add(interpolate(it.next()));
 1132  
             }
 1133  
         }
 1134  36
         else if (value == null)
 1135  
         {
 1136  32
             list = defaultValue;
 1137  
         }
 1138  4
         else if (value.getClass().isArray())
 1139  
         {
 1140  2
             return Arrays.asList((Object[]) value);
 1141  
         }
 1142  
         else
 1143  
         {
 1144  2
             throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a "
 1145  
                     + value.getClass().getName());
 1146  
         }
 1147  211
         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  5894
         Object value = getProperty(key);
 1161  5894
         if (value != null)
 1162  
         {
 1163  5430
             if (value instanceof Collection)
 1164  
             {
 1165  16
                 Collection collection = (Collection) value;
 1166  16
                 value = collection.isEmpty() ? null : collection.iterator().next();
 1167  
             }
 1168  5414
             else if (value.getClass().isArray() && Array.getLength(value) > 0)
 1169  
             {
 1170  8
                 value = Array.get(value, 0);
 1171  
             }
 1172  
         }
 1173  
 
 1174  5894
         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  5
         if (c != null)
 1195  
         {
 1196  4
             for (Iterator it = c.getKeys(); it.hasNext();)
 1197  
             {
 1198  49
                 String key = (String) it.next();
 1199  49
                 Object value = c.getProperty(key);
 1200  49
                 fireEvent(EVENT_SET_PROPERTY, key, value, true);
 1201  49
                 setDetailEvents(false);
 1202  
                 try
 1203  
                 {
 1204  49
                     clearProperty(key);
 1205  49
                     addPropertyValues(key, value, DISABLED_DELIMITER);
 1206  
                 }
 1207  
                 finally
 1208  
                 {
 1209  49
                     setDetailEvents(true);
 1210  49
                 }
 1211  49
                 fireEvent(EVENT_SET_PROPERTY, key, value, false);
 1212  
             }
 1213  
         }
 1214  5
     }
 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  84
         if (c != null)
 1236  
         {
 1237  83
             for (Iterator it = c.getKeys(); it.hasNext();)
 1238  
             {
 1239  2467
                 String key = (String) it.next();
 1240  2467
                 Object value = c.getProperty(key);
 1241  2467
                 fireEvent(EVENT_ADD_PROPERTY, key, value, true);
 1242  2467
                 addPropertyValues(key, value, DISABLED_DELIMITER);
 1243  2467
                 fireEvent(EVENT_ADD_PROPERTY, key, value, false);
 1244  
             }
 1245  
         }
 1246  84
     }
 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  1
         AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils
 1267  
                 .cloneConfiguration(this);
 1268  
 
 1269  
         // now perform interpolation
 1270  1
         c.setDelimiterParsingDisabled(true);
 1271  1
         for (Iterator it = getKeys(); it.hasNext();)
 1272  
         {
 1273  8
             String key = (String) it.next();
 1274  8
             c.setProperty(key, getList(key));
 1275  
         }
 1276  
 
 1277  1
         c.setDelimiterParsingDisabled(isDelimiterParsingDisabled());
 1278  1
         return c;
 1279  
     }
 1280  
 }