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