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 }