GNU Classpath (0.17) | ||
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. 213: line = reader.readLine(); 214: pos = 0; 215: while (pos < line.length() 216: && Character.isWhitespace(c = line.charAt(pos))) 217: pos++; 218: } 219: else 220: { 221: c = line.charAt(pos++); 222: switch (c) 223: { 224: case 'n': 225: key.append('\n'); 226: break; 227: case 't': 228: key.append('\t'); 229: break; 230: case 'r': 231: key.append('\r'); 232: break; 233: case 'u': 234: if (pos + 4 <= line.length()) 235: { 236: char uni = (char) Integer.parseInt 237: (line.substring(pos, pos + 4), 16); 238: key.append(uni); 239: pos += 4; 240: } // else throw exception? 241: break; 242: default: 243: key.append(c); 244: break; 245: } 246: } 247: } 248: else 249: key.append(c); 250: } 251: 252: boolean isDelim = (c == ':' || c == '='); 253: while (pos < line.length() 254: && Character.isWhitespace(c = line.charAt(pos))) 255: pos++; 256: 257: if (! isDelim && (c == ':' || c == '=')) 258: { 259: pos++; 260: while (pos < line.length() 261: && Character.isWhitespace(c = line.charAt(pos))) 262: pos++; 263: } 264: 265: StringBuffer element = new StringBuffer(line.length() - pos); 266: while (pos < line.length()) 267: { 268: c = line.charAt(pos++); 269: if (c == '\\') 270: { 271: if (pos == line.length()) 272: { 273: // The line continues on the next line. 274: line = reader.readLine(); 275: 276: // We might have seen a backslash at the end of 277: // the file. The JDK ignores the backslash in 278: // this case, so we follow for compatibility. 279: if (line == null) 280: break; 281: 282: pos = 0; 283: while (pos < line.length() 284: && Character.isWhitespace(c = line.charAt(pos))) 285: pos++; 286: element.ensureCapacity(line.length() - pos + 287: element.length()); 288: } 289: else 290: { 291: c = line.charAt(pos++); 292: switch (c) 293: { 294: case 'n': 295: element.append('\n'); 296: break; 297: case 't': 298: element.append('\t'); 299: break; 300: case 'r': 301: element.append('\r'); 302: break; 303: case 'u': 304: if (pos + 4 <= line.length()) 305: { 306: char uni = (char) Integer.parseInt 307: (line.substring(pos, pos + 4), 16); 308: element.append(uni); 309: pos += 4; 310: } // else throw exception? 311: break; 312: default: 313: element.append(c); 314: break; 315: } 316: } 317: } 318: else 319: element.append(c); 320: } 321: put(key.toString(), element.toString()); 322: } 323: } 324: 325: /** 326: * Calls <code>store(OutputStream out, String header)</code> and 327: * ignores the IOException that may be thrown. 328: * 329: * @param out the stream to write to 330: * @param header a description of the property list 331: * @throws ClassCastException if this property contains any key or 332: * value that are not strings 333: * @deprecated use {@link #store(OutputStream, String)} instead 334: */ 335: public void save(OutputStream out, String header) 336: { 337: try 338: { 339: store(out, header); 340: } 341: catch (IOException ex) 342: { 343: } 344: } 345: 346: /** 347: * Writes the key/value pairs to the given output stream, in a format 348: * suitable for <code>load</code>.<br> 349: * 350: * If header is not null, this method writes a comment containing 351: * the header as first line to the stream. The next line (or first 352: * line if header is null) contains a comment with the current date. 353: * Afterwards the key/value pairs are written to the stream in the 354: * following format.<br> 355: * 356: * Each line has the form <code>key = value</code>. Newlines, 357: * Returns and tabs are written as <code>\n,\t,\r</code> resp. 358: * The characters <code>\, !, #, =</code> and <code>:</code> are 359: * preceeded by a backslash. Spaces are preceded with a backslash, 360: * if and only if they are at the beginning of the key. Characters 361: * that are not in the ascii range 33 to 127 are written in the 362: * <code>\</code><code>u</code>xxxx Form.<br> 363: * 364: * Following the listing, the output stream is flushed but left open. 365: * 366: * @param out the output stream 367: * @param header the header written in the first line, may be null 368: * @throws ClassCastException if this property contains any key or 369: * value that isn't a string 370: * @throws IOException if writing to the stream fails 371: * @throws NullPointerException if out is null 372: * @since 1.2 373: */ 374: public void store(OutputStream out, String header) throws IOException 375: { 376: // The spec says that the file must be encoded using ISO-8859-1. 377: PrintWriter writer 378: = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1")); 379: if (header != null) 380: writer.println("#" + header); 381: writer.println ("#" + Calendar.getInstance ().getTime ()); 382: 383: Iterator iter = entrySet ().iterator (); 384: int i = size (); 385: StringBuffer s = new StringBuffer (); // Reuse the same buffer. 386: while (--i >= 0) 387: { 388: Map.Entry entry = (Map.Entry) iter.next (); 389: formatForOutput ((String) entry.getKey (), s, true); 390: s.append ('='); 391: formatForOutput ((String) entry.getValue (), s, false); 392: writer.println (s); 393: } 394: 395: writer.flush (); 396: } 397: 398: /** 399: * Gets the property with the specified key in this property list. 400: * If the key is not found, the default property list is searched. 401: * If the property is not found in the default, null is returned. 402: * 403: * @param key The key for this property 404: * @return the value for the given key, or null if not found 405: * @throws ClassCastException if this property contains any key or 406: * value that isn't a string 407: * @see #defaults 408: * @see #setProperty(String, String) 409: * @see #getProperty(String, String) 410: */ 411: public String getProperty(String key) 412: { 413: return getProperty(key, null); 414: } 415: 416: /** 417: * Gets the property with the specified key in this property list. If 418: * the key is not found, the default property list is searched. If the 419: * property is not found in the default, the specified defaultValue is 420: * returned. 421: * 422: * @param key The key for this property 423: * @param defaultValue A default value 424: * @return The value for the given key 425: * @throws ClassCastException if this property contains any key or 426: * value that isn't a string 427: * @see #defaults 428: * @see #setProperty(String, String) 429: */ 430: public String getProperty(String key, String defaultValue) 431: { 432: Properties prop = this; 433: // Eliminate tail recursion. 434: do 435: { 436: String value = (String) prop.get(key); 437: if (value != null) 438: return value; 439: prop = prop.defaults; 440: } 441: while (prop != null); 442: return defaultValue; 443: } 444: 445: /** 446: * Returns an enumeration of all keys in this property list, including 447: * the keys in the default property list. 448: * 449: * @return an Enumeration of all defined keys 450: */ 451: public Enumeration propertyNames() 452: { 453: // We make a new Set that holds all the keys, then return an enumeration 454: // for that. This prevents modifications from ruining the enumeration, 455: // as well as ignoring duplicates. 456: Properties prop = this; 457: Set s = new HashSet(); 458: // Eliminate tail recursion. 459: do 460: { 461: s.addAll(prop.keySet()); 462: prop = prop.defaults; 463: } 464: while (prop != null); 465: return Collections.enumeration(s); 466: } 467: 468: /** 469: * Prints the key/value pairs to the given print stream. This is 470: * mainly useful for debugging purposes. 471: * 472: * @param out the print stream, where the key/value pairs are written to 473: * @throws ClassCastException if this property contains a key or a 474: * value that isn't a string 475: * @see #list(PrintWriter) 476: */ 477: public void list(PrintStream out) 478: { 479: PrintWriter writer = new PrintWriter (out); 480: list (writer); 481: } 482: 483: /** 484: * Prints the key/value pairs to the given print writer. This is 485: * mainly useful for debugging purposes. 486: * 487: * @param out the print writer where the key/value pairs are written to 488: * @throws ClassCastException if this property contains a key or a 489: * value that isn't a string 490: * @see #list(PrintStream) 491: * @since 1.1 492: */ 493: public void list(PrintWriter out) 494: { 495: out.println ("-- listing properties --"); 496: 497: Iterator iter = entrySet ().iterator (); 498: int i = size (); 499: while (--i >= 0) 500: { 501: Map.Entry entry = (Map.Entry) iter.next (); 502: out.print ((String) entry.getKey () + "="); 503: 504: // JDK 1.3/1.4 restrict the printed value, but not the key, 505: // to 40 characters, including the truncating ellipsis. 506: String s = (String ) entry.getValue (); 507: if (s != null && s.length () > 40) 508: out.println (s.substring (0, 37) + "..."); 509: else 510: out.println (s); 511: } 512: out.flush (); 513: } 514: 515: /** 516: * Formats a key or value for output in a properties file. 517: * See store for a description of the format. 518: * 519: * @param str the string to format 520: * @param buffer the buffer to add it to 521: * @param key true if all ' ' must be escaped for the key, false if only 522: * leading spaces must be escaped for the value 523: * @see #store(OutputStream, String) 524: */ 525: private void formatForOutput(String str, StringBuffer buffer, boolean key) 526: { 527: if (key) 528: { 529: buffer.setLength(0); 530: buffer.ensureCapacity(str.length()); 531: } 532: else 533: buffer.ensureCapacity(buffer.length() + str.length()); 534: boolean head = true; 535: int size = str.length(); 536: for (int i = 0; i < size; i++) 537: { 538: char c = str.charAt(i); 539: switch (c) 540: { 541: case '\n': 542: buffer.append("\\n"); 543: break; 544: case '\r': 545: buffer.append("\\r"); 546: break; 547: case '\t': 548: buffer.append("\\t"); 549: break; 550: case ' ': 551: buffer.append(head ? "\\ " : " "); 552: break; 553: case '\\': 554: case '!': 555: case '#': 556: case '=': 557: case ':': 558: buffer.append('\\').append(c); 559: break; 560: default: 561: if (c < ' ' || c > '~') 562: { 563: String hex = Integer.toHexString(c); 564: buffer.append("\\u0000".substring(0, 6 - hex.length())); 565: buffer.append(hex); 566: } 567: else 568: buffer.append(c); 569: } 570: if (c != ' ') 571: head = key; 572: } 573: } 574: } // class Properties
GNU Classpath (0.17) |