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