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: import javax.xml.parsers.ParserConfigurationException; 51: import javax.xml.parsers.SAXParser; 52: import javax.xml.parsers.SAXParserFactory; 53: 54: import org.xml.sax.Attributes; 55: import org.xml.sax.InputSource; 56: import org.xml.sax.SAXException; 57: import org.xml.sax.XMLReader; 58: import org.xml.sax.ext.DefaultHandler2; 59: 60: import org.w3c.dom.Document; 61: import org.w3c.dom.DocumentType; 62: import org.w3c.dom.DOMImplementation; 63: import org.w3c.dom.Element; 64: import org.w3c.dom.bootstrap.DOMImplementationRegistry; 65: import org.w3c.dom.ls.DOMImplementationLS; 66: import org.w3c.dom.ls.LSOutput; 67: import org.w3c.dom.ls.LSSerializer; 68: 69: /** 70: * A set of persistent properties, which can be saved or loaded from a stream. 71: * A property list may also contain defaults, searched if the main list 72: * does not contain a property for a given key. 73: * 74: * An example of a properties file for the german language is given 75: * here. This extends the example given in ListResourceBundle. 76: * Create a file MyResource_de.properties with the following contents 77: * and put it in the CLASSPATH. (The character 78: * <code>\</code><code>u00e4</code> is the german umlaut) 79: * 80: * 81: <pre>s1=3 82: s2=MeineDisk 83: s3=3. M\<code></code>u00e4rz 96 84: s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}. 85: s5=0 86: s6=keine Dateien 87: s7=1 88: s8=eine Datei 89: s9=2 90: s10={0,number} Dateien 91: s11=Das Formatieren schlug fehl mit folgender Exception: {0} 92: s12=FEHLER 93: s13=Ergebnis 94: s14=Dialog 95: s15=Auswahlkriterium 96: s16=1,3</pre> 97: * 98: * <p>Although this is a sub class of a hash table, you should never 99: * insert anything other than strings to this property, or several 100: * methods, that need string keys and values, will fail. To ensure 101: * this, you should use the <code>get/setProperty</code> method instead 102: * of <code>get/put</code>. 103: * 104: * Properties are saved in ISO 8859-1 encoding, using Unicode escapes with 105: * a single <code>u</code> for any character which cannot be represented. 106: * 107: * @author Jochen Hoenicke 108: * @author Eric Blake (ebb9@email.byu.edu) 109: * @see PropertyResourceBundle 110: * @status updated to 1.4 111: */ 112: public class Properties extends Hashtable 113: { 114: // WARNING: Properties is a CORE class in the bootstrap cycle. See the 115: // comments in vm/reference/java/lang/Runtime for implications of this fact. 116: 117: /** 118: * The property list that contains default values for any keys not 119: * in this property list. 120: * 121: * @serial the default properties 122: */ 123: protected Properties defaults; 124: 125: /** 126: * Compatible with JDK 1.0+. 127: */ 128: private static final long serialVersionUID = 4112578634029874840L; 129: 130: /** 131: * Creates a new empty property list with no default values. 132: */ 133: public Properties() 134: { 135: } 136: 137: /** 138: * Create a new empty property list with the specified default values. 139: * 140: * @param defaults a Properties object containing the default values 141: */ 142: public Properties(Properties defaults) 143: { 144: this.defaults = defaults; 145: } 146: 147: /** 148: * Adds the given key/value pair to this properties. This calls 149: * the hashtable method put. 150: * 151: * @param key the key for this property 152: * @param value the value for this property 153: * @return The old value for the given key 154: * @see #getProperty(String) 155: * @since 1.2 156: */ 157: public Object setProperty(String key, String value) 158: { 159: return put(key, value); 160: } 161: 162: /** 163: * Reads a property list from an input stream. The stream should 164: * have the following format: <br> 165: * 166: * An empty line or a line starting with <code>#</code> or 167: * <code>!</code> is ignored. An backslash (<code>\</code>) at the 168: * end of the line makes the line continueing on the next line 169: * (but make sure there is no whitespace after the backslash). 170: * Otherwise, each line describes a key/value pair. <br> 171: * 172: * The chars up to the first whitespace, = or : are the key. You 173: * can include this caracters in the key, if you precede them with 174: * a backslash (<code>\</code>). The key is followed by optional 175: * whitespaces, optionally one <code>=</code> or <code>:</code>, 176: * and optionally some more whitespaces. The rest of the line is 177: * the resource belonging to the key. <br> 178: * 179: * Escape sequences <code>\t, \n, \r, \\, \", \', \!, \#, \ </code>(a 180: * space), and unicode characters with the 181: * <code>\\u</code><em>xxxx</em> notation are detected, and 182: * converted to the corresponding single character. <br> 183: * 184: * 185: <pre># This is a comment 186: key = value 187: k\:5 \ a string starting with space and ending with newline\n 188: # This is a multiline specification; note that the value contains 189: # no white space. 190: weekdays: Sunday,Monday,Tuesday,Wednesday,\\ 191: Thursday,Friday,Saturday 192: # The safest way to include a space at the end of a value: 193: label = Name:\\u0020</pre> 194: * 195: * @param inStream the input stream 196: * @throws IOException if an error occurred when reading the input 197: * @throws NullPointerException if in is null 198: */ 199: public void load(InputStream inStream) throws IOException 200: { 201: // The spec says that the file must be encoded using ISO-8859-1. 202: BufferedReader reader = 203: new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1")); 204: String line; 205: 206: while ((line = reader.readLine()) != null) 207: { 208: char c = 0; 209: int pos = 0; 210: // Leading whitespaces must be deleted first. 211: while (pos < line.length() 212: && Character.isWhitespace(c = line.charAt(pos))) 213: pos++; 214: 215: // If empty line or begins with a comment character, skip this line. 216: if ((line.length() - pos) == 0 217: || line.charAt(pos) == '#' || line.charAt(pos) == '!') 218: continue; 219: 220: // The characters up to the next Whitespace, ':', or '=' 221: // describe the key. But look for escape sequences. 222: // Try to short-circuit when there is no escape char. 223: int start = pos; 224: boolean needsEscape = line.indexOf('\\', pos) != -1; 225: StringBuilder key = needsEscape ? new StringBuilder() : null; 226: while (pos < line.length() 227: && ! Character.isWhitespace(c = line.charAt(pos++)) 228: && c != '=' && c != ':') 229: { 230: if (needsEscape && c == '\\') 231: { 232: if (pos == line.length()) 233: { 234: // The line continues on the next line. If there 235: // is no next line, just treat it as a key with an 236: // empty value. 237: line = reader.readLine(); 238: if (line == null) 239: line = ""; 240: pos = 0; 241: while (pos < line.length() 242: && Character.isWhitespace(c = line.charAt(pos))) 243: pos++; 244: } 245: else 246: { 247: c = line.charAt(pos++); 248: switch (c) 249: { 250: case 'n': 251: key.append('\n'); 252: break; 253: case 't': 254: key.append('\t'); 255: break; 256: case 'r': 257: key.append('\r'); 258: break; 259: case 'u': 260: if (pos + 4 <= line.length()) 261: { 262: char uni = (char) Integer.parseInt 263: (line.substring(pos, pos + 4), 16); 264: key.append(uni); 265: pos += 4; 266: } // else throw exception? 267: break; 268: default: 269: key.append(c); 270: break; 271: } 272: } 273: } 274: else if (needsEscape) 275: key.append(c); 276: } 277: 278: boolean isDelim = (c == ':' || c == '='); 279: 280: String keyString; 281: if (needsEscape) 282: keyString = key.toString(); 283: else if (isDelim || Character.isWhitespace(c)) 284: keyString = line.substring(start, pos - 1); 285: else 286: keyString = line.substring(start, pos); 287: 288: while (pos < line.length() 289: && Character.isWhitespace(c = line.charAt(pos))) 290: pos++; 291: 292: if (! isDelim && (c == ':' || c == '=')) 293: { 294: pos++; 295: while (pos < line.length() 296: && Character.isWhitespace(c = line.charAt(pos))) 297: pos++; 298: } 299: 300: // Short-circuit if no escape chars found. 301: if (!needsEscape) 302: { 303: put(keyString, line.substring(pos)); 304: continue; 305: } 306: 307: // Escape char found so iterate through the rest of the line. 308: StringBuilder element = new StringBuilder(line.length() - pos); 309: while (pos < line.length()) 310: { 311: c = line.charAt(pos++); 312: if (c == '\\') 313: { 314: if (pos == line.length()) 315: { 316: // The line continues on the next line. 317: line = reader.readLine(); 318: 319: // We might have seen a backslash at the end of 320: // the file. The JDK ignores the backslash in 321: // this case, so we follow for compatibility. 322: if (line == null) 323: break; 324: 325: pos = 0; 326: while (pos < line.length() 327: && Character.isWhitespace(c = line.charAt(pos))) 328: pos++; 329: element.ensureCapacity(line.length() - pos + 330: element.length()); 331: } 332: else 333: { 334: c = line.charAt(pos++); 335: switch (c) 336: { 337: case 'n': 338: element.append('\n'); 339: break; 340: case 't': 341: element.append('\t'); 342: break; 343: case 'r': 344: element.append('\r'); 345: break; 346: case 'u': 347: if (pos + 4 <= line.length()) 348: { 349: char uni = (char) Integer.parseInt 350: (line.substring(pos, pos + 4), 16); 351: element.append(uni); 352: pos += 4; 353: } // else throw exception? 354: break; 355: default: 356: element.append(c); 357: break; 358: } 359: } 360: } 361: else 362: element.append(c); 363: } 364: put(keyString, element.toString()); 365: } 366: } 367: 368: /** 369: * Calls <code>store(OutputStream out, String header)</code> and 370: * ignores the IOException that may be thrown. 371: * 372: * @param out the stream to write to 373: * @param header a description of the property list 374: * @throws ClassCastException if this property contains any key or 375: * value that are not strings 376: * @deprecated use {@link #store(OutputStream, String)} instead 377: */ 378: public void save(OutputStream out, String header) 379: { 380: try 381: { 382: store(out, header); 383: } 384: catch (IOException ex) 385: { 386: } 387: } 388: 389: /** 390: * Writes the key/value pairs to the given output stream, in a format 391: * suitable for <code>load</code>.<br> 392: * 393: * If header is not null, this method writes a comment containing 394: * the header as first line to the stream. The next line (or first 395: * line if header is null) contains a comment with the current date. 396: * Afterwards the key/value pairs are written to the stream in the 397: * following format.<br> 398: * 399: * Each line has the form <code>key = value</code>. Newlines, 400: * Returns and tabs are written as <code>\n,\t,\r</code> resp. 401: * The characters <code>\, !, #, =</code> and <code>:</code> are 402: * preceeded by a backslash. Spaces are preceded with a backslash, 403: * if and only if they are at the beginning of the key. Characters 404: * that are not in the ascii range 33 to 127 are written in the 405: * <code>\</code><code>u</code>xxxx Form.<br> 406: * 407: * Following the listing, the output stream is flushed but left open. 408: * 409: * @param out the output stream 410: * @param header the header written in the first line, may be null 411: * @throws ClassCastException if this property contains any key or 412: * value that isn't a string 413: * @throws IOException if writing to the stream fails 414: * @throws NullPointerException if out is null 415: * @since 1.2 416: */ 417: public void store(OutputStream out, String header) throws IOException 418: { 419: // The spec says that the file must be encoded using ISO-8859-1. 420: PrintWriter writer 421: = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1")); 422: if (header != null) 423: writer.println("#" + header); 424: writer.println ("#" + Calendar.getInstance ().getTime ()); 425: 426: Iterator iter = entrySet ().iterator (); 427: int i = size (); 428: StringBuilder s = new StringBuilder (); // Reuse the same buffer. 429: while (--i >= 0) 430: { 431: Map.Entry entry = (Map.Entry) iter.next (); 432: formatForOutput ((String) entry.getKey (), s, true); 433: s.append ('='); 434: formatForOutput ((String) entry.getValue (), s, false); 435: writer.println (s); 436: } 437: 438: writer.flush (); 439: } 440: 441: /** 442: * Gets the property with the specified key in this property list. 443: * If the key is not found, the default property list is searched. 444: * If the property is not found in the default, null is returned. 445: * 446: * @param key The key for this property 447: * @return the value for the given key, or null if not found 448: * @throws ClassCastException if this property contains any key or 449: * value that isn't a string 450: * @see #defaults 451: * @see #setProperty(String, String) 452: * @see #getProperty(String, String) 453: */ 454: public String getProperty(String key) 455: { 456: Properties prop = this; 457: // Eliminate tail recursion. 458: do 459: { 460: String value = (String) prop.get(key); 461: if (value != null) 462: return value; 463: prop = prop.defaults; 464: } 465: while (prop != null); 466: return null; 467: } 468: 469: /** 470: * Gets the property with the specified key in this property list. If 471: * the key is not found, the default property list is searched. If the 472: * property is not found in the default, the specified defaultValue is 473: * returned. 474: * 475: * @param key The key for this property 476: * @param defaultValue A default value 477: * @return The value for the given key 478: * @throws ClassCastException if this property contains any key or 479: * value that isn't a string 480: * @see #defaults 481: * @see #setProperty(String, String) 482: */ 483: public String getProperty(String key, String defaultValue) 484: { 485: String prop = getProperty(key); 486: if (prop == null) 487: prop = defaultValue; 488: return prop; 489: } 490: 491: /** 492: * Returns an enumeration of all keys in this property list, including 493: * the keys in the default property list. 494: * 495: * @return an Enumeration of all defined keys 496: */ 497: public Enumeration propertyNames() 498: { 499: // We make a new Set that holds all the keys, then return an enumeration 500: // for that. This prevents modifications from ruining the enumeration, 501: // as well as ignoring duplicates. 502: Properties prop = this; 503: Set s = new HashSet(); 504: // Eliminate tail recursion. 505: do 506: { 507: s.addAll(prop.keySet()); 508: prop = prop.defaults; 509: } 510: while (prop != null); 511: return Collections.enumeration(s); 512: } 513: 514: /** 515: * Prints the key/value pairs to the given print stream. This is 516: * mainly useful for debugging purposes. 517: * 518: * @param out the print stream, where the key/value pairs are written to 519: * @throws ClassCastException if this property contains a key or a 520: * value that isn't a string 521: * @see #list(PrintWriter) 522: */ 523: public void list(PrintStream out) 524: { 525: PrintWriter writer = new PrintWriter (out); 526: list (writer); 527: } 528: 529: /** 530: * Prints the key/value pairs to the given print writer. This is 531: * mainly useful for debugging purposes. 532: * 533: * @param out the print writer where the key/value pairs are written to 534: * @throws ClassCastException if this property contains a key or a 535: * value that isn't a string 536: * @see #list(PrintStream) 537: * @since 1.1 538: */ 539: public void list(PrintWriter out) 540: { 541: out.println ("-- listing properties --"); 542: 543: Iterator iter = entrySet ().iterator (); 544: int i = size (); 545: while (--i >= 0) 546: { 547: Map.Entry entry = (Map.Entry) iter.next (); 548: out.print ((String) entry.getKey () + "="); 549: 550: // JDK 1.3/1.4 restrict the printed value, but not the key, 551: // to 40 characters, including the truncating ellipsis. 552: String s = (String ) entry.getValue (); 553: if (s != null && s.length () > 40) 554: out.println (s.substring (0, 37) + "..."); 555: else 556: out.println (s); 557: } 558: out.flush (); 559: } 560: 561: /** 562: * Formats a key or value for output in a properties file. 563: * See store for a description of the format. 564: * 565: * @param str the string to format 566: * @param buffer the buffer to add it to 567: * @param key true if all ' ' must be escaped for the key, false if only 568: * leading spaces must be escaped for the value 569: * @see #store(OutputStream, String) 570: */ 571: private void formatForOutput(String str, StringBuilder buffer, boolean key) 572: { 573: if (key) 574: { 575: buffer.setLength(0); 576: buffer.ensureCapacity(str.length()); 577: } 578: else 579: buffer.ensureCapacity(buffer.length() + str.length()); 580: boolean head = true; 581: int size = str.length(); 582: for (int i = 0; i < size; i++) 583: { 584: char c = str.charAt(i); 585: switch (c) 586: { 587: case '\n': 588: buffer.append("\\n"); 589: break; 590: case '\r': 591: buffer.append("\\r"); 592: break; 593: case '\t': 594: buffer.append("\\t"); 595: break; 596: case ' ': 597: buffer.append(head ? "\\ " : " "); 598: break; 599: case '\\': 600: case '!': 601: case '#': 602: case '=': 603: case ':': 604: buffer.append('\\').append(c); 605: break; 606: default: 607: if (c < ' ' || c > '~') 608: { 609: String hex = Integer.toHexString(c); 610: buffer.append("\\u0000".substring(0, 6 - hex.length())); 611: buffer.append(hex); 612: } 613: else 614: buffer.append(c); 615: } 616: if (c != ' ') 617: head = key; 618: } 619: } 620: 621: /** 622: * <p> 623: * Encodes the properties as an XML file using the UTF-8 encoding. 624: * The format of the XML file matches the DTD 625: * <a href="http://java.sun.com/dtd/properties.dtd"> 626: * http://java.sun.com/dtd/properties.dtd</a>. 627: * </p> 628: * <p> 629: * Invoking this method provides the same behaviour as invoking 630: * <code>storeToXML(os, comment, "UTF-8")</code>. 631: * </p> 632: * 633: * @param os the stream to output to. 634: * @param comment a comment to include at the top of the XML file, or 635: * <code>null</code> if one is not required. 636: * @throws IOException if the serialization fails. 637: * @throws NullPointerException if <code>os</code> is null. 638: * @since 1.5 639: */ 640: public void storeToXML(OutputStream os, String comment) 641: throws IOException 642: { 643: storeToXML(os, comment, "UTF-8"); 644: } 645: 646: /** 647: * <p> 648: * Encodes the properties as an XML file using the supplied encoding. 649: * The format of the XML file matches the DTD 650: * <a href="http://java.sun.com/dtd/properties.dtd"> 651: * http://java.sun.com/dtd/properties.dtd</a>. 652: * </p> 653: * 654: * @param os the stream to output to. 655: * @param comment a comment to include at the top of the XML file, or 656: * <code>null</code> if one is not required. 657: * @param encoding the encoding to use for the XML output. 658: * @throws IOException if the serialization fails. 659: * @throws NullPointerException if <code>os</code> or <code>encoding</code> 660: * is null. 661: * @since 1.5 662: */ 663: public void storeToXML(OutputStream os, String comment, String encoding) 664: throws IOException 665: { 666: if (os == null) 667: throw new NullPointerException("Null output stream supplied."); 668: if (encoding == null) 669: throw new NullPointerException("Null encoding supplied."); 670: try 671: { 672: DOMImplementationRegistry registry = 673: DOMImplementationRegistry.newInstance(); 674: DOMImplementation domImpl = registry.getDOMImplementation("LS 3.0"); 675: DocumentType doctype = 676: domImpl.createDocumentType("properties", null, 677: "http://java.sun.com/dtd/properties.dtd"); 678: Document doc = domImpl.createDocument(null, "properties", doctype); 679: Element root = doc.getDocumentElement(); 680: if (comment != null) 681: { 682: Element commentElement = doc.createElement("comment"); 683: commentElement.appendChild(doc.createTextNode(comment)); 684: root.appendChild(commentElement); 685: } 686: Iterator iterator = entrySet().iterator(); 687: while (iterator.hasNext()) 688: { 689: Map.Entry entry = (Map.Entry) iterator.next(); 690: Element entryElement = doc.createElement("entry"); 691: entryElement.setAttribute("key", (String) entry.getKey()); 692: entryElement.appendChild(doc.createTextNode((String) 693: entry.getValue())); 694: root.appendChild(entryElement); 695: } 696: DOMImplementationLS loadAndSave = (DOMImplementationLS) domImpl; 697: LSSerializer serializer = loadAndSave.createLSSerializer(); 698: LSOutput output = loadAndSave.createLSOutput(); 699: output.setByteStream(os); 700: output.setEncoding(encoding); 701: serializer.write(doc, output); 702: } 703: catch (ClassNotFoundException e) 704: { 705: throw (IOException) 706: new IOException("The XML classes could not be found.").initCause(e); 707: } 708: catch (InstantiationException e) 709: { 710: throw (IOException) 711: new IOException("The XML classes could not be instantiated.") 712: .initCause(e); 713: } 714: catch (IllegalAccessException e) 715: { 716: throw (IOException) 717: new IOException("The XML classes could not be accessed.") 718: .initCause(e); 719: } 720: } 721: 722: /** 723: * <p> 724: * Decodes the contents of the supplied <code>InputStream</code> as 725: * an XML file, which represents a set of properties. The format of 726: * the XML file must match the DTD 727: * <a href="http://java.sun.com/dtd/properties.dtd"> 728: * http://java.sun.com/dtd/properties.dtd</a>. 729: * </p> 730: * 731: * @param in the input stream from which to receive the XML data. 732: * @throws IOException if an I/O error occurs in reading the input data. 733: * @throws InvalidPropertiesFormatException if the input data does not 734: * constitute an XML properties 735: * file. 736: * @throws NullPointerException if <code>in</code> is null. 737: * @since 1.5 738: */ 739: public void loadFromXML(InputStream in) 740: throws IOException, InvalidPropertiesFormatException 741: { 742: if (in == null) 743: throw new NullPointerException("Null input stream supplied."); 744: try 745: { 746: SAXParserFactory factory = SAXParserFactory.newInstance(); 747: factory.setValidating(false); /* Don't use the URI */ 748: XMLReader parser = factory.newSAXParser().getXMLReader(); 749: PropertiesHandler handler = new PropertiesHandler(); 750: parser.setContentHandler(handler); 751: parser.setProperty("http://xml.org/sax/properties/lexical-handler", 752: handler); 753: parser.parse(new InputSource(in)); 754: } 755: catch (SAXException e) 756: { 757: throw (InvalidPropertiesFormatException) 758: new InvalidPropertiesFormatException("Error in parsing XML."). 759: initCause(e); 760: } 761: catch (ParserConfigurationException e) 762: { 763: throw (IOException) 764: new IOException("An XML parser could not be found."). 765: initCause(e); 766: } 767: } 768: 769: /** 770: * This class deals with the parsing of XML using 771: * <a href="http://java.sun.com/dtd/properties.dtd"> 772: * http://java.sun.com/dtd/properties.dtd</a>. 773: * 774: * @author Andrew John Hughes (gnu_andrew@member.fsf.org) 775: * @since 1.5 776: */ 777: private class PropertiesHandler 778: extends DefaultHandler2 779: { 780: 781: /** 782: * The current key. 783: */ 784: private String key; 785: 786: /** 787: * The current value. 788: */ 789: private String value; 790: 791: /** 792: * A flag to check whether a valid DTD declaration has been seen. 793: */ 794: private boolean dtdDeclSeen; 795: 796: /** 797: * Constructs a new Properties handler. 798: */ 799: public PropertiesHandler() 800: { 801: key = null; 802: value = null; 803: dtdDeclSeen = false; 804: } 805: 806: /** 807: * <p> 808: * Captures the start of the DTD declarations, if they exist. 809: * A valid properties file must declare the following doctype: 810: * </p> 811: * <p> 812: * <code>!DOCTYPE properties SYSTEM 813: * "http://java.sun.com/dtd/properties.dtd"</code> 814: * </p> 815: * 816: * @param name the name of the document type. 817: * @param publicId the public identifier that was declared, or 818: * null if there wasn't one. 819: * @param systemId the system identifier that was declared, or 820: * null if there wasn't one. 821: * @throws SAXException if some error occurs in parsing. 822: */ 823: public void startDTD(String name, String publicId, String systemId) 824: throws SAXException 825: { 826: if (name.equals("properties") && 827: publicId == null && 828: systemId.equals("http://java.sun.com/dtd/properties.dtd")) 829: { 830: dtdDeclSeen = true; 831: } 832: else 833: throw new SAXException("Invalid DTD declaration: " + name); 834: } 835: 836: /** 837: * Captures the start of an XML element. 838: * 839: * @param uri the namespace URI. 840: * @param localName the local name of the element inside the namespace. 841: * @param qName the local name qualified with the namespace URI. 842: * @param attributes the attributes of this element. 843: * @throws SAXException if some error occurs in parsing. 844: */ 845: public void startElement(String uri, String localName, 846: String qName, Attributes attributes) 847: throws SAXException 848: { 849: if (qName.equals("entry")) 850: { 851: int index = attributes.getIndex("key"); 852: if (index != -1) 853: key = attributes.getValue(index); 854: } 855: else if (qName.equals("comment") || qName.equals("properties")) 856: { 857: /* Ignore it */ 858: } 859: else 860: throw new SAXException("Invalid tag: " + qName); 861: } 862: 863: /** 864: * Captures characters within an XML element. 865: * 866: * @param ch the array of characters. 867: * @param start the start index of the characters to use. 868: * @param length the number of characters to use from the start index on. 869: * @throws SAXException if some error occurs in parsing. 870: */ 871: public void characters(char[] ch, int start, int length) 872: throws SAXException 873: { 874: if (key != null) 875: value = new String(ch,start,length); 876: } 877: 878: /** 879: * Captures the end of an XML element. 880: * 881: * @param uri the namespace URI. 882: * @param localName the local name of the element inside the namespace. 883: * @param qName the local name qualified with the namespace URI. 884: * @throws SAXException if some error occurs in parsing. 885: */ 886: public void endElement(String uri, String localName, 887: String qName) 888: throws SAXException 889: { 890: if (qName.equals("entry")) 891: { 892: if (value == null) 893: value = ""; 894: setProperty(key, value); 895: key = null; 896: value = null; 897: } 898: } 899: 900: /** 901: * Captures the end of the XML document. If a DTD declaration has 902: * not been seen, the document is erroneous and an exception is thrown. 903: * 904: * @throws SAXException if the correct DTD declaration didn't appear. 905: */ 906: public void endDocument() 907: throws SAXException 908: { 909: if (!dtdDeclSeen) 910: throw new SAXException("No appropriate DTD declaration was seen."); 911: } 912: 913: } // class PropertiesHandler 914: 915: } // class Properties