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 gnu.java.lang.CPStringBuilder;
042    
043    import java.io.BufferedReader;
044    import java.io.IOException;
045    import java.io.InputStream;
046    import java.io.InputStreamReader;
047    import java.io.OutputStream;
048    import java.io.OutputStreamWriter;
049    import java.io.PrintStream;
050    import java.io.PrintWriter;
051    import java.io.Reader;
052    
053    import javax.xml.stream.XMLInputFactory;
054    import javax.xml.stream.XMLStreamConstants;
055    import javax.xml.stream.XMLStreamException;
056    import javax.xml.stream.XMLStreamReader;
057    
058    import org.w3c.dom.Document;
059    import org.w3c.dom.DocumentType;
060    import org.w3c.dom.DOMImplementation;
061    import org.w3c.dom.Element;
062    import org.w3c.dom.bootstrap.DOMImplementationRegistry;
063    import org.w3c.dom.ls.DOMImplementationLS;
064    import org.w3c.dom.ls.LSOutput;
065    import org.w3c.dom.ls.LSSerializer;
066    
067    /**
068     * A set of persistent properties, which can be saved or loaded from a stream.
069     * A property list may also contain defaults, searched if the main list
070     * does not contain a property for a given key.
071     *
072     * An example of a properties file for the german language is given
073     * here.  This extends the example given in ListResourceBundle.
074     * Create a file MyResource_de.properties with the following contents
075     * and put it in the CLASSPATH.  (The character
076     * <code>\</code><code>u00e4</code> is the german umlaut)
077     *
078     *
079    <pre>s1=3
080    s2=MeineDisk
081    s3=3. M\<code></code>u00e4rz 96
082    s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
083    s5=0
084    s6=keine Dateien
085    s7=1
086    s8=eine Datei
087    s9=2
088    s10={0,number} Dateien
089    s11=Das Formatieren schlug fehl mit folgender Exception: {0}
090    s12=FEHLER
091    s13=Ergebnis
092    s14=Dialog
093    s15=Auswahlkriterium
094    s16=1,3</pre>
095     *
096     * <p>Although this is a sub class of a hash table, you should never
097     * insert anything other than strings to this property, or several
098     * methods, that need string keys and values, will fail.  To ensure
099     * 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