View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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         // on the first call initialize priorVariables
154         // and add base as the first element
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         // FIXME: we should probably allow the escaping of the start token
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             // if we've got a loop, create a useful exception message and throw
175             if (priorVariables.contains(variable))
176             {
177                 String initialBase = priorVariables.remove(0).toString();
178                 priorVariables.add(variable);
179                 StringBuffer priorVariableSb = new StringBuffer();
180 
181                 // create a nice trace of interpolated variables like so:
182                 // var1->var2->var3
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             // otherwise, add this variable to the interpolation list.
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                 // pop the interpolated variable off the stack
207                 // this maintains priorVariables correctness for
208                 // properties with multiple interpolations, e.g.
209                 // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
210                 priorVariables.remove(priorVariables.size() - 1);
211             }
212             else
213             {
214                 //variable not defined - so put it back in the value
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                 // workaround for Iterators that do not remove the property on calling remove()
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          * Grab an array of the tokens for this key.
322          */
323         String[] tokens = getStringArray(key);
324 
325         /*
326          * Each token is of the form 'key=value'.
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                 // Semantically equivalent to an empty Properties
342                 // object.
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 }