View Javadoc

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