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)
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
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
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. */
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
;
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)
78 s3=3. M\<code></code>u00e4rz 96
79 s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
85 s10={0,number} Dateien
86 s11=Das Formatieren schlug fehl mit folgender Exception: {0}
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.
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)
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
182 k\:5 \ a string starting with space and ending with newline\n
183 # This is a multiline specification; note that the value contains
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"));
201 while ((line
= reader
.readLine()) != null)
205 // Leading whitespaces must be deleted first.
206 while (pos
< line
.length()
207 && Character
.isWhitespace(c
= line
.charAt(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
) == '!')
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.
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
232 line
= reader
.readLine();
236 while (pos
< line
.length()
237 && Character
.isWhitespace(c
= line
.charAt(pos
)))
242 c
= line
.charAt(pos
++);
255 if (pos
+ 4 <= line
.length())
257 char uni
= (char) Integer
.parseInt
258 (line
.substring(pos
, pos
+ 4), 16);
261 } // else throw exception?
269 else if (needsEscape
)
273 boolean isDelim
= (c
== ':' || c
== '=');
277 keyString
= key
.toString();
278 else if (isDelim
|| Character
.isWhitespace(c
))
279 keyString
= line
.substring(start
, pos
- 1);
281 keyString
= line
.substring(start
, pos
);
283 while (pos
< line
.length()
284 && Character
.isWhitespace(c
= line
.charAt(pos
)))
287 if (! isDelim
&& (c
== ':' || c
== '='))
290 while (pos
< line
.length()
291 && Character
.isWhitespace(c
= line
.charAt(pos
)))
295 // Short-circuit if no escape chars found.
298 put(keyString
, line
.substring(pos
));
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
++);
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.
321 while (pos
< line
.length()
322 && Character
.isWhitespace(c
= line
.charAt(pos
)))
324 element
.ensureCapacity(line
.length() - pos
+
329 c
= line
.charAt(pos
++);
333 element
.append('\n');
336 element
.append('\t');
339 element
.append('\r');
342 if (pos
+ 4 <= line
.length())
344 char uni
= (char) Integer
.parseInt
345 (line
.substring(pos
, pos
+ 4), 16);
348 } // else throw exception?
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
)
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
412 public void store(OutputStream out
, String header
) throws IOException
414 // The spec says that the file must be encoded using ISO-8859-1.
416 = new PrintWriter(new OutputStreamWriter(out
, "ISO-8859-1"));
418 writer
.println("#" + header
);
419 writer
.println ("#" + Calendar
.getInstance ().getTime ());
421 Iterator iter
= entrySet ().iterator ();
423 StringBuilder s
= new StringBuilder (); // Reuse the same buffer.
426 Map
.Entry entry
= (Map
.Entry
) iter
.next ();
427 formatForOutput ((String
) entry
.getKey (), s
, true);
429 formatForOutput ((String
) entry
.getValue (), s
, false);
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
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
);
458 prop
= prop
.defaults
;
460 while (prop
!= 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
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
476 * @see #setProperty(String, String)
478 public String
getProperty(String key
, String defaultValue
)
480 String prop
= getProperty(key
);
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
);
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)
534 public void list(PrintWriter out
)
536 out
.println ("-- listing properties --");
538 Iterator iter
= entrySet ().iterator ();
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) + "...");
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
)
571 buffer
.ensureCapacity(str
.length());
574 buffer
.ensureCapacity(buffer
.length() + str
.length());
576 int size
= str
.length();
577 for (int i
= 0; i
< size
; i
++)
579 char c
= str
.charAt(i
);
583 buffer
.append("\\n");
586 buffer
.append("\\r");
589 buffer
.append("\\t");
592 buffer
.append(head ?
"\\ " : " ");
599 buffer
.append('\\').append(c
);
602 if (c
< ' ' || c
> '~')
604 String hex
= Integer
.toHexString(c
);
605 buffer
.append("\\u0000".substring(0, 6 - hex
.length()));
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>.
624 * Invoking this method provides the same behaviour as invoking
625 * <code>storeToXML(os, comment, "UTF-8")</code>.
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.
635 public void storeToXML(OutputStream os
, String comment
)
638 storeToXML(os
, comment
, "UTF-8");
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>.
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>
658 public void storeToXML(OutputStream os
, String comment
, String encoding
)
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();
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
)
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
)
701 new IOException("The XML classes could not be found.").initCause(e
);
703 catch (InstantiationException e
)
706 new IOException("The XML classes could not be instantiated.")
709 catch (IllegalAccessException e
)
712 new IOException("The XML classes could not be accessed.")
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>.
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
731 * @throws NullPointerException if <code>in</code> is null.
734 public void loadFromXML(InputStream in
)
735 throws IOException
, InvalidPropertiesFormatException
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",
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");
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
);
770 case XMLStreamConstants
.END_ELEMENT
:
771 name
= reader
.getLocalName();
772 if (buf
!= null && "entry".equals(name
))
774 put(key
, buf
.toString());
777 else if (!"properties".equals(name
) && !"comment".equals(name
))
779 String msg
= "unexpected element name '" + name
+ "'";
780 throw new InvalidPropertiesFormatException(msg
);
783 case XMLStreamConstants
.CHARACTERS
:
784 case XMLStreamConstants
.SPACE
:
785 case XMLStreamConstants
.CDATA
:
787 buf
.append(reader
.getText());
793 catch (XMLStreamException e
)
795 throw (InvalidPropertiesFormatException
)
796 new InvalidPropertiesFormatException("Error in parsing XML.").
801 } // class Properties