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