OSDN Git Service

2007-02-09 Jakub Jelinek <jakub@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / util / Properties.java
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