GNU Classpath (0.18) | ||
Frames | No Frames |
1: /* Properties.java -- a set of persistent properties 2: Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package java.util; 40: 41: import java.io.BufferedReader; 42: import java.io.IOException; 43: import java.io.InputStream; 44: import java.io.InputStreamReader; 45: import java.io.OutputStream; 46: import java.io.OutputStreamWriter; 47: import java.io.PrintStream; 48: import java.io.PrintWriter; 49: 50: /** 51: * A set of persistent properties, which can be saved or loaded from a stream. 52: * A property list may also contain defaults, searched if the main list 53: * does not contain a property for a given key. 54: * 55: * An example of a properties file for the german language is given 56: * here. This extends the example given in ListResourceBundle. 57: * Create a file MyResource_de.properties with the following contents 58: * and put it in the CLASSPATH. (The character 59: * <code>\</code><code>u00e4</code> is the german umlaut) 60: * 61: * 62: <pre>s1=3 63: s2=MeineDisk 64: s3=3. M\<code></code>u00e4rz 96 65: s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}. 66: s5=0 67: s6=keine Dateien 68: s7=1 69: s8=eine Datei 70: s9=2 71: s10={0,number} Dateien 72: s11=Das Formatieren schlug fehl mit folgender Exception: {0} 73: s12=FEHLER 74: s13=Ergebnis 75: s14=Dialog 76: s15=Auswahlkriterium 77: s16=1,3</pre> 78: * 79: * <p>Although this is a sub class of a hash table, you should never 80: * insert anything other than strings to this property, or several 81: * methods, that need string keys and values, will fail. To ensure 82: * this, you should use the <code>get/setProperty</code> method instead 83: * of <code>get/put</code>. 84: * 85: * Properties are saved in ISO 8859-1 encoding, using Unicode escapes with 86: * a single <code>u</code> for any character which cannot be represented. 87: * 88: * @author Jochen Hoenicke 89: * @author Eric Blake (ebb9@email.byu.edu) 90: * @see PropertyResourceBundle 91: * @status updated to 1.4 92: */ 93: public class Properties extends Hashtable 94: { 95: // WARNING: Properties is a CORE class in the bootstrap cycle. See the 96: // comments in vm/reference/java/lang/Runtime for implications of this fact. 97: 98: /** 99: * The property list that contains default values for any keys not 100: * in this property list. 101: * 102: * @serial the default properties 103: */ 104: protected Properties defaults; 105: 106: /** 107: * Compatible with JDK 1.0+. 108: */ 109: private static final long serialVersionUID = 4112578634029874840L; 110: 111: /** 112: * Creates a new empty property list with no default values. 113: */ 114: public Properties() 115: { 116: } 117: 118: /** 119: * Create a new empty property list with the specified default values. 120: * 121: * @param defaults a Properties object containing the default values 122: */ 123: public Properties(Properties defaults) 124: { 125: this.defaults = defaults; 126: } 127: 128: /** 129: * Adds the given key/value pair to this properties. This calls 130: * the hashtable method put. 131: * 132: * @param key the key for this property 133: * @param value the value for this property 134: * @return The old value for the given key 135: * @see #getProperty(String) 136: * @since 1.2 137: */ 138: public Object setProperty(String key, String value) 139: { 140: return put(key, value); 141: } 142: 143: /** 144: * Reads a property list from an input stream. The stream should 145: * have the following format: <br> 146: * 147: * An empty line or a line starting with <code>#</code> or 148: * <code>!</code> is ignored. An backslash (<code>\</code>) at the 149: * end of the line makes the line continueing on the next line 150: * (but make sure there is no whitespace after the backslash). 151: * Otherwise, each line describes a key/value pair. <br> 152: * 153: * The chars up to the first whitespace, = or : are the key. You 154: * can include this caracters in the key, if you precede them with 155: * a backslash (<code>\</code>). The key is followed by optional 156: * whitespaces, optionally one <code>=</code> or <code>:</code>, 157: * and optionally some more whitespaces. The rest of the line is 158: * the resource belonging to the key. <br> 159: * 160: * Escape sequences <code>\t, \n, \r, \\, \", \', \!, \#, \ </code>(a 161: * space), and unicode characters with the 162: * <code>\\u</code><em>xxxx</em> notation are detected, and 163: * converted to the corresponding single character. <br> 164: * 165: * 166: <pre># This is a comment 167: key = value 168: k\:5 \ a string starting with space and ending with newline\n 169: # This is a multiline specification; note that the value contains 170: # no white space. 171: weekdays: Sunday,Monday,Tuesday,Wednesday,\\ 172: Thursday,Friday,Saturday 173: # The safest way to include a space at the end of a value: 174: label = Name:\\u0020</pre> 175: * 176: * @param inStream the input stream 177: * @throws IOException if an error occurred when reading the input 178: * @throws NullPointerException if in is null 179: */ 180: public void load(InputStream inStream) throws IOException 181: { 182: // The spec says that the file must be encoded using ISO-8859-1. 183: BufferedReader reader = 184: new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1")); 185: String line; 186: 187: while ((line = reader.readLine()) != null) 188: { 189: char c = 0; 190: int pos = 0; 191: // Leading whitespaces must be deleted first. 192: while (pos < line.length() 193: && Character.isWhitespace(c = line.charAt(pos))) 194: pos++; 195: 196: // If empty line or begins with a comment character, skip this line. 197: if ((line.length() - pos) == 0 198: || line.charAt(pos) == '#' || line.charAt(pos) == '!') 199: continue; 200: 201: // The characters up to the next Whitespace, ':', or '=' 202: // describe the key. But look for escape sequences. 203: StringBuffer key = new StringBuffer(); 204: while (pos < line.length() 205: && ! Character.isWhitespace(c = line.charAt(pos++)) 206: && c != '=' && c != ':') 207: { 208: if (c == '\\') 209: { 210: if (pos == line.length()) 211: { 212: // The line continues on the next line. If there 213: // is no next line, just treat it as a key with an 214: // empty value. 215: line = reader.readLine(); 216: if (line == null) 217: line = ""; 218: pos = 0; 219: while (pos < line.length() 220: && Character.isWhitespace(c = line.charAt(pos))) 221: pos++; 222: } 223: else 224: { 225: c = line.charAt(pos++); 226: switch (c) 227: { 228: case 'n': 229: key.append('\n'); 230: break; 231: case 't': 232: key.append('\t'); 233: break; 234: case 'r': 235: key.append('\r'); 236: break; 237: case 'u': 238: if (pos + 4 <= line.length()) 239: { 240: char uni = (char) Integer.parseInt 241: (line.substring(pos, pos + 4), 16); 242: key.append(uni); 243: pos += 4; 244: } // else throw exception? 245: break; 246: default: 247: key.append(c); 248: break; 249: } 250: } 251: } 252: else 253: key.append(c); 254: } 255: 256: boolean isDelim = (c == ':' || c == '='); 257: while (pos < line.length() 258: && Character.isWhitespace(c = line.charAt(pos))) 259: pos++; 260: 261: if (! isDelim && (c == ':' || c == '=')) 262: { 263: pos++; 264: while (pos < line.length() 265: && Character.isWhitespace(c = line.charAt(pos))) 266: pos++; 267: } 268: 269: StringBuffer element = new StringBuffer(line.length() - pos); 270: while (pos < line.length()) 271: { 272: c = line.charAt(pos++); 273: if (c == '\\') 274: { 275: if (pos == line.length()) 276: { 277: // The line continues on the next line. 278: line = reader.readLine(); 279: 280: // We might have seen a backslash at the end of 281: // the file. The JDK ignores the backslash in 282: // this case, so we follow for compatibility. 283: if (line == null) 284: break; 285: 286: pos = 0; 287: while (pos < line.length() 288: && Character.isWhitespace(c = line.charAt(pos))) 289: pos++; 290: element.ensureCapacity(line.length() - pos + 291: element.length()); 292: } 293: else 294: { 295: c = line.charAt(pos++); 296: switch (c) 297: { 298: case 'n': 299: element.append('\n'); 300: break; 301: case 't': 302: element.append('\t'); 303: break; 304: case 'r': 305: element.append('\r'); 306: break; 307: case 'u': 308: if (pos + 4 <= line.length()) 309: { 310: char uni = (char) Integer.parseInt 311: (line.substring(pos, pos + 4), 16); 312: element.append(uni); 313: pos += 4; 314: } // else throw exception? 315: break; 316: default: 317: element.append(c); 318: break; 319: } 320: } 321: } 322: else 323: element.append(c); 324: } 325: put(key.toString(), element.toString()); 326: } 327: } 328: 329: /** 330: * Calls <code>store(OutputStream out, String header)</code> and 331: * ignores the IOException that may be thrown. 332: * 333: * @param out the stream to write to 334: * @param header a description of the property list 335: * @throws ClassCastException if this property contains any key or 336: * value that are not strings 337: * @deprecated use {@link #store(OutputStream, String)} instead 338: */ 339: public void save(OutputStream out, String header) 340: { 341: try 342: { 343: store(out, header); 344: } 345: catch (IOException ex) 346: { 347: } 348: } 349: 350: /** 351: * Writes the key/value pairs to the given output stream, in a format 352: * suitable for <code>load</code>.<br> 353: * 354: * If header is not null, this method writes a comment containing 355: * the header as first line to the stream. The next line (or first 356: * line if header is null) contains a comment with the current date. 357: * Afterwards the key/value pairs are written to the stream in the 358: * following format.<br> 359: * 360: * Each line has the form <code>key = value</code>. Newlines, 361: * Returns and tabs are written as <code>\n,\t,\r</code> resp. 362: * The characters <code>\, !, #, =</code> and <code>:</code> are 363: * preceeded by a backslash. Spaces are preceded with a backslash, 364: * if and only if they are at the beginning of the key. Characters 365: * that are not in the ascii range 33 to 127 are written in the 366: * <code>\</code><code>u</code>xxxx Form.<br> 367: * 368: * Following the listing, the output stream is flushed but left open. 369: * 370: * @param out the output stream 371: * @param header the header written in the first line, may be null 372: * @throws ClassCastException if this property contains any key or 373: * value that isn't a string 374: * @throws IOException if writing to the stream fails 375: * @throws NullPointerException if out is null 376: * @since 1.2 377: */ 378: public void store(OutputStream out, String header) throws IOException 379: { 380: // The spec says that the file must be encoded using ISO-8859-1. 381: PrintWriter writer 382: = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1")); 383: if (header != null) 384: writer.println("#" + header); 385: writer.println ("#" + Calendar.getInstance ().getTime ()); 386: 387: Iterator iter = entrySet ().iterator (); 388: int i = size (); 389: StringBuffer s = new StringBuffer (); // Reuse the same buffer. 390: while (--i >= 0) 391: { 392: Map.Entry entry = (Map.Entry) iter.next (); 393: formatForOutput ((String) entry.getKey (), s, true); 394: s.append ('='); 395: formatForOutput ((String) entry.getValue (), s, false); 396: writer.println (s); 397: } 398: 399: writer.flush (); 400: } 401: 402: /** 403: * Gets the property with the specified key in this property list. 404: * If the key is not found, the default property list is searched. 405: * If the property is not found in the default, null is returned. 406: * 407: * @param key The key for this property 408: * @return the value for the given key, or null if not found 409: * @throws ClassCastException if this property contains any key or 410: * value that isn't a string 411: * @see #defaults 412: * @see #setProperty(String, String) 413: * @see #getProperty(String, String) 414: */ 415: public String getProperty(String key) 416: { 417: Properties prop = this; 418: // Eliminate tail recursion. 419: do 420: { 421: String value = (String) prop.get(key); 422: if (value != null) 423: return value; 424: prop = prop.defaults; 425: } 426: while (prop != null); 427: return null; 428: } 429: 430: /** 431: * Gets the property with the specified key in this property list. If 432: * the key is not found, the default property list is searched. If the 433: * property is not found in the default, the specified defaultValue is 434: * returned. 435: * 436: * @param key The key for this property 437: * @param defaultValue A default value 438: * @return The value for the given key 439: * @throws ClassCastException if this property contains any key or 440: * value that isn't a string 441: * @see #defaults 442: * @see #setProperty(String, String) 443: */ 444: public String getProperty(String key, String defaultValue) 445: { 446: String prop = getProperty(key); 447: if (prop == null) 448: prop = defaultValue; 449: return prop; 450: } 451: 452: /** 453: * Returns an enumeration of all keys in this property list, including 454: * the keys in the default property list. 455: * 456: * @return an Enumeration of all defined keys 457: */ 458: public Enumeration propertyNames() 459: { 460: // We make a new Set that holds all the keys, then return an enumeration 461: // for that. This prevents modifications from ruining the enumeration, 462: // as well as ignoring duplicates. 463: Properties prop = this; 464: Set s = new HashSet(); 465: // Eliminate tail recursion. 466: do 467: { 468: s.addAll(prop.keySet()); 469: prop = prop.defaults; 470: } 471: while (prop != null); 472: return Collections.enumeration(s); 473: } 474: 475: /** 476: * Prints the key/value pairs to the given print stream. This is 477: * mainly useful for debugging purposes. 478: * 479: * @param out the print stream, where the key/value pairs are written to 480: * @throws ClassCastException if this property contains a key or a 481: * value that isn't a string 482: * @see #list(PrintWriter) 483: */ 484: public void list(PrintStream out) 485: { 486: PrintWriter writer = new PrintWriter (out); 487: list (writer); 488: } 489: 490: /** 491: * Prints the key/value pairs to the given print writer. This is 492: * mainly useful for debugging purposes. 493: * 494: * @param out the print writer where the key/value pairs are written to 495: * @throws ClassCastException if this property contains a key or a 496: * value that isn't a string 497: * @see #list(PrintStream) 498: * @since 1.1 499: */ 500: public void list(PrintWriter out) 501: { 502: out.println ("-- listing properties --"); 503: 504: Iterator iter = entrySet ().iterator (); 505: int i = size (); 506: while (--i >= 0) 507: { 508: Map.Entry entry = (Map.Entry) iter.next (); 509: out.print ((String) entry.getKey () + "="); 510: 511: // JDK 1.3/1.4 restrict the printed value, but not the key, 512: // to 40 characters, including the truncating ellipsis. 513: String s = (String ) entry.getValue (); 514: if (s != null && s.length () > 40) 515: out.println (s.substring (0, 37) + "..."); 516: else 517: out.println (s); 518: } 519: out.flush (); 520: } 521: 522: /** 523: * Formats a key or value for output in a properties file. 524: * See store for a description of the format. 525: * 526: * @param str the string to format 527: * @param buffer the buffer to add it to 528: * @param key true if all ' ' must be escaped for the key, false if only 529: * leading spaces must be escaped for the value 530: * @see #store(OutputStream, String) 531: */ 532: private void formatForOutput(String str, StringBuffer buffer, boolean key) 533: { 534: if (key) 535: { 536: buffer.setLength(0); 537: buffer.ensureCapacity(str.length()); 538: } 539: else 540: buffer.ensureCapacity(buffer.length() + str.length()); 541: boolean head = true; 542: int size = str.length(); 543: for (int i = 0; i < size; i++) 544: { 545: char c = str.charAt(i); 546: switch (c) 547: { 548: case '\n': 549: buffer.append("\\n"); 550: break; 551: case '\r': 552: buffer.append("\\r"); 553: break; 554: case '\t': 555: buffer.append("\\t"); 556: break; 557: case ' ': 558: buffer.append(head ? "\\ " : " "); 559: break; 560: case '\\': 561: case '!': 562: case '#': 563: case '=': 564: case ':': 565: buffer.append('\\').append(c); 566: break; 567: default: 568: if (c < ' ' || c > '~') 569: { 570: String hex = Integer.toHexString(c); 571: buffer.append("\\u0000".substring(0, 6 - hex.length())); 572: buffer.append(hex); 573: } 574: else 575: buffer.append(c); 576: } 577: if (c != ' ') 578: head = key; 579: } 580: } 581: } // class Properties
GNU Classpath (0.18) |