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