001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.collections;
018
019 import java.io.File;
020 import java.io.FileInputStream;
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.io.InputStreamReader;
024 import java.io.LineNumberReader;
025 import java.io.OutputStream;
026 import java.io.PrintWriter;
027 import java.io.Reader;
028 import java.io.UnsupportedEncodingException;
029 import java.util.ArrayList;
030 import java.util.Enumeration;
031 import java.util.Hashtable;
032 import java.util.Iterator;
033 import java.util.List;
034 import java.util.NoSuchElementException;
035 import java.util.Properties;
036 import java.util.StringTokenizer;
037 import java.util.Vector;
038
039 /**
040 * This class extends normal Java properties by adding the possibility
041 * to use the same key many times concatenating the value strings
042 * instead of overwriting them.
043 * <p>
044 * <b>Please consider using the <code>PropertiesConfiguration</code> class in
045 * Commons-Configuration as soon as it is released.</b>
046 * <p>
047 * The Extended Properties syntax is explained here:
048 *
049 * <ul>
050 * <li>
051 * Each property has the syntax <code>key = value</code>
052 * </li>
053 * <li>
054 * The <i>key</i> may use any character but the equal sign '='.
055 * </li>
056 * <li>
057 * <i>value</i> may be separated on different lines if a backslash
058 * is placed at the end of the line that continues below.
059 * </li>
060 * <li>
061 * If <i>value</i> is a list of strings, each token is separated
062 * by a comma ','.
063 * </li>
064 * <li>
065 * Commas in each token are escaped placing a backslash right before
066 * the comma.
067 * </li>
068 * <li>
069 * Backslashes are escaped by using two consecutive backslashes i.e. \\
070 * </li>
071 * <li>
072 * If a <i>key</i> is used more than once, the values are appended
073 * as if they were on the same line separated with commas.
074 * </li>
075 * <li>
076 * Blank lines and lines starting with character '#' are skipped.
077 * </li>
078 * <li>
079 * If a property is named "include" (or whatever is defined by
080 * setInclude() and getInclude() and the value of that property is
081 * the full path to a file on disk, that file will be included into
082 * the ConfigurationsRepository. You can also pull in files relative
083 * to the parent configuration file. So if you have something
084 * like the following:
085 *
086 * include = additional.properties
087 *
088 * Then "additional.properties" is expected to be in the same
089 * directory as the parent configuration file.
090 *
091 * Duplicate name values will be replaced, so be careful.
092 *
093 * </li>
094 * </ul>
095 *
096 * <p>Here is an example of a valid extended properties file:
097 *
098 * <p><pre>
099 * # lines starting with # are comments
100 *
101 * # This is the simplest property
102 * key = value
103 *
104 * # A long property may be separated on multiple lines
105 * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
106 * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
107 *
108 * # This is a property with many tokens
109 * tokens_on_a_line = first token, second token
110 *
111 * # This sequence generates exactly the same result
112 * tokens_on_multiple_lines = first token
113 * tokens_on_multiple_lines = second token
114 *
115 * # commas may be escaped in tokens
116 * commas.escaped = Hi\, what'up?
117 * </pre>
118 *
119 * <p><b>NOTE</b>: this class has <b>not</b> been written for
120 * performance nor low memory usage. In fact, it's way slower than it
121 * could be and generates too much memory garbage. But since
122 * performance is not an issue during intialization (and there is not
123 * much time to improve it), I wrote it this way. If you don't like
124 * it, go ahead and tune it up!
125 *
126 * @since Commons Collections 1.0
127 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
128 *
129 * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
130 * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
131 * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
132 * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
133 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
134 * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
135 * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
136 * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
137 * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
138 * @author Janek Bogucki
139 * @author Mohan Kishore
140 * @author Stephen Colebourne
141 * @author Shinobu Kawai
142 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
143 */
144 public class ExtendedProperties extends Hashtable {
145
146 /**
147 * Default configurations repository.
148 */
149 private ExtendedProperties defaults;
150
151 /**
152 * The file connected to this repository (holding comments and
153 * such).
154 *
155 * @serial
156 */
157 protected String file;
158
159 /**
160 * Base path of the configuration file used to create
161 * this ExtendedProperties object.
162 */
163 protected String basePath;
164
165 /**
166 * File separator.
167 */
168 protected String fileSeparator = System.getProperty("file.separator");
169
170 /**
171 * Has this configuration been intialized.
172 */
173 protected boolean isInitialized = false;
174
175 /**
176 * This is the name of the property that can point to other
177 * properties file for including other properties files.
178 */
179 protected static String include = "include";
180
181 /**
182 * These are the keys in the order they listed
183 * in the configuration file. This is useful when
184 * you wish to perform operations with configuration
185 * information in a particular order.
186 */
187 protected ArrayList keysAsListed = new ArrayList();
188
189 protected final static String START_TOKEN="${";
190 protected final static String END_TOKEN="}";
191
192
193 /**
194 * Interpolate key names to handle ${key} stuff
195 *
196 * @param base string to interpolate
197 * @return returns the key name with the ${key} substituted
198 */
199 protected String interpolate(String base) {
200 // COPIED from [configuration] 2003-12-29
201 return (interpolateHelper(base, null));
202 }
203
204 /**
205 * Recursive handler for multiple levels of interpolation.
206 *
207 * When called the first time, priorVariables should be null.
208 *
209 * @param base string with the ${key} variables
210 * @param priorVariables serves two purposes: to allow checking for
211 * loops, and creating a meaningful exception message should a loop
212 * occur. It's 0'th element will be set to the value of base from
213 * the first call. All subsequent interpolated variables are added
214 * afterward.
215 *
216 * @return the string with the interpolation taken care of
217 */
218 protected String interpolateHelper(String base, List priorVariables) {
219 // COPIED from [configuration] 2003-12-29
220 if (base == null) {
221 return null;
222 }
223
224 // on the first call initialize priorVariables
225 // and add base as the first element
226 if (priorVariables == null) {
227 priorVariables = new ArrayList();
228 priorVariables.add(base);
229 }
230
231 int begin = -1;
232 int end = -1;
233 int prec = 0 - END_TOKEN.length();
234 String variable = null;
235 StringBuffer result = new StringBuffer();
236
237 // FIXME: we should probably allow the escaping of the start token
238 while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length())) > -1)
239 && ((end = base.indexOf(END_TOKEN, begin)) > -1)) {
240 result.append(base.substring(prec + END_TOKEN.length(), begin));
241 variable = base.substring(begin + START_TOKEN.length(), end);
242
243 // if we've got a loop, create a useful exception message and throw
244 if (priorVariables.contains(variable)) {
245 String initialBase = priorVariables.remove(0).toString();
246 priorVariables.add(variable);
247 StringBuffer priorVariableSb = new StringBuffer();
248
249 // create a nice trace of interpolated variables like so:
250 // var1->var2->var3
251 for (Iterator it = priorVariables.iterator(); it.hasNext();) {
252 priorVariableSb.append(it.next());
253 if (it.hasNext()) {
254 priorVariableSb.append("->");
255 }
256 }
257
258 throw new IllegalStateException(
259 "infinite loop in property interpolation of " + initialBase + ": " + priorVariableSb.toString());
260 }
261 // otherwise, add this variable to the interpolation list.
262 else {
263 priorVariables.add(variable);
264 }
265
266 //QUESTION: getProperty or getPropertyDirect
267 Object value = getProperty(variable);
268 if (value != null) {
269 result.append(interpolateHelper(value.toString(), priorVariables));
270
271 // pop the interpolated variable off the stack
272 // this maintains priorVariables correctness for
273 // properties with multiple interpolations, e.g.
274 // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
275 priorVariables.remove(priorVariables.size() - 1);
276 } else if (defaults != null && defaults.getString(variable, null) != null) {
277 result.append(defaults.getString(variable));
278 } else {
279 //variable not defined - so put it back in the value
280 result.append(START_TOKEN).append(variable).append(END_TOKEN);
281 }
282 prec = end;
283 }
284 result.append(base.substring(prec + END_TOKEN.length(), base.length()));
285
286 return result.toString();
287 }
288
289 /**
290 * Inserts a backslash before every comma and backslash.
291 */
292 private static String escape(String s) {
293 StringBuffer buf = new StringBuffer(s);
294 for (int i = 0; i < buf.length(); i++) {
295 char c = buf.charAt(i);
296 if (c == ',' || c == '\\') {
297 buf.insert(i, '\\');
298 i++;
299 }
300 }
301 return buf.toString();
302 }
303
304 /**
305 * Removes a backslash from every pair of backslashes.
306 */
307 private static String unescape(String s) {
308 StringBuffer buf = new StringBuffer(s);
309 for (int i = 0; i < buf.length() - 1; i++) {
310 char c1 = buf.charAt(i);
311 char c2 = buf.charAt(i + 1);
312 if (c1 == '\\' && c2 == '\\') {
313 buf.deleteCharAt(i);
314 }
315 }
316 return buf.toString();
317 }
318
319 /**
320 * Counts the number of successive times 'ch' appears in the
321 * 'line' before the position indicated by the 'index'.
322 */
323 private static int countPreceding(String line, int index, char ch) {
324 int i;
325 for (i = index - 1; i >= 0; i--) {
326 if (line.charAt(i) != ch) {
327 break;
328 }
329 }
330 return index - 1 - i;
331 }
332
333 /**
334 * Checks if the line ends with odd number of backslashes
335 */
336 private static boolean endsWithSlash(String line) {
337 if (!line.endsWith("\\")) {
338 return false;
339 }
340 return (countPreceding(line, line.length() - 1, '\\') % 2 == 0);
341 }
342
343 /**
344 * This class is used to read properties lines. These lines do
345 * not terminate with new-line chars but rather when there is no
346 * backslash sign a the end of the line. This is used to
347 * concatenate multiple lines for readability.
348 */
349 static class PropertiesReader extends LineNumberReader {
350 /**
351 * Constructor.
352 *
353 * @param reader A Reader.
354 */
355 public PropertiesReader(Reader reader) {
356 super(reader);
357 }
358
359 /**
360 * Read a property.
361 *
362 * @return a String property
363 * @throws IOException if there is difficulty reading the source.
364 */
365 public String readProperty() throws IOException {
366 StringBuffer buffer = new StringBuffer();
367 String line = readLine();
368 while (line != null) {
369 line = line.trim();
370 if ((line.length() != 0) && (line.charAt(0) != '#')) {
371 if (endsWithSlash(line)) {
372 line = line.substring(0, line.length() - 1);
373 buffer.append(line);
374 } else {
375 buffer.append(line);
376 return buffer.toString(); // normal method end
377 }
378 }
379 line = readLine();
380 }
381 return null; // EOF reached
382 }
383 }
384
385 /**
386 * This class divides into tokens a property value. Token
387 * separator is "," but commas into the property value are escaped
388 * using the backslash in front.
389 */
390 static class PropertiesTokenizer extends StringTokenizer {
391 /**
392 * The property delimiter used while parsing (a comma).
393 */
394 static final String DELIMITER = ",";
395
396 /**
397 * Constructor.
398 *
399 * @param string A String.
400 */
401 public PropertiesTokenizer(String string) {
402 super(string, DELIMITER);
403 }
404
405 /**
406 * Check whether the object has more tokens.
407 *
408 * @return True if the object has more tokens.
409 */
410 public boolean hasMoreTokens() {
411 return super.hasMoreTokens();
412 }
413
414 /**
415 * Get next token.
416 *
417 * @return A String.
418 */
419 public String nextToken() {
420 StringBuffer buffer = new StringBuffer();
421
422 while (hasMoreTokens()) {
423 String token = super.nextToken();
424 if (endsWithSlash(token)) {
425 buffer.append(token.substring(0, token.length() - 1));
426 buffer.append(DELIMITER);
427 } else {
428 buffer.append(token);
429 break;
430 }
431 }
432
433 return buffer.toString().trim();
434 }
435 }
436
437 /**
438 * Creates an empty extended properties object.
439 */
440 public ExtendedProperties() {
441 super();
442 }
443
444 /**
445 * Creates and loads the extended properties from the specified file.
446 *
447 * @param file the filename to load
448 * @throws IOException if a file error occurs
449 */
450 public ExtendedProperties(String file) throws IOException {
451 this(file, null);
452 }
453
454 /**
455 * Creates and loads the extended properties from the specified file.
456 *
457 * @param file the filename to load
458 * @param defaultFile a second filename to load default values from
459 * @throws IOException if a file error occurs
460 */
461 public ExtendedProperties(String file, String defaultFile) throws IOException {
462 this.file = file;
463
464 basePath = new File(file).getAbsolutePath();
465 basePath = basePath.substring(0, basePath.lastIndexOf(fileSeparator) + 1);
466
467 FileInputStream in = null;
468 try {
469 in = new FileInputStream(file);
470 this.load(in);
471 } finally {
472 try {
473 if (in != null) {
474 in.close();
475 }
476 } catch (IOException ex) {}
477 }
478
479 if (defaultFile != null) {
480 defaults = new ExtendedProperties(defaultFile);
481 }
482 }
483
484 /**
485 * Indicate to client code whether property
486 * resources have been initialized or not.
487 */
488 public boolean isInitialized() {
489 return isInitialized;
490 }
491
492 /**
493 * Gets the property value for including other properties files.
494 * By default it is "include".
495 *
496 * @return A String.
497 */
498 public String getInclude() {
499 return include;
500 }
501
502 /**
503 * Sets the property value for including other properties files.
504 * By default it is "include".
505 *
506 * @param inc A String.
507 */
508 public void setInclude(String inc) {
509 include = inc;
510 }
511
512 /**
513 * Load the properties from the given input stream.
514 *
515 * @param input the InputStream to load from
516 * @throws IOException if an IO error occurs
517 */
518 public void load(InputStream input) throws IOException {
519 load(input, null);
520 }
521
522 /**
523 * Load the properties from the given input stream
524 * and using the specified encoding.
525 *
526 * @param input the InputStream to load from
527 * @param enc the encoding to use
528 * @throws IOException if an IO error occurs
529 */
530 public synchronized void load(InputStream input, String enc) throws IOException {
531 PropertiesReader reader = null;
532 if (enc != null) {
533 try {
534 reader = new PropertiesReader(new InputStreamReader(input, enc));
535
536 } catch (UnsupportedEncodingException ex) {
537 // Another try coming up....
538 }
539 }
540
541 if (reader == null) {
542 try {
543 reader = new PropertiesReader(new InputStreamReader(input, "8859_1"));
544
545 } catch (UnsupportedEncodingException ex) {
546 // ISO8859-1 support is required on java platforms but....
547 // If it's not supported, use the system default encoding
548 reader = new PropertiesReader(new InputStreamReader(input));
549 }
550 }
551
552 try {
553 while (true) {
554 String line = reader.readProperty();
555 if (line == null) {
556 return; // EOF
557 }
558 int equalSign = line.indexOf('=');
559
560 if (equalSign > 0) {
561 String key = line.substring(0, equalSign).trim();
562 String value = line.substring(equalSign + 1).trim();
563
564 // Configure produces lines like this ... just ignore them
565 if ("".equals(value)) {
566 continue;
567 }
568
569 if (getInclude() != null && key.equalsIgnoreCase(getInclude())) {
570 // Recursively load properties files.
571 File file = null;
572
573 if (value.startsWith(fileSeparator)) {
574 // We have an absolute path so we'll use this
575 file = new File(value);
576
577 } else {
578 // We have a relative path, and we have two
579 // possible forms here. If we have the "./" form
580 // then just strip that off first before continuing.
581 if (value.startsWith("." + fileSeparator)) {
582 value = value.substring(2);
583 }
584
585 file = new File(basePath + value);
586 }
587
588 if (file != null && file.exists() && file.canRead()) {
589 load(new FileInputStream(file));
590 }
591 } else {
592 addProperty(key, value);
593 }
594 }
595 }
596 } finally {
597 // Loading is initializing
598 isInitialized = true;
599 }
600 }
601
602 /**
603 * Gets a property from the configuration.
604 *
605 * @param key property to retrieve
606 * @return value as object. Will return user value if exists,
607 * if not then default value if exists, otherwise null
608 */
609 public Object getProperty(String key) {
610 // first, try to get from the 'user value' store
611 Object obj = this.get(key);
612
613 if (obj == null) {
614 // if there isn't a value there, get it from the
615 // defaults if we have them
616 if (defaults != null) {
617 obj = defaults.get(key);
618 }
619 }
620
621 return obj;
622 }
623
624 /**
625 * Add a property to the configuration. If it already
626 * exists then the value stated here will be added
627 * to the configuration entry. For example, if
628 *
629 * <code>resource.loader = file</code>
630 *
631 * is already present in the configuration and you
632 *
633 * <code>addProperty("resource.loader", "classpath")</code>
634 *
635 * Then you will end up with a Vector like the
636 * following:
637 *
638 * <code>["file", "classpath"]</code>
639 *
640 * @param key the key to add
641 * @param value the value to add
642 */
643 public void addProperty(String key, Object value) {
644 if (value instanceof String) {
645 String str = (String) value;
646 if (str.indexOf(PropertiesTokenizer.DELIMITER) > 0) {
647 // token contains commas, so must be split apart then added
648 PropertiesTokenizer tokenizer = new PropertiesTokenizer(str);
649 while (tokenizer.hasMoreTokens()) {
650 String token = tokenizer.nextToken();
651 addPropertyInternal(key, unescape(token));
652 }
653 } else {
654 // token contains no commas, so can be simply added
655 addPropertyInternal(key, unescape(str));
656 }
657 } else {
658 addPropertyInternal(key, value);
659 }
660
661 // Adding a property connotes initialization
662 isInitialized = true;
663 }
664
665 /**
666 * Adds a key/value pair to the map. This routine does
667 * no magic morphing. It ensures the keylist is maintained
668 *
669 * @param key the key to store at
670 * @param value the decoded object to store
671 */
672 private void addPropertyDirect(String key, Object value) {
673 // safety check
674 if (!containsKey(key)) {
675 keysAsListed.add(key);
676 }
677 put(key, value);
678 }
679
680 /**
681 * Adds a decoded property to the map w/o checking for commas - used
682 * internally when a property has been broken up into
683 * strings that could contain escaped commas to prevent
684 * the inadvertent vectorization.
685 * <p>
686 * Thanks to Leon Messerschmidt for this one.
687 *
688 * @param key the key to store at
689 * @param value the decoded object to store
690 */
691 private void addPropertyInternal(String key, Object value) {
692 Object current = this.get(key);
693
694 if (current instanceof String) {
695 // one object already in map - convert it to a vector
696 List values = new Vector(2);
697 values.add(current);
698 values.add(value);
699 put(key, values);
700
701 } else if (current instanceof List) {
702 // already a list - just add the new token
703 ((List) current).add(value);
704
705 } else {
706 // brand new key - store in keysAsListed to retain order
707 if (!containsKey(key)) {
708 keysAsListed.add(key);
709 }
710 put(key, value);
711 }
712 }
713
714 /**
715 * Set a property, this will replace any previously
716 * set values. Set values is implicitly a call
717 * to clearProperty(key), addProperty(key,value).
718 *
719 * @param key the key to set
720 * @param value the value to set
721 */
722 public void setProperty(String key, Object value) {
723 clearProperty(key);
724 addProperty(key, value);
725 }
726
727 /**
728 * Save the properties to the given output stream.
729 * <p>
730 * The stream is not closed, but it is flushed.
731 *
732 * @param output an OutputStream, may be null
733 * @param header a textual comment to act as a file header
734 * @throws IOException if an IO error occurs
735 */
736 public synchronized void save(OutputStream output, String header) throws IOException {
737 if (output == null) {
738 return;
739 }
740 PrintWriter theWrtr = new PrintWriter(output);
741 if (header != null) {
742 theWrtr.println(header);
743 }
744
745 Enumeration theKeys = keys();
746 while (theKeys.hasMoreElements()) {
747 String key = (String) theKeys.nextElement();
748 Object value = get(key);
749 if (value != null) {
750 if (value instanceof String) {
751 StringBuffer currentOutput = new StringBuffer();
752 currentOutput.append(key);
753 currentOutput.append("=");
754 currentOutput.append(escape((String) value));
755 theWrtr.println(currentOutput.toString());
756
757 } else if (value instanceof List) {
758 List values = (List) value;
759 for (Iterator it = values.iterator(); it.hasNext(); ) {
760 String currentElement = (String) it.next();
761 StringBuffer currentOutput = new StringBuffer();
762 currentOutput.append(key);
763 currentOutput.append("=");
764 currentOutput.append(escape(currentElement));
765 theWrtr.println(currentOutput.toString());
766 }
767 }
768 }
769 theWrtr.println();
770 theWrtr.flush();
771 }
772 }
773
774 /**
775 * Combines an existing Hashtable with this Hashtable.
776 * <p>
777 * Warning: It will overwrite previous entries without warning.
778 *
779 * @param props the properties to combine
780 */
781 public void combine(ExtendedProperties props) {
782 for (Iterator it = props.getKeys(); it.hasNext();) {
783 String key = (String) it.next();
784 setProperty(key, props.get(key));
785 }
786 }
787
788 /**
789 * Clear a property in the configuration.
790 *
791 * @param key the property key to remove along with corresponding value
792 */
793 public void clearProperty(String key) {
794 if (containsKey(key)) {
795 // we also need to rebuild the keysAsListed or else
796 // things get *very* confusing
797 for (int i = 0; i < keysAsListed.size(); i++) {
798 if (( keysAsListed.get(i)).equals(key)) {
799 keysAsListed.remove(i);
800 break;
801 }
802 }
803 remove(key);
804 }
805 }
806
807 /**
808 * Get the list of the keys contained in the configuration
809 * repository.
810 *
811 * @return an Iterator over the keys
812 */
813 public Iterator getKeys() {
814 return keysAsListed.iterator();
815 }
816
817 /**
818 * Get the list of the keys contained in the configuration
819 * repository that match the specified prefix.
820 *
821 * @param prefix the prefix to match
822 * @return an Iterator of keys that match the prefix
823 */
824 public Iterator getKeys(String prefix) {
825 Iterator keys = getKeys();
826 ArrayList matchingKeys = new ArrayList();
827
828 while (keys.hasNext()) {
829 Object key = keys.next();
830
831 if (key instanceof String && ((String) key).startsWith(prefix)) {
832 matchingKeys.add(key);
833 }
834 }
835 return matchingKeys.iterator();
836 }
837
838 /**
839 * Create an ExtendedProperties object that is a subset
840 * of this one. Take into account duplicate keys
841 * by using the setProperty() in ExtendedProperties.
842 *
843 * @param prefix the prefix to get a subset for
844 * @return a new independent ExtendedProperties
845 */
846 public ExtendedProperties subset(String prefix) {
847 ExtendedProperties c = new ExtendedProperties();
848 Iterator keys = getKeys();
849 boolean validSubset = false;
850
851 while (keys.hasNext()) {
852 Object key = keys.next();
853
854 if (key instanceof String && ((String) key).startsWith(prefix)) {
855 if (!validSubset) {
856 validSubset = true;
857 }
858
859 /*
860 * Check to make sure that c.subset(prefix) doesn't
861 * blow up when there is only a single property
862 * with the key prefix. This is not a useful
863 * subset but it is a valid subset.
864 */
865 String newKey = null;
866 if (((String) key).length() == prefix.length()) {
867 newKey = prefix;
868 } else {
869 newKey = ((String) key).substring(prefix.length() + 1);
870 }
871
872 /*
873 * use addPropertyDirect() - this will plug the data as
874 * is into the Map, but will also do the right thing
875 * re key accounting
876 */
877 c.addPropertyDirect(newKey, get(key));
878 }
879 }
880
881 if (validSubset) {
882 return c;
883 } else {
884 return null;
885 }
886 }
887
888 /**
889 * Display the configuration for debugging purposes to System.out.
890 */
891 public void display() {
892 Iterator i = getKeys();
893
894 while (i.hasNext()) {
895 String key = (String) i.next();
896 Object value = get(key);
897 System.out.println(key + " => " + value);
898 }
899 }
900
901 /**
902 * Get a string associated with the given configuration key.
903 *
904 * @param key The configuration key.
905 * @return The associated string.
906 * @throws ClassCastException is thrown if the key maps to an
907 * object that is not a String.
908 */
909 public String getString(String key) {
910 return getString(key, null);
911 }
912
913 /**
914 * Get a string associated with the given configuration key.
915 *
916 * @param key The configuration key.
917 * @param defaultValue The default value.
918 * @return The associated string if key is found,
919 * default value otherwise.
920 * @throws ClassCastException is thrown if the key maps to an
921 * object that is not a String.
922 */
923 public String getString(String key, String defaultValue) {
924 Object value = get(key);
925
926 if (value instanceof String) {
927 return interpolate((String) value);
928
929 } else if (value == null) {
930 if (defaults != null) {
931 return interpolate(defaults.getString(key, defaultValue));
932 } else {
933 return interpolate(defaultValue);
934 }
935 } else if (value instanceof List) {
936 return interpolate((String) ((List) value).get(0));
937 } else {
938 throw new ClassCastException('\'' + key + "' doesn't map to a String object");
939 }
940 }
941
942 /**
943 * Get a list of properties associated with the given
944 * configuration key.
945 *
946 * @param key The configuration key.
947 * @return The associated properties if key is found.
948 * @throws ClassCastException is thrown if the key maps to an
949 * object that is not a String/List.
950 * @throws IllegalArgumentException if one of the tokens is
951 * malformed (does not contain an equals sign).
952 */
953 public Properties getProperties(String key) {
954 return getProperties(key, new Properties());
955 }
956
957 /**
958 * Get a list of properties associated with the given
959 * configuration key.
960 *
961 * @param key The configuration key.
962 * @return The associated properties if key is found.
963 * @throws ClassCastException is thrown if the key maps to an
964 * object that is not a String/List.
965 * @throws IllegalArgumentException if one of the tokens is
966 * malformed (does not contain an equals sign).
967 */
968 public Properties getProperties(String key, Properties defaults) {
969 /*
970 * Grab an array of the tokens for this key.
971 */
972 String[] tokens = getStringArray(key);
973
974 // Each token is of the form 'key=value'.
975 Properties props = new Properties(defaults);
976 for (int i = 0; i < tokens.length; i++) {
977 String token = tokens[i];
978 int equalSign = token.indexOf('=');
979 if (equalSign > 0) {
980 String pkey = token.substring(0, equalSign).trim();
981 String pvalue = token.substring(equalSign + 1).trim();
982 props.put(pkey, pvalue);
983 } else {
984 throw new IllegalArgumentException('\'' + token + "' does not contain " + "an equals sign");
985 }
986 }
987 return props;
988 }
989
990 /**
991 * Get an array of strings associated with the given configuration
992 * key.
993 *
994 * @param key The configuration key.
995 * @return The associated string array if key is found.
996 * @throws ClassCastException is thrown if the key maps to an
997 * object that is not a String/List.
998 */
999 public String[] getStringArray(String key) {
1000 Object value = get(key);
1001
1002 List values;
1003 if (value instanceof String) {
1004 values = new Vector(1);
1005 values.add(value);
1006
1007 } else if (value instanceof List) {
1008 values = (List) value;
1009
1010 } else if (value == null) {
1011 if (defaults != null) {
1012 return defaults.getStringArray(key);
1013 } else {
1014 return new String[0];
1015 }
1016 } else {
1017 throw new ClassCastException('\'' + key + "' doesn't map to a String/List object");
1018 }
1019
1020 String[] tokens = new String[values.size()];
1021 for (int i = 0; i < tokens.length; i++) {
1022 tokens[i] = (String) values.get(i);
1023 }
1024
1025 return tokens;
1026 }
1027
1028 /**
1029 * Get a Vector of strings associated with the given configuration
1030 * key.
1031 *
1032 * @param key The configuration key.
1033 * @return The associated Vector.
1034 * @throws ClassCastException is thrown if the key maps to an
1035 * object that is not a Vector.
1036 */
1037 public Vector getVector(String key) {
1038 return getVector(key, null);
1039 }
1040
1041 /**
1042 * Get a Vector of strings associated with the given configuration key.
1043 * <p>
1044 * The list is a copy of the internal data of this object, and as
1045 * such you may alter it freely.
1046 *
1047 * @param key The configuration key.
1048 * @param defaultValue The default value.
1049 * @return The associated Vector.
1050 * @throws ClassCastException is thrown if the key maps to an
1051 * object that is not a Vector.
1052 */
1053 public Vector getVector(String key, Vector defaultValue) {
1054 Object value = get(key);
1055
1056 if (value instanceof List) {
1057 return new Vector((List) value);
1058
1059 } else if (value instanceof String) {
1060 Vector values = new Vector(1);
1061 values.add(value);
1062 put(key, values);
1063 return values;
1064
1065 } else if (value == null) {
1066 if (defaults != null) {
1067 return defaults.getVector(key, defaultValue);
1068 } else {
1069 return ((defaultValue == null) ? new Vector() : defaultValue);
1070 }
1071 } else {
1072 throw new ClassCastException('\'' + key + "' doesn't map to a Vector object");
1073 }
1074 }
1075
1076 /**
1077 * Get a List of strings associated with the given configuration key.
1078 * <p>
1079 * The list is a copy of the internal data of this object, and as
1080 * such you may alter it freely.
1081 *
1082 * @param key The configuration key.
1083 * @return The associated List object.
1084 * @throws ClassCastException is thrown if the key maps to an
1085 * object that is not a List.
1086 * @since Commons Collections 3.2
1087 */
1088 public List getList(String key) {
1089 return getList(key, null);
1090 }
1091
1092 /**
1093 * Get a List of strings associated with the given configuration key.
1094 * <p>
1095 * The list is a copy of the internal data of this object, and as
1096 * such you may alter it freely.
1097 *
1098 * @param key The configuration key.
1099 * @param defaultValue The default value.
1100 * @return The associated List.
1101 * @throws ClassCastException is thrown if the key maps to an
1102 * object that is not a List.
1103 * @since Commons Collections 3.2
1104 */
1105 public List getList(String key, List defaultValue) {
1106 Object value = get(key);
1107
1108 if (value instanceof List) {
1109 return new ArrayList((List) value);
1110
1111 } else if (value instanceof String) {
1112 List values = new ArrayList(1);
1113 values.add(value);
1114 put(key, values);
1115 return values;
1116
1117 } else if (value == null) {
1118 if (defaults != null) {
1119 return defaults.getList(key, defaultValue);
1120 } else {
1121 return ((defaultValue == null) ? new ArrayList() : defaultValue);
1122 }
1123 } else {
1124 throw new ClassCastException('\'' + key + "' doesn't map to a List object");
1125 }
1126 }
1127
1128 /**
1129 * Get a boolean associated with the given configuration key.
1130 *
1131 * @param key The configuration key.
1132 * @return The associated boolean.
1133 * @throws NoSuchElementException is thrown if the key doesn't
1134 * map to an existing object.
1135 * @throws ClassCastException is thrown if the key maps to an
1136 * object that is not a Boolean.
1137 */
1138 public boolean getBoolean(String key) {
1139 Boolean b = getBoolean(key, null);
1140 if (b != null) {
1141 return b.booleanValue();
1142 } else {
1143 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1144 }
1145 }
1146
1147 /**
1148 * Get a boolean associated with the given configuration key.
1149 *
1150 * @param key The configuration key.
1151 * @param defaultValue The default value.
1152 * @return The associated boolean.
1153 * @throws ClassCastException is thrown if the key maps to an
1154 * object that is not a Boolean.
1155 */
1156 public boolean getBoolean(String key, boolean defaultValue) {
1157 return getBoolean(key, new Boolean(defaultValue)).booleanValue();
1158 }
1159
1160 /**
1161 * Get a boolean associated with the given configuration key.
1162 *
1163 * @param key The configuration key.
1164 * @param defaultValue The default value.
1165 * @return The associated boolean if key is found and has valid
1166 * format, default value otherwise.
1167 * @throws ClassCastException is thrown if the key maps to an
1168 * object that is not a Boolean.
1169 */
1170 public Boolean getBoolean(String key, Boolean defaultValue) {
1171
1172 Object value = get(key);
1173
1174 if (value instanceof Boolean) {
1175 return (Boolean) value;
1176
1177 } else if (value instanceof String) {
1178 String s = testBoolean((String) value);
1179 Boolean b = new Boolean(s);
1180 put(key, b);
1181 return b;
1182
1183 } else if (value == null) {
1184 if (defaults != null) {
1185 return defaults.getBoolean(key, defaultValue);
1186 } else {
1187 return defaultValue;
1188 }
1189 } else {
1190 throw new ClassCastException('\'' + key + "' doesn't map to a Boolean object");
1191 }
1192 }
1193
1194 /**
1195 * Test whether the string represent by value maps to a boolean
1196 * value or not. We will allow <code>true</code>, <code>on</code>,
1197 * and <code>yes</code> for a <code>true</code> boolean value, and
1198 * <code>false</code>, <code>off</code>, and <code>no</code> for
1199 * <code>false</code> boolean values. Case of value to test for
1200 * boolean status is ignored.
1201 *
1202 * @param value the value to test for boolean state
1203 * @return <code>true</code> or <code>false</code> if the supplied
1204 * text maps to a boolean value, or <code>null</code> otherwise.
1205 */
1206 public String testBoolean(String value) {
1207 String s = value.toLowerCase();
1208
1209 if (s.equals("true") || s.equals("on") || s.equals("yes")) {
1210 return "true";
1211 } else if (s.equals("false") || s.equals("off") || s.equals("no")) {
1212 return "false";
1213 } else {
1214 return null;
1215 }
1216 }
1217
1218 /**
1219 * Get a byte associated with the given configuration key.
1220 *
1221 * @param key The configuration key.
1222 * @return The associated byte.
1223 * @throws NoSuchElementException is thrown if the key doesn't
1224 * map to an existing object.
1225 * @throws ClassCastException is thrown if the key maps to an
1226 * object that is not a Byte.
1227 * @throws NumberFormatException is thrown if the value mapped
1228 * by the key has not a valid number format.
1229 */
1230 public byte getByte(String key) {
1231 Byte b = getByte(key, null);
1232 if (b != null) {
1233 return b.byteValue();
1234 } else {
1235 throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
1236 }
1237 }
1238
1239 /**
1240 * Get a byte associated with the given configuration key.
1241 *
1242 * @param key The configuration key.
1243 * @param defaultValue The default value.
1244 * @return The associated byte.
1245 * @throws ClassCastException is thrown if the key maps to an
1246 * object that is not a Byte.
1247 * @throws NumberFormatException is thrown if the value mapped
1248 * by the key has not a valid number format.
1249 */
1250 public byte getByte(String key, byte defaultValue) {
1251 return getByte(key, new Byte(defaultValue)).byteValue();
1252 }
1253
1254 /**
1255 * Get a byte associated with the given configuration key.
1256 *
1257 * @param key The configuration key.
1258 * @param defaultValue The default value.
1259 * @return The associated byte if key is found and has valid
1260 * format, default value otherwise.
1261 * @throws ClassCastException is thrown if the key maps to an
1262 * object that is not a Byte.
1263 * @throws NumberFormatException is thrown if the value mapped
1264 * by the key has not a valid number format.
1265 */
1266 public Byte getByte(String key, Byte defaultValue) {
1267 Object value = get(key);
1268
1269 if (value instanceof Byte) {
1270 return (Byte) value;
1271
1272 } else if (value instanceof String) {
1273 Byte b = new Byte((String) value);
1274 put(key, b);
1275 return b;
1276
1277 } else if (value == null) {
1278 if (defaults != null) {
1279 return defaults.getByte(key, defaultValue);
1280 } else {
1281 return defaultValue;
1282 }
1283 } else {
1284 throw new ClassCastException('\'' + key + "' doesn't map to a Byte object");
1285 }
1286 }
1287
1288 /**
1289 * Get a short associated with the given configuration key.
1290 *
1291 * @param key The configuration key.
1292 * @return The associated short.
1293 * @throws NoSuchElementException is thrown if the key doesn't
1294 * map to an existing object.
1295 * @throws ClassCastException is thrown if the key maps to an
1296 * object that is not a Short.
1297 * @throws NumberFormatException is thrown if the value mapped
1298 * by the key has not a valid number format.
1299 */
1300 public short getShort(String key) {
1301 Short s = getShort(key, null);
1302 if (s != null) {
1303 return s.shortValue();
1304 } else {
1305 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1306 }
1307 }
1308
1309 /**
1310 * Get a short associated with the given configuration key.
1311 *
1312 * @param key The configuration key.
1313 * @param defaultValue The default value.
1314 * @return The associated short.
1315 * @throws ClassCastException is thrown if the key maps to an
1316 * object that is not a Short.
1317 * @throws NumberFormatException is thrown if the value mapped
1318 * by the key has not a valid number format.
1319 */
1320 public short getShort(String key, short defaultValue) {
1321 return getShort(key, new Short(defaultValue)).shortValue();
1322 }
1323
1324 /**
1325 * Get a short associated with the given configuration key.
1326 *
1327 * @param key The configuration key.
1328 * @param defaultValue The default value.
1329 * @return The associated short if key is found and has valid
1330 * format, default value otherwise.
1331 * @throws ClassCastException is thrown if the key maps to an
1332 * object that is not a Short.
1333 * @throws NumberFormatException is thrown if the value mapped
1334 * by the key has not a valid number format.
1335 */
1336 public Short getShort(String key, Short defaultValue) {
1337 Object value = get(key);
1338
1339 if (value instanceof Short) {
1340 return (Short) value;
1341
1342 } else if (value instanceof String) {
1343 Short s = new Short((String) value);
1344 put(key, s);
1345 return s;
1346
1347 } else if (value == null) {
1348 if (defaults != null) {
1349 return defaults.getShort(key, defaultValue);
1350 } else {
1351 return defaultValue;
1352 }
1353 } else {
1354 throw new ClassCastException('\'' + key + "' doesn't map to a Short object");
1355 }
1356 }
1357
1358 /**
1359 * The purpose of this method is to get the configuration resource
1360 * with the given name as an integer.
1361 *
1362 * @param name The resource name.
1363 * @return The value of the resource as an integer.
1364 */
1365 public int getInt(String name) {
1366 return getInteger(name);
1367 }
1368
1369 /**
1370 * The purpose of this method is to get the configuration resource
1371 * with the given name as an integer, or a default value.
1372 *
1373 * @param name The resource name
1374 * @param def The default value of the resource.
1375 * @return The value of the resource as an integer.
1376 */
1377 public int getInt(String name, int def) {
1378 return getInteger(name, def);
1379 }
1380
1381 /**
1382 * Get a int associated with the given configuration key.
1383 *
1384 * @param key The configuration key.
1385 * @return The associated int.
1386 * @throws NoSuchElementException is thrown if the key doesn't
1387 * map to an existing object.
1388 * @throws ClassCastException is thrown if the key maps to an
1389 * object that is not a Integer.
1390 * @throws NumberFormatException is thrown if the value mapped
1391 * by the key has not a valid number format.
1392 */
1393 public int getInteger(String key) {
1394 Integer i = getInteger(key, null);
1395 if (i != null) {
1396 return i.intValue();
1397 } else {
1398 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1399 }
1400 }
1401
1402 /**
1403 * Get a int associated with the given configuration key.
1404 *
1405 * @param key The configuration key.
1406 * @param defaultValue The default value.
1407 * @return The associated int.
1408 * @throws ClassCastException is thrown if the key maps to an
1409 * object that is not a Integer.
1410 * @throws NumberFormatException is thrown if the value mapped
1411 * by the key has not a valid number format.
1412 */
1413 public int getInteger(String key, int defaultValue) {
1414 Integer i = getInteger(key, null);
1415
1416 if (i == null) {
1417 return defaultValue;
1418 }
1419 return i.intValue();
1420 }
1421
1422 /**
1423 * Get a int associated with the given configuration key.
1424 *
1425 * @param key The configuration key.
1426 * @param defaultValue The default value.
1427 * @return The associated int if key is found and has valid
1428 * format, default value otherwise.
1429 * @throws ClassCastException is thrown if the key maps to an
1430 * object that is not a Integer.
1431 * @throws NumberFormatException is thrown if the value mapped
1432 * by the key has not a valid number format.
1433 */
1434 public Integer getInteger(String key, Integer defaultValue) {
1435 Object value = get(key);
1436
1437 if (value instanceof Integer) {
1438 return (Integer) value;
1439
1440 } else if (value instanceof String) {
1441 Integer i = new Integer((String) value);
1442 put(key, i);
1443 return i;
1444
1445 } else if (value == null) {
1446 if (defaults != null) {
1447 return defaults.getInteger(key, defaultValue);
1448 } else {
1449 return defaultValue;
1450 }
1451 } else {
1452 throw new ClassCastException('\'' + key + "' doesn't map to a Integer object");
1453 }
1454 }
1455
1456 /**
1457 * Get a long associated with the given configuration key.
1458 *
1459 * @param key The configuration key.
1460 * @return The associated long.
1461 * @throws NoSuchElementException is thrown if the key doesn't
1462 * map to an existing object.
1463 * @throws ClassCastException is thrown if the key maps to an
1464 * object that is not a Long.
1465 * @throws NumberFormatException is thrown if the value mapped
1466 * by the key has not a valid number format.
1467 */
1468 public long getLong(String key) {
1469 Long l = getLong(key, null);
1470 if (l != null) {
1471 return l.longValue();
1472 } else {
1473 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1474 }
1475 }
1476
1477 /**
1478 * Get a long associated with the given configuration key.
1479 *
1480 * @param key The configuration key.
1481 * @param defaultValue The default value.
1482 * @return The associated long.
1483 * @throws ClassCastException is thrown if the key maps to an
1484 * object that is not a Long.
1485 * @throws NumberFormatException is thrown if the value mapped
1486 * by the key has not a valid number format.
1487 */
1488 public long getLong(String key, long defaultValue) {
1489 return getLong(key, new Long(defaultValue)).longValue();
1490 }
1491
1492 /**
1493 * Get a long associated with the given configuration key.
1494 *
1495 * @param key The configuration key.
1496 * @param defaultValue The default value.
1497 * @return The associated long if key is found and has valid
1498 * format, default value otherwise.
1499 * @throws ClassCastException is thrown if the key maps to an
1500 * object that is not a Long.
1501 * @throws NumberFormatException is thrown if the value mapped
1502 * by the key has not a valid number format.
1503 */
1504 public Long getLong(String key, Long defaultValue) {
1505 Object value = get(key);
1506
1507 if (value instanceof Long) {
1508 return (Long) value;
1509
1510 } else if (value instanceof String) {
1511 Long l = new Long((String) value);
1512 put(key, l);
1513 return l;
1514
1515 } else if (value == null) {
1516 if (defaults != null) {
1517 return defaults.getLong(key, defaultValue);
1518 } else {
1519 return defaultValue;
1520 }
1521 } else {
1522 throw new ClassCastException('\'' + key + "' doesn't map to a Long object");
1523 }
1524 }
1525
1526 /**
1527 * Get a float associated with the given configuration key.
1528 *
1529 * @param key The configuration key.
1530 * @return The associated float.
1531 * @throws NoSuchElementException is thrown if the key doesn't
1532 * map to an existing object.
1533 * @throws ClassCastException is thrown if the key maps to an
1534 * object that is not a Float.
1535 * @throws NumberFormatException is thrown if the value mapped
1536 * by the key has not a valid number format.
1537 */
1538 public float getFloat(String key) {
1539 Float f = getFloat(key, null);
1540 if (f != null) {
1541 return f.floatValue();
1542 } else {
1543 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1544 }
1545 }
1546
1547 /**
1548 * Get a float associated with the given configuration key.
1549 *
1550 * @param key The configuration key.
1551 * @param defaultValue The default value.
1552 * @return The associated float.
1553 * @throws ClassCastException is thrown if the key maps to an
1554 * object that is not a Float.
1555 * @throws NumberFormatException is thrown if the value mapped
1556 * by the key has not a valid number format.
1557 */
1558 public float getFloat(String key, float defaultValue) {
1559 return getFloat(key, new Float(defaultValue)).floatValue();
1560 }
1561
1562 /**
1563 * Get a float associated with the given configuration key.
1564 *
1565 * @param key The configuration key.
1566 * @param defaultValue The default value.
1567 * @return The associated float if key is found and has valid
1568 * format, default value otherwise.
1569 * @throws ClassCastException is thrown if the key maps to an
1570 * object that is not a Float.
1571 * @throws NumberFormatException is thrown if the value mapped
1572 * by the key has not a valid number format.
1573 */
1574 public Float getFloat(String key, Float defaultValue) {
1575 Object value = get(key);
1576
1577 if (value instanceof Float) {
1578 return (Float) value;
1579
1580 } else if (value instanceof String) {
1581 Float f = new Float((String) value);
1582 put(key, f);
1583 return f;
1584
1585 } else if (value == null) {
1586 if (defaults != null) {
1587 return defaults.getFloat(key, defaultValue);
1588 } else {
1589 return defaultValue;
1590 }
1591 } else {
1592 throw new ClassCastException('\'' + key + "' doesn't map to a Float object");
1593 }
1594 }
1595
1596 /**
1597 * Get a double associated with the given configuration key.
1598 *
1599 * @param key The configuration key.
1600 * @return The associated double.
1601 * @throws NoSuchElementException is thrown if the key doesn't
1602 * map to an existing object.
1603 * @throws ClassCastException is thrown if the key maps to an
1604 * object that is not a Double.
1605 * @throws NumberFormatException is thrown if the value mapped
1606 * by the key has not a valid number format.
1607 */
1608 public double getDouble(String key) {
1609 Double d = getDouble(key, null);
1610 if (d != null) {
1611 return d.doubleValue();
1612 } else {
1613 throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
1614 }
1615 }
1616
1617 /**
1618 * Get a double associated with the given configuration key.
1619 *
1620 * @param key The configuration key.
1621 * @param defaultValue The default value.
1622 * @return The associated double.
1623 * @throws ClassCastException is thrown if the key maps to an
1624 * object that is not a Double.
1625 * @throws NumberFormatException is thrown if the value mapped
1626 * by the key has not a valid number format.
1627 */
1628 public double getDouble(String key, double defaultValue) {
1629 return getDouble(key, new Double(defaultValue)).doubleValue();
1630 }
1631
1632 /**
1633 * Get a double associated with the given configuration key.
1634 *
1635 * @param key The configuration key.
1636 * @param defaultValue The default value.
1637 * @return The associated double if key is found and has valid
1638 * format, default value otherwise.
1639 * @throws ClassCastException is thrown if the key maps to an
1640 * object that is not a Double.
1641 * @throws NumberFormatException is thrown if the value mapped
1642 * by the key has not a valid number format.
1643 */
1644 public Double getDouble(String key, Double defaultValue) {
1645 Object value = get(key);
1646
1647 if (value instanceof Double) {
1648 return (Double) value;
1649
1650 } else if (value instanceof String) {
1651 Double d = new Double((String) value);
1652 put(key, d);
1653 return d;
1654
1655 } else if (value == null) {
1656 if (defaults != null) {
1657 return defaults.getDouble(key, defaultValue);
1658 } else {
1659 return defaultValue;
1660 }
1661 } else {
1662 throw new ClassCastException('\'' + key + "' doesn't map to a Double object");
1663 }
1664 }
1665
1666 /**
1667 * Convert a standard properties class into a configuration class.
1668 * <p>
1669 * NOTE: From Commons Collections 3.2 this method will pick up
1670 * any default parent Properties of the specified input object.
1671 *
1672 * @param props the properties object to convert
1673 * @return new ExtendedProperties created from props
1674 */
1675 public static ExtendedProperties convertProperties(Properties props) {
1676 ExtendedProperties c = new ExtendedProperties();
1677
1678 for (Enumeration e = props.propertyNames(); e.hasMoreElements();) {
1679 String s = (String) e.nextElement();
1680 c.setProperty(s, props.getProperty(s));
1681 }
1682
1683 return c;
1684 }
1685
1686 }