001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import java.lang.reflect.Array;
021 import java.math.BigDecimal;
022 import java.math.BigInteger;
023 import java.util.ArrayList;
024 import java.util.Arrays;
025 import java.util.Collection;
026 import java.util.Collections;
027 import java.util.Iterator;
028 import java.util.List;
029 import java.util.NoSuchElementException;
030 import java.util.Properties;
031
032 import org.apache.commons.configuration.event.ConfigurationErrorEvent;
033 import org.apache.commons.configuration.event.ConfigurationErrorListener;
034 import org.apache.commons.configuration.event.EventSource;
035 import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
036 import org.apache.commons.lang.BooleanUtils;
037 import org.apache.commons.lang.ClassUtils;
038 import org.apache.commons.lang.ObjectUtils;
039 import org.apache.commons.lang.text.StrLookup;
040 import org.apache.commons.lang.text.StrSubstitutor;
041 import org.apache.commons.logging.Log;
042 import org.apache.commons.logging.impl.NoOpLog;
043
044 /**
045 * <p>Abstract configuration class. Provides basic functionality but does not
046 * store any data.</p>
047 * <p>If you want to write your own Configuration class then you should
048 * implement only abstract methods from this class. A lot of functionality
049 * needed by typical implementations of the {@code Configuration}
050 * interface is already provided by this base class. Following is a list of
051 * features implemented here:
052 * <ul><li>Data conversion support. The various data types required by the
053 * {@code Configuration} interface are already handled by this base class.
054 * A concrete sub class only needs to provide a generic {@code getProperty()}
055 * method.</li>
056 * <li>Support for variable interpolation. Property values containing special
057 * variable tokens (like <code>${var}</code>) will be replaced by their
058 * corresponding values.</li>
059 * <li>Support for string lists. The values of properties to be added to this
060 * configuration are checked whether they contain a list delimiter character. If
061 * this is the case and if list splitting is enabled, the string is split and
062 * multiple values are added for this property. (With the
063 * {@code setListDelimiter()} method the delimiter character can be
064 * specified; per default a comma is used. The
065 * {@code setDelimiterParsingDisabled()} method can be used to disable
066 * list splitting completely.)</li>
067 * <li>Allows to specify how missing properties are treated. Per default the
068 * get methods returning an object will return <b>null</b> if the searched
069 * property key is not found (and no default value is provided). With the
070 * {@code setThrowExceptionOnMissing()} method this behavior can be
071 * changed to throw an exception when a requested property cannot be found.</li>
072 * <li>Basic event support. Whenever this configuration is modified registered
073 * event listeners are notified. Refer to the various {@code EVENT_XXX}
074 * constants to get an impression about which event types are supported.</li>
075 * </ul></p>
076 *
077 * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov </a>
078 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen </a>
079 * @version $Id: AbstractConfiguration.java 1234985 2012-01-23 21:09:09Z oheger $
080 */
081 public abstract class AbstractConfiguration extends EventSource implements Configuration
082 {
083 /**
084 * Constant for the add property event type.
085 * @since 1.3
086 */
087 public static final int EVENT_ADD_PROPERTY = 1;
088
089 /**
090 * Constant for the clear property event type.
091 * @since 1.3
092 */
093 public static final int EVENT_CLEAR_PROPERTY = 2;
094
095 /**
096 * Constant for the set property event type.
097 * @since 1.3
098 */
099 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<Object> 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 = 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 }