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