001    /* Properties.java -- a set of persistent properties
002       Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.util;
040    
041    import java.io.BufferedReader;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import java.io.InputStreamReader;
045    import java.io.OutputStream;
046    import java.io.OutputStreamWriter;
047    import java.io.PrintStream;
048    import java.io.PrintWriter;
049    
050    import javax.xml.stream.XMLInputFactory;
051    import javax.xml.stream.XMLStreamConstants;
052    import javax.xml.stream.XMLStreamException;
053    import javax.xml.stream.XMLStreamReader;
054    
055    import org.w3c.dom.Document;
056    import org.w3c.dom.DocumentType;
057    import org.w3c.dom.DOMImplementation;
058    import org.w3c.dom.Element;
059    import org.w3c.dom.bootstrap.DOMImplementationRegistry;
060    import org.w3c.dom.ls.DOMImplementationLS;
061    import org.w3c.dom.ls.LSOutput;
062    import org.w3c.dom.ls.LSSerializer;
063    
064    /**
065     * A set of persistent properties, which can be saved or loaded from a stream.
066     * A property list may also contain defaults, searched if the main list
067     * does not contain a property for a given key.
068     *
069     * An example of a properties file for the german language is given
070     * here.  This extends the example given in ListResourceBundle.
071     * Create a file MyResource_de.properties with the following contents
072     * and put it in the CLASSPATH.  (The character
073     * <code>\</code><code>u00e4</code> is the german umlaut)
074     *
075     * 
076    <pre>s1=3
077    s2=MeineDisk
078    s3=3. M\<code></code>u00e4rz 96
079    s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
080    s5=0
081    s6=keine Dateien
082    s7=1
083    s8=eine Datei
084    s9=2
085    s10={0,number} Dateien
086    s11=Das Formatieren schlug fehl mit folgender Exception: {0}
087    s12=FEHLER
088    s13=Ergebnis
089    s14=Dialog
090    s15=Auswahlkriterium
091    s16=1,3</pre>
092     *
093     * <p>Although this is a sub class of a hash table, you should never
094     * insert anything other than strings to this property, or several
095     * methods, that need string keys and values, will fail.  To ensure
096     * this, you should use the <code>get/setProperty</code> method instead
097     * of <code>get/put</code>.
098     *
099     * 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