View Javadoc

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