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