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