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