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