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