View Javadoc

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