View Javadoc

    1   /*
    2    * Copyright 2001-2004 The Apache Software Foundation.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License")
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16
    17  package org.apache.commons.configuration;
    18
    19  import java.math.BigDecimal;
    20  import java.math.BigInteger;
    21  import java.util.ArrayList;
    22  import java.util.Collection;
    23  import java.util.Iterator;
    24  import java.util.List;
    25  import java.util.NoSuchElementException;
    26  import java.util.Properties;
    27  import java.util.StringTokenizer;
    28  import java.util.Vector;
    29
    30  import org.apache.commons.collections.Predicate;
    31  import org.apache.commons.collections.iterators.FilterIterator;
    32  import org.apache.commons.lang.BooleanUtils;
    33
    34  /***
    35   * Abstract configuration class. Provide basic functionality but does not
    36   * store any data. If you want to write your own Configuration class
    37   * then you should implement only abstract methods from this class.
    38   *
    39   * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov</a>
    40   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
    41   * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
    42   * @version $Id: AbstractConfiguration.java,v 1.25 2004/10/05 21:17:25 ebourg Exp $
    43   */
    44  public abstract class AbstractConfiguration implements Configuration
    45  {
    46      /*** start token */
    47      protected static final String START_TOKEN = "${";
    48
    49      /*** end token */
    50      protected static final String END_TOKEN = "}";
    51
    52      /*** The property delimiter used while parsing (a comma). */
    53      private static char DELIMITER = ',';
    54
    55      /*** how big the initial arraylist for splitting up name value pairs */
    56      private static final int INITIAL_LIST_SIZE = 2;
    57
    58      /***
    59       * Whether the configuration should throw NoSuchElementExceptions or simply
    60       * return null when a property does not exist. Defaults to return null.
    61       */
    62      private boolean throwExceptionOnMissing = false;
    63
    64      /***
    65       * For configurations extending AbstractConfiguration, allow them to
    66       * change the delimiter from the default comma (",").
    67       *
    68       * @param delimiter The new delimiter
    69       */
    70      public static void setDelimiter(char delimiter)
    71      {
    72          AbstractConfiguration.DELIMITER = delimiter;
    73      }
    74
    75      /***
    76       * Retrieve the current delimiter.  By default this is a comma (",").
    77       *
    78       * @return The delimiter in use
    79       */
    80      public static char getDelimiter()
    81      {
    82          return AbstractConfiguration.DELIMITER;
    83      }
    84
    85      /***
    86       * If set to false, missing elements return null if possible (for objects).
    87       *
    88       * @param throwExceptionOnMissing The new value for the property
    89       */
    90      public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
    91      {
    92          this.throwExceptionOnMissing = throwExceptionOnMissing;
    93      }
    94
    95      /***
    96       * Returns true if missing values throw Exceptions.
    97       *
    98       * @return true if missing values throw Exceptions
    99       */
    100     public boolean isThrowExceptionOnMissing()
    101     {
    102         return throwExceptionOnMissing;
    103     }
    104
    105
    106     /***
    107      * {@inheritDoc}
    108      */
    109     public void addProperty(String key, Object token)
    110     {
    111         if (token instanceof String)
    112         {
    113             Iterator it = split((String) token).iterator();
    114             while (it.hasNext())
    115             {
    116                 addPropertyDirect(key, it.next());
    117             }
    118         }
    119         else if (token instanceof Collection)
    120         {
    121             Iterator it = ((Collection) token).iterator();
    122             while (it.hasNext())
    123             {
    124                 addProperty(key, it.next());
    125             }
    126         }
    127         else
    128         {
    129             addPropertyDirect(key, token);
    130         }
    131     }
    132
    133     /***
    134      * Read property. Should return <code>null</code> if the key doesn't
    135      * map to an existing object.
    136      *
    137      * @param key key to use for mapping
    138      *
    139      * @return object associated with the given configuration key.
    140      */
    141     protected abstract Object getPropertyDirect(String key);
    142
    143     /***
    144      * Adds a key/value pair to the Configuration. Override this method to
    145      * provide write acces to underlying Configuration store.
    146      *
    147      * @param key key to use for mapping
    148      * @param obj object to store
    149      */
    150     protected abstract void addPropertyDirect(String key, Object obj);
    151
    152     /***
    153      * interpolate key names to handle ${key} stuff
    154      *
    155      * @param base string to interpolate
    156      *
    157      * @return returns the key name with the ${key} substituted
    158      */
    159     protected String interpolate(String base)
    160     {
    161         return interpolateHelper(base, null);
    162     }
    163
    164     /***
    165      * Recursive handler for multple levels of interpolation.
    166      *
    167      * When called the first time, priorVariables should be null.
    168      *
    169      * @param base string with the ${key} variables
    170      * @param priorVariables serves two purposes: to allow checking for
    171      * loops, and creating a meaningful exception message should a loop
    172      * occur.  It's 0'th element will be set to the value of base from
    173      * the first call.  All subsequent interpolated variables are added
    174      * afterward.
    175      *
    176      * @return the string with the interpolation taken care of
    177      */
    178     protected String interpolateHelper(String base, List priorVariables)
    179     {
    180         if (base == null)
    181         {
    182             return null;
    183         }
    184
    185         // on the first call initialize priorVariables
    186         // and add base as the first element
    187         if (priorVariables == null)
    188         {
    189             priorVariables = new ArrayList();
    190             priorVariables.add(base);
    191         }
    192
    193         int begin = -1;
    194         int end = -1;
    195         int prec = 0 - END_TOKEN.length();
    196         String variable = null;
    197         StringBuffer result = new StringBuffer();
    198
    199         // FIXME: we should probably allow the escaping of the start token
    200         while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length()))
    201             > -1)
    202             && ((end = base.indexOf(END_TOKEN, begin)) > -1))
    203         {
    204             result.append(base.substring(prec + END_TOKEN.length(), begin));
    205             variable = base.substring(begin + START_TOKEN.length(), end);
    206
    207             // if we've got a loop, create a useful exception message and throw
    208             if (priorVariables.contains(variable))
    209             {
    210                 String initialBase = priorVariables.remove(0).toString();
    211                 priorVariables.add(variable);
    212                 StringBuffer priorVariableSb = new StringBuffer();
    213
    214                 // create a nice trace of interpolated variables like so:
    215                 // var1->var2->var3
    216                 for (Iterator it = priorVariables.iterator(); it.hasNext();)
    217                 {
    218                     priorVariableSb.append(it.next());
    219                     if (it.hasNext())
    220                     {
    221                         priorVariableSb.append("->");
    222                     }
    223                 }
    224
    225                 throw new IllegalStateException(
    226                     "infinite loop in property interpolation of "
    227                         + initialBase
    228                         + ": "
    229                         + priorVariableSb.toString());
    230             }
    231             // otherwise, add this variable to the interpolation list.
    232             else
    233             {
    234                 priorVariables.add(variable);
    235             }
    236
    237             //QUESTION: getProperty or getPropertyDirect
    238             Object value = getProperty(variable);
    239             if (value != null)
    240             {
    241                 result.append(interpolateHelper(value.toString(),
    242                     priorVariables));
    243
    244                 // pop the interpolated variable off the stack
    245                 // this maintains priorVariables correctness for
    246                 // properties with multiple interpolations, e.g.
    247                 // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
    248                 priorVariables.remove(priorVariables.size() - 1);
    249             }
    250             else
    251             {
    252                 //variable not defined - so put it back in the value
    253                 result.append(START_TOKEN).append(variable).append(END_TOKEN);
    254             }
    255
    256             prec = end;
    257         }
    258         result.append(base.substring(prec + END_TOKEN.length(), base.length()));
    259         return result.toString();
    260     }
    261
    262     /***
    263      * Returns a List of Strings built from the supplied String. Splits up CSV
    264      * lists. If no commas are in the String, simply returns a List with the
    265      * String as its first element.
    266      *
    267      * @param token The String to tokenize
    268      *
    269      * @return A List of Strings
    270      */
    271     protected List split(String token)
    272     {
    273         List list = new ArrayList(INITIAL_LIST_SIZE);
    274
    275         if (token.indexOf(DELIMITER) > 0)
    276         {
    277             PropertiesTokenizer tokenizer = new PropertiesTokenizer(token);
    278
    279             while (tokenizer.hasMoreTokens())
    280             {
    281                 list.add(tokenizer.nextToken());
    282             }
    283         }
    284         else
    285         {
    286             list.add(token);
    287         }
    288
    289         //
    290         // We keep the sequence of the keys here and
    291         // we also keep it in the List. So the
    292         // keys are added to the store in the sequence that
    293         // is given in the properties
    294         return list;
    295     }
    296
    297     /***
    298      * {@inheritDoc}
    299      */
    300     public Configuration subset(String prefix)
    301     {
    302         return new SubsetConfiguration(this, prefix, ".");
    303     }
    304
    305     /***
    306      * {@inheritDoc}
    307      */
    308     public abstract boolean isEmpty();
    309
    310     /***
    311      * {@inheritDoc}
    312      */
    313     public abstract boolean containsKey(String key);
    314
    315     /***
    316      * {@inheritDoc}
    317      */
    318     public void setProperty(String key, Object value)
    319     {
    320         clearProperty(key);
    321         addProperty(key, value); // QUESTION: or addPropertyDirect?
    322     }
    323
    324     /***
    325      * {@inheritDoc}
    326      */
    327     public  abstract void clearProperty(String key);
    328
    329     /***
    330      * {@inheritDoc}
    331      */
    332     public abstract Iterator getKeys();
    333
    334     /***
    335      * {@inheritDoc}
    336      */
    337     public Iterator getKeys(final String prefix)
    338     {
    339         return new FilterIterator(getKeys(), new Predicate()
    340         {
    341             public boolean evaluate(Object obj)
    342             {
    343                 String key = (String) obj;
    344                 return key.startsWith(prefix + ".") || key.equals(prefix);
    345             }
    346         });
    347     }
    348
    349     /***
    350      * {@inheritDoc}
    351      */
    352     public Properties getProperties(String key)
    353     {
    354         return getProperties(key, null);
    355     }
    356
    357     /***
    358      * Get a list of properties associated with the given configuration key.
    359      *
    360      * @param key The configuration key.
    361      * @param defaults Any default values for the returned
    362      * <code>Properties</code> object.  Ignored if <code>null</code>.
    363      *
    364      * @return The associated properties if key is found.
    365      *
    366      * @throws ConversionException is thrown if the key maps to an
    367      *         object that is not a String/List of Strings.
    368      *
    369      * @throws IllegalArgumentException if one of the tokens is
    370      *         malformed (does not contain an equals sign).
    371      */
    372     public Properties getProperties(String key, Properties defaults)
    373     {
    374         /*
    375          * Grab an array of the tokens for this key.
    376          */
    377         String[] tokens = getStringArray(key);
    378
    379         /*
    380          * Each token is of the form 'key=value'.
    381          */
    382         Properties props = defaults == null ? new Properties() : new Properties(defaults);
    383         for (int i = 0; i < tokens.length; i++)
    384         {
    385             String token = tokens[i];
    386             int equalSign = token.indexOf('=');
    387             if (equalSign > 0)
    388             {
    389                 String pkey = token.substring(0, equalSign).trim();
    390                 String pvalue = token.substring(equalSign + 1).trim();
    391                 props.put(pkey, pvalue);
    392             }
    393             else if (tokens.length == 1 && "".equals(token))
    394             {
    395                 // Semantically equivalent to an empty Properties
    396                 // object.
    397                 break;
    398             }
    399             else
    400             {
    401                 throw new IllegalArgumentException(
    402                     '\'' + token + "' does not contain an equals sign");
    403             }
    404         }
    405         return props;
    406     }
    407
    408     /***
    409      * {@inheritDoc}
    410      */
    411     public Object getProperty(String key)
    412     {
    413         return getPropertyDirect(key);
    414     }
    415
    416     /***
    417      * {@inheritDoc}
    418      */
    419     public boolean getBoolean(String key)
    420     {
    421         Boolean b = getBoolean(key, null);
    422         if (b != null)
    423         {
    424             return b.booleanValue();
    425         }
    426         else
    427         {
    428             throw new NoSuchElementException(
    429                 '\'' + key + "' doesn't map to an existing object");
    430         }
    431     }
    432
    433     /***
    434      * {@inheritDoc}
    435      */
    436     public boolean getBoolean(String key, boolean defaultValue)
    437     {
    438         return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue)).booleanValue();
    439     }
    440
    441     /***
    442      * {@inheritDoc}
    443      */
    444     public Boolean getBoolean(String key, Boolean defaultValue)
    445     {
    446         Object value = resolveContainerStore(key);
    447
    448         if (value instanceof Boolean)
    449         {
    450             return (Boolean) value;
    451         }
    452         else if (value instanceof String)
    453         {
    454             Boolean b = BooleanUtils.toBooleanObject((String) value);
    455             if (b == null)
    456             {
    457                 throw new ConversionException('\'' + key + "' doesn't map to a Boolean object");
    458             }
    459             return b;
    460         }
    461         else if (value == null)
    462         {
    463             return defaultValue;
    464         }
    465         else
    466         {
    467             throw new ConversionException(
    468                 '\'' + key + "' doesn't map to a Boolean object");
    469         }
    470     }
    471
    472     /***
    473      * {@inheritDoc}
    474      */
    475     public byte getByte(String key)
    476     {
    477         Byte b = getByte(key, null);
    478         if (b != null)
    479         {
    480             return b.byteValue();
    481         }
    482         else
    483         {
    484             throw new NoSuchElementException(
    485                 '\'' + key + " doesn't map to an existing object");
    486         }
    487     }
    488
    489     /***
    490      * {@inheritDoc}
    491      */
    492     public byte getByte(String key, byte defaultValue)
    493     {
    494         return getByte(key, new Byte(defaultValue)).byteValue();
    495     }
    496
    497     /***
    498      * {@inheritDoc}
    499      */
    500     public Byte getByte(String key, Byte defaultValue)
    501     {
    502         Object value = resolveContainerStore(key);
    503
    504         if (value instanceof Byte)
    505         {
    506             return (Byte) value;
    507         }
    508         else if (value instanceof String)
    509         {
    510             try
    511             {
    512                 Byte b = new Byte((String) value);
    513                 return b;
    514             }
    515             catch (NumberFormatException e)
    516             {
    517                 throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e);
    518             }
    519         }
    520         else if (value == null)
    521         {
    522             return defaultValue;
    523         }
    524         else
    525         {
    526             throw new ConversionException('\'' + key + "' doesn't map to a Byte object");
    527         }
    528     }
    529
    530     /***
    531      * {@inheritDoc}
    532      */
    533     public double getDouble(String key)
    534     {
    535         Double d = getDouble(key, null);
    536         if (d != null)
    537         {
    538             return d.doubleValue();
    539         }
    540         else
    541         {
    542             throw new NoSuchElementException(
    543                 '\'' + key + "' doesn't map to an existing object");
    544         }
    545     }
    546
    547     /***
    548      * {@inheritDoc}
    549      */
    550     public double getDouble(String key, double defaultValue)
    551     {
    552         return getDouble(key, new Double(defaultValue)).doubleValue();
    553     }
    554
    555     /***
    556      * {@inheritDoc}
    557      */
    558     public Double getDouble(String key, Double defaultValue)
    559     {
    560         Object value = resolveContainerStore(key);
    561
    562         if (value instanceof Double)
    563         {
    564             return (Double) value;
    565         }
    566         else if (value instanceof String)
    567         {
    568             try
    569             {
    570                 Double d = new Double((String) value);
    571                 return d;
    572             }
    573             catch (NumberFormatException e)
    574             {
    575                 throw new ConversionException('\'' + key + "' doesn't map to a Double object", e);
    576             }
    577         }
    578         else if (value == null)
    579         {
    580             return defaultValue;
    581         }
    582         else
    583         {
    584             throw new ConversionException('\'' + key + "' doesn't map to a Double object");
    585         }
    586     }
    587
    588     /***
    589      * {@inheritDoc}
    590      */
    591     public float getFloat(String key)
    592     {
    593         Float f = getFloat(key, null);
    594         if (f != null)
    595         {
    596             return f.floatValue();
    597         }
    598         else
    599         {
    600             throw new NoSuchElementException(
    601                 '\'' + key + "' doesn't map to an existing object");
    602         }
    603     }
    604
    605     /***
    606      * {@inheritDoc}
    607      */
    608     public float getFloat(String key, float defaultValue)
    609     {
    610         return getFloat(key, new Float(defaultValue)).floatValue();
    611     }
    612
    613     /***
    614      * {@inheritDoc}
    615      */
    616     public Float getFloat(String key, Float defaultValue)
    617     {
    618         Object value = resolveContainerStore(key);
    619
    620         if (value instanceof Float)
    621         {
    622             return (Float) value;
    623         }
    624         else if (value instanceof String)
    625         {
    626             try
    627             {
    628                 Float f = new Float((String) value);
    629                 return f;
    630             }
    631             catch (NumberFormatException e)
    632             {
    633                 throw new ConversionException('\'' + key + "' doesn't map to a Float object", e);
    634             }
    635         }
    636         else if (value == null)
    637         {
    638            return defaultValue;
    639         }
    640         else
    641         {
    642             throw new ConversionException('\'' + key + "' doesn't map to a Float object");
    643         }
    644     }
    645
    646     /***
    647      * {@inheritDoc}
    648      */
    649     public int getInt(String key)
    650     {
    651         Integer i = getInteger(key, null);
    652         if (i != null)
    653         {
    654             return i.intValue();
    655         }
    656         else
    657         {
    658             throw new NoSuchElementException(
    659                 '\'' + key + "' doesn't map to an existing object");
    660         }
    661     }
    662
    663     /***
    664      * {@inheritDoc}
    665      */
    666     public int getInt(String key, int defaultValue)
    667     {
    668         Integer i = getInteger(key, null);
    669
    670         if (i == null)
    671         {
    672             return defaultValue;
    673         }
    674
    675         return i.intValue();
    676     }
    677
    678     /***
    679      * {@inheritDoc}
    680      */
    681     public Integer getInteger(String key, Integer defaultValue)
    682     {
    683         Object value = resolveContainerStore(key);
    684
    685         if (value instanceof Integer)
    686         {
    687             return (Integer) value;
    688         }
    689         else if (value instanceof String)
    690         {
    691             try
    692             {
    693                 Integer i = new Integer((String) value);
    694                 return i;
    695             }
    696             catch (NumberFormatException e)
    697             {
    698                 throw new ConversionException('\'' + key + "' doesn't map to a Integer object", e);
    699             }
    700         }
    701         else if (value == null)
    702         {
    703             return defaultValue;
    704         }
    705         else
    706         {
    707             throw new ConversionException('\'' + key + "' doesn't map to a Integer object");
    708         }
    709     }
    710
    711     /***
    712      * {@inheritDoc}
    713      */
    714     public long getLong(String key)
    715     {
    716         Long l = getLong(key, null);
    717         if (l != null)
    718         {
    719             return l.longValue();
    720         }
    721         else
    722         {
    723             throw new NoSuchElementException(
    724                 '\'' + key + "' doesn't map to an existing object");
    725         }
    726     }
    727
    728     /***
    729      * {@inheritDoc}
    730      */
    731     public long getLong(String key, long defaultValue)
    732     {
    733         return getLong(key, new Long(defaultValue)).longValue();
    734     }
    735
    736     /***
    737      * {@inheritDoc}
    738      */
    739     public Long getLong(String key, Long defaultValue)
    740     {
    741         Object value = resolveContainerStore(key);
    742
    743         if (value instanceof Long)
    744         {
    745             return (Long) value;
    746         }
    747         else if (value instanceof String)
    748         {
    749             try
    750             {
    751                 Long l = new Long((String) value);
    752                 return l;
    753             }
    754             catch (NumberFormatException e)
    755             {
    756                 throw new ConversionException('\'' + key + "' doesn't map to a Long object", e);
    757             }
    758         }
    759         else if (value == null)
    760         {
    761             return defaultValue;
    762         }
    763         else
    764         {
    765             throw new ConversionException('\'' + key + "' doesn't map to a Long object");
    766         }
    767     }
    768
    769     /***
    770      * {@inheritDoc}
    771      */
    772     public short getShort(String key)
    773     {
    774         Short s = getShort(key, null);
    775         if (s != null)
    776         {
    777             return s.shortValue();
    778         }
    779         else
    780         {
    781             throw new NoSuchElementException(
    782                 '\'' + key + "' doesn't map to an existing object");
    783         }
    784     }
    785
    786     /***
    787      * {@inheritDoc}
    788      */
    789     public short getShort(String key, short defaultValue)
    790     {
    791         return getShort(key, new Short(defaultValue)).shortValue();
    792     }
    793
    794     /***
    795      * {@inheritDoc}
    796      */
    797     public Short getShort(String key, Short defaultValue)
    798     {
    799         Object value = resolveContainerStore(key);
    800
    801         if (value instanceof Short)
    802         {
    803             return (Short) value;
    804         }
    805         else if (value instanceof String)
    806         {
    807             try
    808             {
    809                 Short s = new Short((String) value);
    810                 return s;
    811             }
    812             catch (NumberFormatException e)
    813             {
    814                 throw new ConversionException('\'' + key + "' doesn't map to a Short object", e);
    815             }
    816         }
    817         else if (value == null)
    818         {
    819             return defaultValue;
    820         }
    821         else
    822         {
    823             throw new ConversionException('\'' + key + "' doesn't map to a Short object");
    824         }
    825     }
    826
    827     /***
    828      * {@inheritDoc}
    829      */
    830     public BigDecimal getBigDecimal(String key)
    831     {
    832         BigDecimal number = getBigDecimal(key, null);
    833         if (number != null)
    834         {
    835             return number;
    836         }
    837         else if (isThrowExceptionOnMissing())
    838         {
    839             throw new NoSuchElementException(
    840                 '\'' + key + "' doesn't map to an existing object");
    841         }
    842         else
    843         {
    844             return null;
    845         }
    846     }
    847
    848     /***
    849      * {@inheritDoc}
    850      */
    851     public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
    852     {
    853         Object value = resolveContainerStore(key);
    854
    855         if (value instanceof BigDecimal)
    856         {
    857             return (BigDecimal) value;
    858         }
    859         else if (value instanceof String)
    860         {
    861             try
    862             {
    863                 BigDecimal number = new BigDecimal((String) value);
    864                 return number;
    865             }
    866             catch (Exception e)
    867             {
    868                 throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e);
    869             }
    870         }
    871         else if (value == null)
    872         {
    873             return defaultValue;
    874         }
    875         else
    876         {
    877             throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object");
    878         }
    879     }
    880
    881     /***
    882      * {@inheritDoc}
    883      */
    884     public BigInteger getBigInteger(String key)
    885     {
    886         BigInteger number = getBigInteger(key, null);
    887         if (number != null)
    888         {
    889             return number;
    890         }
    891         else if (isThrowExceptionOnMissing())
    892         {
    893             throw new NoSuchElementException(
    894                     '\'' + key + "' doesn't map to an existing object");
    895         }
    896         else
    897         {
    898             return null;
    899         }
    900     }
    901
    902     /***
    903      * {@inheritDoc}
    904      */
    905     public BigInteger getBigInteger(String key, BigInteger defaultValue)
    906     {
    907         Object value = resolveContainerStore(key);
    908
    909         if (value instanceof BigInteger)
    910         {
    911             return (BigInteger) value;
    912         }
    913         else if (value instanceof String)
    914         {
    915             try
    916             {
    917                 BigInteger number = new BigInteger((String) value);
    918                 return number;
    919             }
    920             catch (Exception e)
    921             {
    922                 throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e);
    923             }
    924         }
    925         else if (value == null)
    926         {
    927             return defaultValue;
    928         }
    929         else
    930         {
    931             throw new ConversionException(
    932                 '\'' + key + "' doesn't map to a BigDecimal object");
    933         }
    934     }
    935
    936     /***
    937      * {@inheritDoc}
    938      */
    939     public String getString(String key)
    940     {
    941         String s = getString(key, null);
    942         if (s != null)
    943         {
    944             return s;
    945         }
    946         else if (isThrowExceptionOnMissing())
    947         {
    948             throw new NoSuchElementException(
    949                 '\'' + key + "' doesn't map to an existing object");
    950         }
    951         else
    952         {
    953             return null;
    954         }
    955     }
    956
    957     /***
    958      * {@inheritDoc}
    959      */
    960     public String getString(String key, String defaultValue)
    961     {
    962         Object value = resolveContainerStore(key);
    963
    964         if (value instanceof String)
    965         {
    966             return interpolate((String) value);
    967         }
    968         else if (value == null)
    969         {
    970            return interpolate(defaultValue);
    971         }
    972         else
    973         {
    974             throw new ConversionException(
    975                 '\'' + key + "' doesn't map to a String object");
    976         }
    977     }
    978
    979     /***
    980      * {@inheritDoc}
    981      */
    982     public String[] getStringArray(String key)
    983     {
    984         Object value = getPropertyDirect(key);
    985
    986         String[] tokens;
    987
    988         if (value instanceof String)
    989         {
    990             tokens = new String[1];
    991
    992             tokens[0] = interpolate((String) value);
    993         }
    994         else if (value instanceof List)
    995         {
    996             List list = (List) value;
    997             tokens = new String[list.size()];
    998
    999             for (int i = 0; i < tokens.length; i++)
    1000             {
    1001                 tokens[i] = interpolate((String) list.get(i));
    1002             }
    1003         }
    1004         else if (value == null)
    1005         {
    1006             tokens = new String[0];
    1007         }
    1008         else
    1009         {
    1010             throw new ConversionException(
    1011                 '\'' + key + "' doesn't map to a String/List object");
    1012         }
    1013         return tokens;
    1014     }
    1015
    1016     /***
    1017      * {@inheritDoc}
    1018      */
    1019     public List getList(String key)
    1020     {
    1021         return getList(key, new ArrayList());
    1022     }
    1023
    1024     /***
    1025      * {@inheritDoc}
    1026      */
    1027     public List getList(String key, List defaultValue)
    1028     {
    1029         Object value = getPropertyDirect(key);
    1030         List list = null;
    1031
    1032         if (value instanceof String)
    1033         {
    1034             list = new ArrayList(1);
    1035             list.add(value);
    1036         }
    1037         else if (value instanceof List)
    1038         {
    1039             list = (List) value;
    1040         }
    1041         else if (value == null)
    1042         {
    1043             list = defaultValue;
    1044         }
    1045         else
    1046         {
    1047             throw new ConversionException(
    1048                 '\''
    1049                     + key
    1050                     + "' doesn't map to a List object: "
    1051                     + value
    1052                     + ", a "
    1053                     + value.getClass().getName());
    1054         }
    1055         return list;
    1056     }
    1057
    1058     /***
    1059      * {@inheritDoc}
    1060      */
    1061     public Vector getVector(String key)
    1062     {
    1063         return getVector(key, new Vector());
    1064     }
    1065
    1066     /***
    1067      * {@inheritDoc}
    1068      */
    1069     public Vector getVector(String key, Vector defaultValue)
    1070     {
    1071         Object value = getPropertyDirect(key);
    1072         Vector vector = null;
    1073
    1074         if (value instanceof String)
    1075         {
    1076             vector = new Vector(1);
    1077             vector.add(value);
    1078         }
    1079         else if (value instanceof List)
    1080         {
    1081             vector = new Vector(((List) value).size());
    1082
    1083             for (Iterator it = ((List) value).iterator(); it.hasNext(); )
    1084             {
    1085                 Object obj = it.next();
    1086                 vector.add(obj);
    1087             }
    1088         }
    1089         else if (value == null)
    1090         {
    1091             vector = defaultValue;
    1092         }
    1093         else
    1094         {
    1095             throw new ConversionException(
    1096                 '\''
    1097                     + key
    1098                     + "' doesn't map to a Vector object: "
    1099                     + value
    1100                     + ", a "
    1101                     + value.getClass().getName());
    1102         }
    1103         return vector;
    1104     }
    1105
    1106     /***
    1107      * Returns an object from the store described by the key. If the value is
    1108      * a List object, replace it with the first object in the list.
    1109      *
    1110      * @param key The property key.
    1111      *
    1112      * @return value Value, transparently resolving a possible List dependency.
    1113      */
    1114     private Object resolveContainerStore(String key)
    1115     {
    1116         Object value = getPropertyDirect(key);
    1117         if (value != null && value instanceof List)
    1118         {
    1119             List list = (List) value;
    1120             value = list.isEmpty() ? null : list.get(0);
    1121         }
    1122         return value;
    1123     }
    1124
    1125     /***
    1126      * This class divides into tokens a property value.  Token
    1127      * separator is "," but commas into the property value are escaped
    1128      * using the backslash in front.
    1129      */
    1130     static class PropertiesTokenizer extends StringTokenizer
    1131     {
    1132         /***
    1133          * Constructor.
    1134          *
    1135          * @param string A String.
    1136          */
    1137         public PropertiesTokenizer(String string)
    1138         {
    1139             super(string, String.valueOf(DELIMITER));
    1140         }
    1141
    1142         /***
    1143          * Get next token.
    1144          *
    1145          * @return A String.
    1146          */
    1147         public String nextToken()
    1148         {
    1149             StringBuffer buffer = new StringBuffer();
    1150
    1151             while (hasMoreTokens())
    1152             {
    1153                 String token = super.nextToken();
    1154                 if (token.endsWith("//"))
    1155                 {
    1156                     buffer.append(token.substring(0, token.length() - 1));
    1157                     buffer.append(DELIMITER);
    1158                 }
    1159                 else
    1160                 {
    1161                     buffer.append(token);
    1162                     break;
    1163                 }
    1164             }
    1165             return buffer.toString().trim();
    1166         }
    1167     } // class PropertiesTokenizer
    1168
    1169 }