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