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