GNU Classpath (0.19) | ||
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: StringBuffer key = new StringBuffer(); 223: while (pos < line.length() 224: && ! Character.isWhitespace(c = line.charAt(pos++)) 225: && c != '=' && c != ':') 226: { 227: if (c == '\\') 228: { 229: if (pos == line.length()) 230: { 231: // The line continues on the next line. If there 232: // is no next line, just treat it as a key with an 233: // empty value. 234: line = reader.readLine(); 235: if (line == null) 236: line = ""; 237: pos = 0; 238: while (pos < line.length() 239: && Character.isWhitespace(c = line.charAt(pos))) 240: pos++; 241: } 242: else 243: { 244: c = line.charAt(pos++); 245: switch (c) 246: { 247: case 'n': 248: key.append('\n'); 249: break; 250: case 't': 251: key.append('\t'); 252: break; 253: case 'r': 254: key.append('\r'); 255: break; 256: case 'u': 257: if (pos + 4 <= line.length()) 258: { 259: char uni = (char) Integer.parseInt 260: (line.substring(pos, pos + 4), 16); 261: key.append(uni); 262: pos += 4; 263: } // else throw exception? 264: break; 265: default: 266: key.append(c); 267: break; 268: } 269: } 270: } 271: else 272: key.append(c); 273: } 274: 275: boolean isDelim = (c == ':' || c == '='); 276: while (pos < line.length() 277: && Character.isWhitespace(c = line.charAt(pos))) 278: pos++; 279: 280: if (! isDelim && (c == ':' || c == '=')) 281: { 282: pos++; 283: while (pos < line.length() 284: && Character.isWhitespace(c = line.charAt(pos))) 285: pos++; 286: } 287: 288: StringBuffer element = new StringBuffer(line.length() - pos); 289: while (pos < line.length()) 290: { 291: c = line.charAt(pos++); 292: if (c == '\\') 293: { 294: if (pos == line.length()) 295: { 296: // The line continues on the next line. 297: line = reader.readLine(); 298: 299: // We might have seen a backslash at the end of 300: // the file. The JDK ignores the backslash in 301: // this case, so we follow for compatibility. 302: if (line == null) 303: break; 304: 305: pos = 0; 306: while (pos < line.length() 307: && Character.isWhitespace(c = line.charAt(pos))) 308: pos++; 309: element.ensureCapacity(line.length() - pos + 310: element.length()); 311: } 312: else 313: { 314: c = line.charAt(pos++); 315: switch (c) 316: { 317: case 'n': 318: element.append('\n'); 319: break; 320: case 't': 321: element.append('\t'); 322: break; 323: case 'r': 324: element.append('\r'); 325: break; 326: case 'u': 327: if (pos + 4 <= line.length()) 328: { 329: char uni = (char) Integer.parseInt 330: (line.substring(pos, pos + 4), 16); 331: element.append(uni); 332: pos += 4; 333: } // else throw exception? 334: break; 335: default: 336: element.append(c); 337: break; 338: } 339: } 340: } 341: else 342: element.append(c); 343: } 344: put(key.toString(), element.toString()); 345: } 346: } 347: 348: /** 349: * Calls <code>store(OutputStream out, String header)</code> and 350: * ignores the IOException that may be thrown. 351: * 352: * @param out the stream to write to 353: * @param header a description of the property list 354: * @throws ClassCastException if this property contains any key or 355: * value that are not strings 356: * @deprecated use {@link #store(OutputStream, String)} instead 357: */ 358: public void save(OutputStream out, String header) 359: { 360: try 361: { 362: store(out, header); 363: } 364: catch (IOException ex) 365: { 366: } 367: } 368: 369: /** 370: * Writes the key/value pairs to the given output stream, in a format 371: * suitable for <code>load</code>.<br> 372: * 373: * If header is not null, this method writes a comment containing 374: * the header as first line to the stream. The next line (or first 375: * line if header is null) contains a comment with the current date. 376: * Afterwards the key/value pairs are written to the stream in the 377: * following format.<br> 378: * 379: * Each line has the form <code>key = value</code>. Newlines, 380: * Returns and tabs are written as <code>\n,\t,\r</code> resp. 381: * The characters <code>\, !, #, =</code> and <code>:</code> are 382: * preceeded by a backslash. Spaces are preceded with a backslash, 383: * if and only if they are at the beginning of the key. Characters 384: * that are not in the ascii range 33 to 127 are written in the 385: * <code>\</code><code>u</code>xxxx Form.<br> 386: * 387: * Following the listing, the output stream is flushed but left open. 388: * 389: * @param out the output stream 390: * @param header the header written in the first line, may be null 391: * @throws ClassCastException if this property contains any key or 392: * value that isn't a string 393: * @throws IOException if writing to the stream fails 394: * @throws NullPointerException if out is null 395: * @since 1.2 396: */ 397: public void store(OutputStream out, String header) throws IOException 398: { 399: // The spec says that the file must be encoded using ISO-8859-1. 400: PrintWriter writer 401: = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1")); 402: if (header != null) 403: writer.println("#" + header); 404: writer.println ("#" + Calendar.getInstance ().getTime ()); 405: 406: Iterator iter = entrySet ().iterator (); 407: int i = size (); 408: StringBuffer s = new StringBuffer (); // Reuse the same buffer. 409: while (--i >= 0) 410: { 411: Map.Entry entry = (Map.Entry) iter.next (); 412: formatForOutput ((String) entry.getKey (), s, true); 413: s.append ('='); 414: formatForOutput ((String) entry.getValue (), s, false); 415: writer.println (s); 416: } 417: 418: writer.flush (); 419: } 420: 421: /** 422: * Gets the property with the specified key in this property list. 423: * If the key is not found, the default property list is searched. 424: * If the property is not found in the default, null is returned. 425: * 426: * @param key The key for this property 427: * @return the value for the given key, or null if not found 428: * @throws ClassCastException if this property contains any key or 429: * value that isn't a string 430: * @see #defaults 431: * @see #setProperty(String, String) 432: * @see #getProperty(String, String) 433: */ 434: public String getProperty(String key) 435: { 436: Properties prop = this; 437: // Eliminate tail recursion. 438: do 439: { 440: String value = (String) prop.get(key); 441: if (value != null) 442: return value; 443: prop = prop.defaults; 444: } 445: while (prop != null); 446: return null; 447: } 448: 449: /** 450: * Gets the property with the specified key in this property list. If 451: * the key is not found, the default property list is searched. If the 452: * property is not found in the default, the specified defaultValue is 453: * returned. 454: * 455: * @param key The key for this property 456: * @param defaultValue A default value 457: * @return The value for the given key 458: * @throws ClassCastException if this property contains any key or 459: * value that isn't a string 460: * @see #defaults 461: * @see #setProperty(String, String) 462: */ 463: public String getProperty(String key, String defaultValue) 464: { 465: String prop = getProperty(key); 466: if (prop == null) 467: prop = defaultValue; 468: return prop; 469: } 470: 471: /** 472: * Returns an enumeration of all keys in this property list, including 473: * the keys in the default property list. 474: * 475: * @return an Enumeration of all defined keys 476: */ 477: public Enumeration propertyNames() 478: { 479: // We make a new Set that holds all the keys, then return an enumeration 480: // for that. This prevents modifications from ruining the enumeration, 481: // as well as ignoring duplicates. 482: Properties prop = this; 483: Set s = new HashSet(); 484: // Eliminate tail recursion. 485: do 486: { 487: s.addAll(prop.keySet()); 488: prop = prop.defaults; 489: } 490: while (prop != null); 491: return Collections.enumeration(s); 492: } 493: 494: /** 495: * Prints the key/value pairs to the given print stream. This is 496: * mainly useful for debugging purposes. 497: * 498: * @param out the print stream, where the key/value pairs are written to 499: * @throws ClassCastException if this property contains a key or a 500: * value that isn't a string 501: * @see #list(PrintWriter) 502: */ 503: public void list(PrintStream out) 504: { 505: PrintWriter writer = new PrintWriter (out); 506: list (writer); 507: } 508: 509: /** 510: * Prints the key/value pairs to the given print writer. This is 511: * mainly useful for debugging purposes. 512: * 513: * @param out the print writer where the key/value pairs are written to 514: * @throws ClassCastException if this property contains a key or a 515: * value that isn't a string 516: * @see #list(PrintStream) 517: * @since 1.1 518: */ 519: public void list(PrintWriter out) 520: { 521: out.println ("-- listing properties --"); 522: 523: Iterator iter = entrySet ().iterator (); 524: int i = size (); 525: while (--i >= 0) 526: { 527: Map.Entry entry = (Map.Entry) iter.next (); 528: out.print ((String) entry.getKey () + "="); 529: 530: // JDK 1.3/1.4 restrict the printed value, but not the key, 531: // to 40 characters, including the truncating ellipsis. 532: String s = (String ) entry.getValue (); 533: if (s != null && s.length () > 40) 534: out.println (s.substring (0, 37) + "..."); 535: else 536: out.println (s); 537: } 538: out.flush (); 539: } 540: 541: /** 542: * Formats a key or value for output in a properties file. 543: * See store for a description of the format. 544: * 545: * @param str the string to format 546: * @param buffer the buffer to add it to 547: * @param key true if all ' ' must be escaped for the key, false if only 548: * leading spaces must be escaped for the value 549: * @see #store(OutputStream, String) 550: */ 551: private void formatForOutput(String str, StringBuffer buffer, boolean key) 552: { 553: if (key) 554: { 555: buffer.setLength(0); 556: buffer.ensureCapacity(str.length()); 557: } 558: else 559: buffer.ensureCapacity(buffer.length() + str.length()); 560: boolean head = true; 561: int size = str.length(); 562: for (int i = 0; i < size; i++) 563: { 564: char c = str.charAt(i); 565: switch (c) 566: { 567: case '\n': 568: buffer.append("\\n"); 569: break; 570: case '\r': 571: buffer.append("\\r"); 572: break; 573: case '\t': 574: buffer.append("\\t"); 575: break; 576: case ' ': 577: buffer.append(head ? "\\ " : " "); 578: break; 579: case '\\': 580: case '!': 581: case '#': 582: case '=': 583: case ':': 584: buffer.append('\\').append(c); 585: break; 586: default: 587: if (c < ' ' || c > '~') 588: { 589: String hex = Integer.toHexString(c); 590: buffer.append("\\u0000".substring(0, 6 - hex.length())); 591: buffer.append(hex); 592: } 593: else 594: buffer.append(c); 595: } 596: if (c != ' ') 597: head = key; 598: } 599: } 600: 601: /** 602: * <p> 603: * Encodes the properties as an XML file using the UTF-8 encoding. 604: * The format of the XML file matches the DTD 605: * <a href="http://java.sun.com/dtd/properties.dtd"> 606: * http://java.sun.com/dtd/properties.dtd</a>. 607: * </p> 608: * <p> 609: * Invoking this method provides the same behaviour as invoking 610: * <code>storeToXML(os, comment, "UTF-8")</code>. 611: * </p> 612: * 613: * @param os the stream to output to. 614: * @param comment a comment to include at the top of the XML file, or 615: * <code>null</code> if one is not required. 616: * @throws IOException if the serialization fails. 617: * @throws NullPointerException if <code>os</code> is null. 618: * @since 1.5 619: */ 620: public void storeToXML(OutputStream os, String comment) 621: throws IOException 622: { 623: storeToXML(os, comment, "UTF-8"); 624: } 625: 626: /** 627: * <p> 628: * Encodes the properties as an XML file using the supplied encoding. 629: * The format of the XML file matches the DTD 630: * <a href="http://java.sun.com/dtd/properties.dtd"> 631: * http://java.sun.com/dtd/properties.dtd</a>. 632: * </p> 633: * 634: * @param os the stream to output to. 635: * @param comment a comment to include at the top of the XML file, or 636: * <code>null</code> if one is not required. 637: * @param encoding the encoding to use for the XML output. 638: * @throws IOException if the serialization fails. 639: * @throws NullPointerException if <code>os</code> or <code>encoding</code> 640: * is null. 641: * @since 1.5 642: */ 643: public void storeToXML(OutputStream os, String comment, String encoding) 644: throws IOException 645: { 646: if (os == null) 647: throw new NullPointerException("Null output stream supplied."); 648: if (encoding == null) 649: throw new NullPointerException("Null encoding supplied."); 650: try 651: { 652: DOMImplementationRegistry registry = 653: DOMImplementationRegistry.newInstance(); 654: DOMImplementation domImpl = registry.getDOMImplementation("LS 3.0"); 655: DocumentType doctype = 656: domImpl.createDocumentType("properties", null, 657: "http://java.sun.com/dtd/properties.dtd"); 658: Document doc = domImpl.createDocument(null, "properties", doctype); 659: Element root = doc.getDocumentElement(); 660: if (comment != null) 661: { 662: Element commentElement = doc.createElement("comment"); 663: commentElement.appendChild(doc.createTextNode(comment)); 664: root.appendChild(commentElement); 665: } 666: Iterator iterator = entrySet().iterator(); 667: while (iterator.hasNext()) 668: { 669: Map.Entry entry = (Map.Entry) iterator.next(); 670: Element entryElement = doc.createElement("entry"); 671: entryElement.setAttribute("key", (String) entry.getKey()); 672: entryElement.appendChild(doc.createTextNode((String) 673: entry.getValue())); 674: root.appendChild(entryElement); 675: } 676: DOMImplementationLS loadAndSave = (DOMImplementationLS) domImpl; 677: LSSerializer serializer = loadAndSave.createLSSerializer(); 678: LSOutput output = loadAndSave.createLSOutput(); 679: output.setByteStream(os); 680: output.setEncoding(encoding); 681: serializer.write(doc, output); 682: } 683: catch (ClassNotFoundException e) 684: { 685: throw (IOException) 686: new IOException("The XML classes could not be found.").initCause(e); 687: } 688: catch (InstantiationException e) 689: { 690: throw (IOException) 691: new IOException("The XML classes could not be instantiated.") 692: .initCause(e); 693: } 694: catch (IllegalAccessException e) 695: { 696: throw (IOException) 697: new IOException("The XML classes could not be accessed.") 698: .initCause(e); 699: } 700: } 701: 702: /** 703: * <p> 704: * Decodes the contents of the supplied <code>InputStream</code> as 705: * an XML file, which represents a set of properties. The format of 706: * the XML file must match the DTD 707: * <a href="http://java.sun.com/dtd/properties.dtd"> 708: * http://java.sun.com/dtd/properties.dtd</a>. 709: * </p> 710: * 711: * @param in the input stream from which to receive the XML data. 712: * @throws IOException if an I/O error occurs in reading the input data. 713: * @throws InvalidPropertiesFormatException if the input data does not 714: * constitute an XML properties 715: * file. 716: * @throws NullPointerException if <code>in</code> is null. 717: * @since 1.5 718: */ 719: public void loadFromXML(InputStream in) 720: throws IOException, InvalidPropertiesFormatException 721: { 722: if (in == null) 723: throw new NullPointerException("Null input stream supplied."); 724: try 725: { 726: SAXParserFactory factory = SAXParserFactory.newInstance(); 727: factory.setValidating(false); /* Don't use the URI */ 728: XMLReader parser = factory.newSAXParser().getXMLReader(); 729: PropertiesHandler handler = new PropertiesHandler(); 730: parser.setContentHandler(handler); 731: parser.setProperty("http://xml.org/sax/properties/lexical-handler", 732: handler); 733: parser.parse(new InputSource(in)); 734: } 735: catch (SAXException e) 736: { 737: throw (InvalidPropertiesFormatException) 738: new InvalidPropertiesFormatException("Error in parsing XML."). 739: initCause(e); 740: } 741: catch (ParserConfigurationException e) 742: { 743: throw (IOException) 744: new IOException("An XML parser could not be found."). 745: initCause(e); 746: } 747: } 748: 749: /** 750: * This class deals with the parsing of XML using 751: * <a href="http://java.sun.com/dtd/properties.dtd"> 752: * http://java.sun.com/dtd/properties.dtd</a>. 753: * 754: * @author Andrew John Hughes (gnu_andrew@member.fsf.org) 755: * @since 1.5 756: */ 757: private class PropertiesHandler 758: extends DefaultHandler2 759: { 760: 761: /** 762: * The current key. 763: */ 764: private String key; 765: 766: /** 767: * The current value. 768: */ 769: private String value; 770: 771: /** 772: * A flag to check whether a valid DTD declaration has been seen. 773: */ 774: private boolean dtdDeclSeen; 775: 776: /** 777: * Constructs a new Properties handler. 778: */ 779: public PropertiesHandler() 780: { 781: key = null; 782: value = null; 783: dtdDeclSeen = false; 784: } 785: 786: /** 787: * <p> 788: * Captures the start of the DTD declarations, if they exist. 789: * A valid properties file must declare the following doctype: 790: * </p> 791: * <p> 792: * <code>!DOCTYPE properties SYSTEM 793: * "http://java.sun.com/dtd/properties.dtd"</code> 794: * </p> 795: * 796: * @param name the name of the document type. 797: * @param publicId the public identifier that was declared, or 798: * null if there wasn't one. 799: * @param systemId the system identifier that was declared, or 800: * null if there wasn't one. 801: * @throws SAXException if some error occurs in parsing. 802: */ 803: public void startDTD(String name, String publicId, String systemId) 804: throws SAXException 805: { 806: if (name.equals("properties") && 807: publicId == null && 808: systemId.equals("http://java.sun.com/dtd/properties.dtd")) 809: { 810: dtdDeclSeen = true; 811: } 812: else 813: throw new SAXException("Invalid DTD declaration: " + name); 814: } 815: 816: /** 817: * Captures the start of an XML element. 818: * 819: * @param uri the namespace URI. 820: * @param localName the local name of the element inside the namespace. 821: * @param qName the local name qualified with the namespace URI. 822: * @param attributes the attributes of this element. 823: * @throws SAXException if some error occurs in parsing. 824: */ 825: public void startElement(String uri, String localName, 826: String qName, Attributes attributes) 827: throws SAXException 828: { 829: if (qName.equals("entry")) 830: { 831: int index = attributes.getIndex("key"); 832: if (index != -1) 833: key = attributes.getValue(index); 834: } 835: else if (qName.equals("comment") || qName.equals("properties")) 836: { 837: /* Ignore it */ 838: } 839: else 840: throw new SAXException("Invalid tag: " + qName); 841: } 842: 843: /** 844: * Captures characters within an XML element. 845: * 846: * @param ch the array of characters. 847: * @param start the start index of the characters to use. 848: * @param length the number of characters to use from the start index on. 849: * @throws SAXException if some error occurs in parsing. 850: */ 851: public void characters(char[] ch, int start, int length) 852: throws SAXException 853: { 854: if (key != null) 855: value = new String(ch,start,length); 856: } 857: 858: /** 859: * Captures the end of an XML element. 860: * 861: * @param uri the namespace URI. 862: * @param localName the local name of the element inside the namespace. 863: * @param qName the local name qualified with the namespace URI. 864: * @throws SAXException if some error occurs in parsing. 865: */ 866: public void endElement(String uri, String localName, 867: String qName) 868: throws SAXException 869: { 870: if (qName.equals("entry")) 871: { 872: if (value == null) 873: value = ""; 874: setProperty(key, value); 875: key = null; 876: value = null; 877: } 878: } 879: 880: /** 881: * Captures the end of the XML document. If a DTD declaration has 882: * not been seen, the document is erroneous and an exception is thrown. 883: * 884: * @throws SAXException if the correct DTD declaration didn't appear. 885: */ 886: public void endDocument() 887: throws SAXException 888: { 889: if (!dtdDeclSeen) 890: throw new SAXException("No appropriate DTD declaration was seen."); 891: } 892: 893: } // class PropertiesHandler 894: 895: } // class Properties
GNU Classpath (0.19) |