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