Merge from mainline
[official-gcc.git] / libjava / classpath / java / util / Properties.java
blobeb208f5a93d5a44122b98c484031b11c25bb44dd
1 /* Properties.java -- a set of persistent properties
2 Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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.
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.
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.
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.
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. */
39 package java.util;
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;
50 import javax.xml.stream.XMLInputFactory;
51 import javax.xml.stream.XMLStreamConstants;
52 import javax.xml.stream.XMLStreamException;
53 import javax.xml.stream.XMLStreamReader;
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;
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.
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)
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>
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>.
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.
102 * @author Jochen Hoenicke
103 * @author Eric Blake (ebb9@email.byu.edu)
104 * @see PropertyResourceBundle
105 * @status updated to 1.4
107 public class Properties extends Hashtable
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.
113 * The property list that contains default values for any keys not
114 * in this property list.
116 * @serial the default properties
118 protected Properties defaults;
121 * Compatible with JDK 1.0+.
123 private static final long serialVersionUID = 4112578634029874840L;
126 * Creates a new empty property list with no default values.
128 public Properties()
133 * Create a new empty property list with the specified default values.
135 * @param defaults a Properties object containing the default values
137 public Properties(Properties defaults)
139 this.defaults = defaults;
143 * Adds the given key/value pair to this properties. This calls
144 * the hashtable method put.
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
152 public Object setProperty(String key, String value)
154 return put(key, value);
158 * Reads a property list from an input stream. The stream should
159 * have the following format: <br>
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>
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>
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>
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>
190 * @param inStream the input stream
191 * @throws IOException if an error occurred when reading the input
192 * @throws NullPointerException if in is null
194 public void load(InputStream inStream) throws IOException
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;
201 while ((line = reader.readLine()) != null)
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++;
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;
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 != ':')
225 if (needsEscape && c == '\\')
227 if (pos == line.length())
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++;
240 else
242 c = line.charAt(pos++);
243 switch (c)
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())
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;
269 else if (needsEscape)
270 key.append(c);
273 boolean isDelim = (c == ':' || c == '=');
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);
283 while (pos < line.length()
284 && Character.isWhitespace(c = line.charAt(pos)))
285 pos++;
287 if (! isDelim && (c == ':' || c == '='))
289 pos++;
290 while (pos < line.length()
291 && Character.isWhitespace(c = line.charAt(pos)))
292 pos++;
295 // Short-circuit if no escape chars found.
296 if (!needsEscape)
298 put(keyString, line.substring(pos));
299 continue;
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())
306 c = line.charAt(pos++);
307 if (c == '\\')
309 if (pos == line.length())
311 // The line continues on the next line.
312 line = reader.readLine();
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;
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());
327 else
329 c = line.charAt(pos++);
330 switch (c)
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())
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;
356 else
357 element.append(c);
359 put(keyString, element.toString());
364 * Calls <code>store(OutputStream out, String header)</code> and
365 * ignores the IOException that may be thrown.
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
373 public void save(OutputStream out, String header)
377 store(out, header);
379 catch (IOException ex)
385 * Writes the key/value pairs to the given output stream, in a format
386 * suitable for <code>load</code>.<br>
388 * If header is not null, this method writes a comment containing
389 * the header as first line to the stream. The next line (or first
390 * line if header is null) contains a comment with the current date.
391 * Afterwards the key/value pairs are written to the stream in the
392 * following format.<br>
394 * Each line has the form <code>key = value</code>. Newlines,
395 * Returns and tabs are written as <code>\n,\t,\r</code> resp.
396 * The characters <code>\, !, #, =</code> and <code>:</code> are
397 * preceeded by a backslash. Spaces are preceded with a backslash,
398 * if and only if they are at the beginning of the key. Characters
399 * that are not in the ascii range 33 to 127 are written in the
400 * <code>\</code><code>u</code>xxxx Form.<br>
402 * Following the listing, the output stream is flushed but left open.
404 * @param out the output stream
405 * @param header the header written in the first line, may be null
406 * @throws ClassCastException if this property contains any key or
407 * value that isn't a string
408 * @throws IOException if writing to the stream fails
409 * @throws NullPointerException if out is null
410 * @since 1.2
412 public void store(OutputStream out, String header) throws IOException
414 // The spec says that the file must be encoded using ISO-8859-1.
415 PrintWriter writer
416 = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1"));
417 if (header != null)
418 writer.println("#" + header);
419 writer.println ("#" + Calendar.getInstance ().getTime ());
421 Iterator iter = entrySet ().iterator ();
422 int i = size ();
423 StringBuilder s = new StringBuilder (); // Reuse the same buffer.
424 while (--i >= 0)
426 Map.Entry entry = (Map.Entry) iter.next ();
427 formatForOutput ((String) entry.getKey (), s, true);
428 s.append ('=');
429 formatForOutput ((String) entry.getValue (), s, false);
430 writer.println (s);
433 writer.flush ();
437 * Gets the property with the specified key in this property list.
438 * If the key is not found, the default property list is searched.
439 * If the property is not found in the default, null is returned.
441 * @param key The key for this property
442 * @return the value for the given key, or null if not found
443 * @throws ClassCastException if this property contains any key or
444 * value that isn't a string
445 * @see #defaults
446 * @see #setProperty(String, String)
447 * @see #getProperty(String, String)
449 public String getProperty(String key)
451 Properties prop = this;
452 // Eliminate tail recursion.
455 String value = (String) prop.get(key);
456 if (value != null)
457 return value;
458 prop = prop.defaults;
460 while (prop != null);
461 return null;
465 * Gets the property with the specified key in this property list. If
466 * the key is not found, the default property list is searched. If the
467 * property is not found in the default, the specified defaultValue is
468 * returned.
470 * @param key The key for this property
471 * @param defaultValue A default value
472 * @return The value for the given key
473 * @throws ClassCastException if this property contains any key or
474 * value that isn't a string
475 * @see #defaults
476 * @see #setProperty(String, String)
478 public String getProperty(String key, String defaultValue)
480 String prop = getProperty(key);
481 if (prop == null)
482 prop = defaultValue;
483 return prop;
487 * Returns an enumeration of all keys in this property list, including
488 * the keys in the default property list.
490 * @return an Enumeration of all defined keys
492 public Enumeration propertyNames()
494 // We make a new Set that holds all the keys, then return an enumeration
495 // for that. This prevents modifications from ruining the enumeration,
496 // as well as ignoring duplicates.
497 Properties prop = this;
498 Set s = new HashSet();
499 // Eliminate tail recursion.
502 s.addAll(prop.keySet());
503 prop = prop.defaults;
505 while (prop != null);
506 return Collections.enumeration(s);
510 * Prints the key/value pairs to the given print stream. This is
511 * mainly useful for debugging purposes.
513 * @param out the print stream, where the key/value pairs are written to
514 * @throws ClassCastException if this property contains a key or a
515 * value that isn't a string
516 * @see #list(PrintWriter)
518 public void list(PrintStream out)
520 PrintWriter writer = new PrintWriter (out);
521 list (writer);
525 * Prints the key/value pairs to the given print writer. This is
526 * mainly useful for debugging purposes.
528 * @param out the print writer where the key/value pairs are written to
529 * @throws ClassCastException if this property contains a key or a
530 * value that isn't a string
531 * @see #list(PrintStream)
532 * @since 1.1
534 public void list(PrintWriter out)
536 out.println ("-- listing properties --");
538 Iterator iter = entrySet ().iterator ();
539 int i = size ();
540 while (--i >= 0)
542 Map.Entry entry = (Map.Entry) iter.next ();
543 out.print ((String) entry.getKey () + "=");
545 // JDK 1.3/1.4 restrict the printed value, but not the key,
546 // to 40 characters, including the truncating ellipsis.
547 String s = (String ) entry.getValue ();
548 if (s != null && s.length () > 40)
549 out.println (s.substring (0, 37) + "...");
550 else
551 out.println (s);
553 out.flush ();
557 * Formats a key or value for output in a properties file.
558 * See store for a description of the format.
560 * @param str the string to format
561 * @param buffer the buffer to add it to
562 * @param key true if all ' ' must be escaped for the key, false if only
563 * leading spaces must be escaped for the value
564 * @see #store(OutputStream, String)
566 private void formatForOutput(String str, StringBuilder buffer, boolean key)
568 if (key)
570 buffer.setLength(0);
571 buffer.ensureCapacity(str.length());
573 else
574 buffer.ensureCapacity(buffer.length() + str.length());
575 boolean head = true;
576 int size = str.length();
577 for (int i = 0; i < size; i++)
579 char c = str.charAt(i);
580 switch (c)
582 case '\n':
583 buffer.append("\\n");
584 break;
585 case '\r':
586 buffer.append("\\r");
587 break;
588 case '\t':
589 buffer.append("\\t");
590 break;
591 case ' ':
592 buffer.append(head ? "\\ " : " ");
593 break;
594 case '\\':
595 case '!':
596 case '#':
597 case '=':
598 case ':':
599 buffer.append('\\').append(c);
600 break;
601 default:
602 if (c < ' ' || c > '~')
604 String hex = Integer.toHexString(c);
605 buffer.append("\\u0000".substring(0, 6 - hex.length()));
606 buffer.append(hex);
608 else
609 buffer.append(c);
611 if (c != ' ')
612 head = key;
617 * <p>
618 * Encodes the properties as an XML file using the UTF-8 encoding.
619 * The format of the XML file matches the DTD
620 * <a href="http://java.sun.com/dtd/properties.dtd">
621 * http://java.sun.com/dtd/properties.dtd</a>.
622 * </p>
623 * <p>
624 * Invoking this method provides the same behaviour as invoking
625 * <code>storeToXML(os, comment, "UTF-8")</code>.
626 * </p>
628 * @param os the stream to output to.
629 * @param comment a comment to include at the top of the XML file, or
630 * <code>null</code> if one is not required.
631 * @throws IOException if the serialization fails.
632 * @throws NullPointerException if <code>os</code> is null.
633 * @since 1.5
635 public void storeToXML(OutputStream os, String comment)
636 throws IOException
638 storeToXML(os, comment, "UTF-8");
642 * <p>
643 * Encodes the properties as an XML file using the supplied encoding.
644 * The format of the XML file matches the DTD
645 * <a href="http://java.sun.com/dtd/properties.dtd">
646 * http://java.sun.com/dtd/properties.dtd</a>.
647 * </p>
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 * @param encoding the encoding to use for the XML output.
653 * @throws IOException if the serialization fails.
654 * @throws NullPointerException if <code>os</code> or <code>encoding</code>
655 * is null.
656 * @since 1.5
658 public void storeToXML(OutputStream os, String comment, String encoding)
659 throws IOException
661 if (os == null)
662 throw new NullPointerException("Null output stream supplied.");
663 if (encoding == null)
664 throw new NullPointerException("Null encoding supplied.");
667 DOMImplementationRegistry registry =
668 DOMImplementationRegistry.newInstance();
669 DOMImplementation domImpl = registry.getDOMImplementation("LS 3.0");
670 DocumentType doctype =
671 domImpl.createDocumentType("properties", null,
672 "http://java.sun.com/dtd/properties.dtd");
673 Document doc = domImpl.createDocument(null, "properties", doctype);
674 Element root = doc.getDocumentElement();
675 if (comment != null)
677 Element commentElement = doc.createElement("comment");
678 commentElement.appendChild(doc.createTextNode(comment));
679 root.appendChild(commentElement);
681 Iterator iterator = entrySet().iterator();
682 while (iterator.hasNext())
684 Map.Entry entry = (Map.Entry) iterator.next();
685 Element entryElement = doc.createElement("entry");
686 entryElement.setAttribute("key", (String) entry.getKey());
687 entryElement.appendChild(doc.createTextNode((String)
688 entry.getValue()));
689 root.appendChild(entryElement);
691 DOMImplementationLS loadAndSave = (DOMImplementationLS) domImpl;
692 LSSerializer serializer = loadAndSave.createLSSerializer();
693 LSOutput output = loadAndSave.createLSOutput();
694 output.setByteStream(os);
695 output.setEncoding(encoding);
696 serializer.write(doc, output);
698 catch (ClassNotFoundException e)
700 throw (IOException)
701 new IOException("The XML classes could not be found.").initCause(e);
703 catch (InstantiationException e)
705 throw (IOException)
706 new IOException("The XML classes could not be instantiated.")
707 .initCause(e);
709 catch (IllegalAccessException e)
711 throw (IOException)
712 new IOException("The XML classes could not be accessed.")
713 .initCause(e);
718 * <p>
719 * Decodes the contents of the supplied <code>InputStream</code> as
720 * an XML file, which represents a set of properties. The format of
721 * the XML file must match the DTD
722 * <a href="http://java.sun.com/dtd/properties.dtd">
723 * http://java.sun.com/dtd/properties.dtd</a>.
724 * </p>
726 * @param in the input stream from which to receive the XML data.
727 * @throws IOException if an I/O error occurs in reading the input data.
728 * @throws InvalidPropertiesFormatException if the input data does not
729 * constitute an XML properties
730 * file.
731 * @throws NullPointerException if <code>in</code> is null.
732 * @since 1.5
734 public void loadFromXML(InputStream in)
735 throws IOException, InvalidPropertiesFormatException
737 if (in == null)
738 throw new NullPointerException("Null input stream supplied.");
741 XMLInputFactory factory = XMLInputFactory.newInstance();
742 // Don't resolve external entity references
743 factory.setProperty("javax.xml.stream.isSupportingExternalEntities",
744 Boolean.FALSE);
745 XMLStreamReader reader = factory.createXMLStreamReader(in);
746 String name, key = null;
747 StringBuffer buf = null;
748 while (reader.hasNext())
750 switch (reader.next())
752 case XMLStreamConstants.START_ELEMENT:
753 name = reader.getLocalName();
754 if (buf == null && "entry".equals(name))
756 key = reader.getAttributeValue(null, "key");
757 if (key == null)
759 String msg = "missing 'key' attribute";
760 throw new InvalidPropertiesFormatException(msg);
762 buf = new StringBuffer();
764 else if (!"properties".equals(name) && !"comment".equals(name))
766 String msg = "unexpected element name '" + name + "'";
767 throw new InvalidPropertiesFormatException(msg);
769 break;
770 case XMLStreamConstants.END_ELEMENT:
771 name = reader.getLocalName();
772 if (buf != null && "entry".equals(name))
774 put(key, buf.toString());
775 buf = null;
777 else if (!"properties".equals(name) && !"comment".equals(name))
779 String msg = "unexpected element name '" + name + "'";
780 throw new InvalidPropertiesFormatException(msg);
782 break;
783 case XMLStreamConstants.CHARACTERS:
784 case XMLStreamConstants.SPACE:
785 case XMLStreamConstants.CDATA:
786 if (buf != null)
787 buf.append(reader.getText());
788 break;
791 reader.close();
793 catch (XMLStreamException e)
795 throw (InvalidPropertiesFormatException)
796 new InvalidPropertiesFormatException("Error in parsing XML.").
797 initCause(e);
801 } // class Properties