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