Source for java.util.Properties

   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