View Javadoc

1   /*
2    * Copyright 2001-2005 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;
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         // on the first call initialize priorVariables
179         // and add base as the first element
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         // FIXME: we should probably allow the escaping of the start token
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             // if we've got a loop, create a useful exception message and throw
199             if (priorVariables.contains(variable))
200             {
201                 String initialBase = priorVariables.remove(0).toString();
202                 priorVariables.add(variable);
203                 StringBuffer priorVariableSb = new StringBuffer();
204 
205                 // create a nice trace of interpolated variables like so:
206                 // var1->var2->var3
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             // otherwise, add this variable to the interpolation list.
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                 // pop the interpolated variable off the stack
231                 // this maintains priorVariables correctness for
232                 // properties with multiple interpolations, e.g.
233                 // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
234                 priorVariables.remove(priorVariables.size() - 1);
235             }
236             else
237             {
238                 //variable not defined - so put it back in the value
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                 // workaround for Iterators that do not remove the property on calling remove()
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          * Grab an array of the tokens for this key.
346          */
347         String[] tokens = getStringArray(key);
348 
349         /*
350          * Each token is of the form 'key=value'.
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                 // Semantically equivalent to an empty Properties
366                 // object.
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             // add the interpolated elements in the new list
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 }