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