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. */
40 * A set of persistent properties, which can be saved or loaded from a stream.
41 * A property list may also contain defaults, searched if the main list
42 * does not contain a property for a given key.
44 * An example of a properties file for the german language is given
45 * here. This extends the example given in ListResourceBundle.
46 * Create a file MyResource_de.properties with the following contents
47 * and put it in the CLASSPATH. (The character
48 * <code>\</code><code>u00e4</code> is the german umlaut)
53 s3=3. M\<code></code>u00e4rz 96
54 s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
60 s10={0,number} Dateien
61 s11=Das Formatieren schlug fehl mit folgender Exception: {0}
68 * <p>Although this is a sub class of a hash table, you should never
69 * insert anything other than strings to this property, or several
70 * methods, that need string keys and values, will fail. To ensure
71 * this, you should use the <code>get/setProperty</code> method instead
72 * of <code>get/put</code>.
74 * Properties are saved in ISO 8859-1 encoding, using Unicode escapes with
75 * a single <code>u</code> for any character which cannot be represented.
77 * @author Jochen Hoenicke
78 * @author Eric Blake (ebb9@email.byu.edu)
79 * @see PropertyResourceBundle
80 * @status updated to 1.4
82 public class Properties //extends Hashtable//<Object, Object>
84 // WARNING: Properties is a CORE class in the bootstrap cycle. See the
85 // comments in vm/reference/java/lang/Runtime for implications of this fact.
88 * The property list that contains default values for any keys not
89 * in this property list.
91 * @serial the default properties
95 protected Properties defaults;
98 * Compatible with JDK 1.0+.
100 private static final long serialVersionUID = 4112578634029874840L;
103 * Creates a new empty property list with no default values.
107 proptbl = new Hashtable();
111 * Create a new empty property list with the specified default values.
113 * @param defaults a Properties object containing the default values
115 public Properties(Properties defaults)
117 proptbl = new Hashtable();
118 this.defaults = defaults;
122 * Adds the given key/value pair to this properties. This calls
123 * the hashtable method put.
125 * @param key the key for this property
126 * @param value the value for this property
127 * @return The old value for the given key
128 * @see #getProperty(String)
131 public Object setProperty(String key, String value)
133 return proptbl.put(key, value);
136 public Object put(String key, String value)
138 return proptbl.put(key, value);
142 * Reads a property list from a character stream. The stream should
143 * have the following format: <br>
145 * An empty line or a line starting with <code>#</code> or
146 * <code>!</code> is ignored. An backslash (<code>\</code>) at the
147 * end of the line makes the line continueing on the next line
148 * (but make sure there is no whitespace after the backslash).
149 * Otherwise, each line describes a key/value pair. <br>
151 * The chars up to the first whitespace, = or : are the key. You
152 * can include this caracters in the key, if you precede them with
153 * a backslash (<code>\</code>). The key is followed by optional
154 * whitespaces, optionally one <code>=</code> or <code>:</code>,
155 * and optionally some more whitespaces. The rest of the line is
156 * the resource belonging to the key. <br>
158 * Escape sequences <code>\t, \n, \r, \\, \", \', \!, \#, \ </code>(a
159 * space), and unicode characters with the
160 * <code>\\u</code><em>xxxx</em> notation are detected, and
161 * converted to the corresponding single character. <br>
164 <pre># This is a comment
166 k\:5 \ a string starting with space and ending with newline\n
167 # This is a multiline specification; note that the value contains
169 weekdays: Sunday,Monday,Tuesday,Wednesday,\\
170 Thursday,Friday,Saturday
171 # The safest way to include a space at the end of a value:
172 label = Name:\\u0020</pre>
174 * @param inReader the input {@link java.io.Reader}.
175 * @throws IOException if an error occurred when reading the input
176 * @throws NullPointerException if in is null
179 /*public void load(Reader inReader) throws IOException
181 BufferedReader reader = new BufferedReader(inReader);
184 while ((line = reader.readLine()) != null)
188 // Leading whitespaces must be deleted first.
189 while (pos < line.length()
190 && Character.isWhitespace(c = line.charAt(pos)))
193 // If empty line or begins with a comment character, skip this line.
194 if ((line.length() - pos) == 0
195 || line.charAt(pos) == '#' || line.charAt(pos) == '!')
198 // The characters up to the next Whitespace, ':', or '='
199 // describe the key. But look for escape sequences.
200 // Try to short-circuit when there is no escape char.
202 boolean needsEscape = line.indexOf('\\', pos) != -1;
203 CPStringBuilder key = needsEscape ? new CPStringBuilder() : null;
204 while (pos < line.length()
205 && ! Character.isWhitespace(c = line.charAt(pos++))
206 && c != '=' && c != ':')
208 if (needsEscape && c == '\\')
210 if (pos == line.length())
212 // The line continues on the next line. If there
213 // is no next line, just treat it as a key with an
215 line = reader.readLine();
219 while (pos < line.length()
220 && Character.isWhitespace(c = line.charAt(pos)))
225 c = line.charAt(pos++);
238 if (pos + 4 <= line.length())
240 char uni = (char) Integer.parseInt
241 (line.substring(pos, pos + 4), 16);
244 } // else throw exception?
252 else if (needsEscape)
256 boolean isDelim = (c == ':' || c == '=');
260 keyString = key.toString();
261 else if (isDelim || Character.isWhitespace(c))
262 keyString = line.substring(start, pos - 1);
264 keyString = line.substring(start, pos);
266 while (pos < line.length()
267 && Character.isWhitespace(c = line.charAt(pos)))
270 if (! isDelim && (c == ':' || c == '='))
273 while (pos < line.length()
274 && Character.isWhitespace(c = line.charAt(pos)))
278 // Short-circuit if no escape chars found.
281 put(keyString, line.substring(pos));
285 // Escape char found so iterate through the rest of the line.
286 StringBuilder element = new StringBuilder(line.length() - pos);
287 while (pos < line.length())
289 c = line.charAt(pos++);
292 if (pos == line.length())
294 // The line continues on the next line.
295 line = reader.readLine();
297 // We might have seen a backslash at the end of
298 // the file. The JDK ignores the backslash in
299 // this case, so we follow for compatibility.
304 while (pos < line.length()
305 && Character.isWhitespace(c = line.charAt(pos)))
307 element.ensureCapacity(line.length() - pos +
312 c = line.charAt(pos++);
316 element.append('\n');
319 element.append('\t');
322 element.append('\r');
325 if (pos + 4 <= line.length())
327 char uni = (char) Integer.parseInt
328 (line.substring(pos, pos + 4), 16);
331 } // else throw exception?
342 put(keyString, element.toString());
347 * Reads a property list from the supplied input stream.
348 * This method has the same functionality as {@link #load(Reader)}
349 * but the character encoding is assumed to be ISO-8859-1.
350 * Unicode characters not within the Latin1 set supplied by
351 * ISO-8859-1 should be escaped using '\\uXXXX' where XXXX
352 * is the UTF-16 code unit in hexadecimal.
354 * @param inStream the byte stream to read the property list from.
355 * @throws IOException if an I/O error occurs.
359 public void load(InputStream inStream) throws IOException
361 //load(new InputStreamReader(inStream, "ISO-8859-1"));
362 //TODO System.println("Properties.load(InputStream) invoked");
366 * Calls <code>store(OutputStream out, String header)</code> and
367 * ignores the IOException that may be thrown.
369 * @param out the stream to write to
370 * @param header a description of the property list
371 * @throws ClassCastException if this property contains any key or
372 * value that are not strings
373 * @deprecated use {@link #store(OutputStream, String)} instead
376 /*public void save(OutputStream out, String header)
382 catch (IOException ex)
388 * Writes the key/value pairs to the given output stream, in a format
389 * suitable for <code>load</code>.<br>
391 * If header is not null, this method writes a comment containing
392 * the header as first line to the stream. The next line (or first
393 * line if header is null) contains a comment with the current date.
394 * Afterwards the key/value pairs are written to the stream in the
395 * following format.<br>
397 * Each line has the form <code>key = value</code>. Newlines,
398 * Returns and tabs are written as <code>\n,\t,\r</code> resp.
399 * The characters <code>\, !, #, =</code> and <code>:</code> are
400 * preceeded by a backslash. Spaces are preceded with a backslash,
401 * if and only if they are at the beginning of the key. Characters
402 * that are not in the ascii range 33 to 127 are written in the
403 * <code>\</code><code>u</code>xxxx Form.<br>
405 * Following the listing, the output stream is flushed but left open.
407 * @param out the output stream
408 * @param header the header written in the first line, may be null
409 * @throws ClassCastException if this property contains any key or
410 * value that isn't a string
411 * @throws IOException if writing to the stream fails
412 * @throws NullPointerException if out is null
415 public void store(OutputStream out, String header)// throws IOException
417 // The spec says that the file must be encoded using ISO-8859-1.
419 = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1"));
421 writer.println("#" + header);
422 writer.println ("#" + Calendar.getInstance ().getTime ());
424 Iterator iter = entrySet ().iterator ();
426 CPStringBuilder s = new CPStringBuilder (); // Reuse the same buffer.
429 Map.Entry entry = (Map.Entry) iter.next ();
430 formatForOutput ((String) entry.getKey (), s, true);
432 formatForOutput ((String) entry.getValue (), s, false);
437 System.println("Properties.store() invoked");
441 * Gets the property with the specified key in this property list.
442 * If the key is not found, the default property list is searched.
443 * If the property is not found in the default, null is returned.
445 * @param key The key for this property
446 * @return the value for the given key, or null if not found
447 * @throws ClassCastException if this property contains any key or
448 * value that isn't a string
450 * @see #setProperty(String, String)
451 * @see #getProperty(String, String)
453 public String getProperty(String key)
455 Hashtable tbl = this.proptbl;
456 // Eliminate tail recursion.
457 Properties prop = this.defaults;
460 String value = (String) tbl.get(key);
464 prop = prop.defaults;
466 String value = (String) tbl.get(key);
474 * Gets the property with the specified key in this property list. If
475 * the key is not found, the default property list is searched. If the
476 * property is not found in the default, the specified defaultValue is
479 * @param key The key for this property
480 * @param defaultValue A default value
481 * @return The value for the given key
482 * @throws ClassCastException if this property contains any key or
483 * value that isn't a string
485 * @see #setProperty(String, String)
487 public String getProperty(String key, String defaultValue)
489 String prop = getProperty(key);
496 * Returns an enumeration of all keys in this property list, including
497 * the keys in the default property list.
499 * @return an Enumeration of all defined keys
501 public Enumeration/*<?>*/ propertyNames()
503 // We make a new Set that holds all the keys, then return an enumeration
504 // for that. This prevents modifications from ruining the enumeration,
505 // as well as ignoring duplicates.
506 Properties prop = this.defaults;
507 Object[] tarray = keySet().toArray();
508 HashSet s = new HashSet();
509 for(int i = 0; i < tarray.length; i++) {
512 // Eliminate tail recursion.
515 tarray = prop.keySet().toArray();
516 for(int i = 0; i < tarray.length; i++) {
519 prop = prop.defaults;
521 return new Enumeration(); //Collections.enumeration(s);
524 public Set keySet() {
525 HashMapIterator it = (HashMapIterator)this.proptbl.iterator(0);
526 Set keys = new Vector();
527 while(it.hasNext()) {
534 * Prints the key/value pairs to the given print stream. This is
535 * mainly useful for debugging purposes.
537 * @param out the print stream, where the key/value pairs are written to
538 * @throws ClassCastException if this property contains a key or a
539 * value that isn't a string
540 * @see #list(PrintWriter)
542 /*public void list(PrintStream out)
544 PrintWriter writer = new PrintWriter (out);
549 * Prints the key/value pairs to the given print writer. This is
550 * mainly useful for debugging purposes.
552 * @param out the print writer where the key/value pairs are written to
553 * @throws ClassCastException if this property contains a key or a
554 * value that isn't a string
555 * @see #list(PrintStream)
558 /*public void list(PrintWriter out)
560 out.println ("-- listing properties --");
562 Iterator iter = entrySet ().iterator ();
566 Map.Entry entry = (Map.Entry) iter.next ();
567 out.print ((String) entry.getKey () + "=");
569 // JDK 1.3/1.4 restrict the printed value, but not the key,
570 // to 40 characters, including the truncating ellipsis.
571 String s = (String ) entry.getValue ();
572 if (s != null && s.length () > 40)
573 out.println (s.substring (0, 37) + "...");
581 * Formats a key or value for output in a properties file.
582 * See store for a description of the format.
584 * @param str the string to format
585 * @param buffer the buffer to add it to
586 * @param key true if all ' ' must be escaped for the key, false if only
587 * leading spaces must be escaped for the value
588 * @see #store(OutputStream, String)
590 /*private void formatForOutput(String str, CPStringBuilder buffer, boolean key)
595 buffer.ensureCapacity(str.length());
598 buffer.ensureCapacity(buffer.length() + str.length());
600 int size = str.length();
601 for (int i = 0; i < size; i++)
603 char c = str.charAt(i);
607 buffer.append("\\n");
610 buffer.append("\\r");
613 buffer.append("\\t");
616 buffer.append(head ? "\\ " : " ");
623 buffer.append('\\').append(c);
626 if (c < ' ' || c > '~')
628 String hex = Integer.toHexString(c);
629 buffer.append("\\u0000".substring(0, 6 - hex.length()));
642 * Encodes the properties as an XML file using the UTF-8 encoding.
643 * The format of the XML file matches the DTD
644 * <a href="http://java.sun.com/dtd/properties.dtd">
645 * http://java.sun.com/dtd/properties.dtd</a>.
648 * Invoking this method provides the same behaviour as invoking
649 * <code>storeToXML(os, comment, "UTF-8")</code>.
652 * @param os the stream to output to.
653 * @param comment a comment to include at the top of the XML file, or
654 * <code>null</code> if one is not required.
655 * @throws IOException if the serialization fails.
656 * @throws NullPointerException if <code>os</code> is null.
659 /*public void storeToXML(OutputStream os, String comment)
662 storeToXML(os, comment, "UTF-8");
667 * Encodes the properties as an XML file using the supplied encoding.
668 * The format of the XML file matches the DTD
669 * <a href="http://java.sun.com/dtd/properties.dtd">
670 * http://java.sun.com/dtd/properties.dtd</a>.
673 * @param os the stream to output to.
674 * @param comment a comment to include at the top of the XML file, or
675 * <code>null</code> if one is not required.
676 * @param encoding the encoding to use for the XML output.
677 * @throws IOException if the serialization fails.
678 * @throws NullPointerException if <code>os</code> or <code>encoding</code>
682 /*public void storeToXML(OutputStream os, String comment, String encoding)
686 throw new NullPointerException("Null output stream supplied.");
687 if (encoding == null)
688 throw new NullPointerException("Null encoding supplied.");
691 DOMImplementationRegistry registry =
692 DOMImplementationRegistry.newInstance();
693 DOMImplementation domImpl = registry.getDOMImplementation("LS 3.0");
694 DocumentType doctype =
695 domImpl.createDocumentType("properties", null,
696 "http://java.sun.com/dtd/properties.dtd");
697 Document doc = domImpl.createDocument(null, "properties", doctype);
698 Element root = doc.getDocumentElement();
701 Element commentElement = doc.createElement("comment");
702 commentElement.appendChild(doc.createTextNode(comment));
703 root.appendChild(commentElement);
705 Iterator iterator = entrySet().iterator();
706 while (iterator.hasNext())
708 Map.Entry entry = (Map.Entry) iterator.next();
709 Element entryElement = doc.createElement("entry");
710 entryElement.setAttribute("key", (String) entry.getKey());
711 entryElement.appendChild(doc.createTextNode((String)
713 root.appendChild(entryElement);
715 DOMImplementationLS loadAndSave = (DOMImplementationLS) domImpl;
716 LSSerializer serializer = loadAndSave.createLSSerializer();
717 LSOutput output = loadAndSave.createLSOutput();
718 output.setByteStream(os);
719 output.setEncoding(encoding);
720 serializer.write(doc, output);
722 catch (ClassNotFoundException e)
725 new IOException("The XML classes could not be found.").initCause(e);
727 catch (InstantiationException e)
730 new IOException("The XML classes could not be instantiated.")
733 catch (IllegalAccessException e)
736 new IOException("The XML classes could not be accessed.")
743 * Decodes the contents of the supplied <code>InputStream</code> as
744 * an XML file, which represents a set of properties. The format of
745 * the XML file must match the DTD
746 * <a href="http://java.sun.com/dtd/properties.dtd">
747 * http://java.sun.com/dtd/properties.dtd</a>.
750 * @param in the input stream from which to receive the XML data.
751 * @throws IOException if an I/O error occurs in reading the input data.
752 * @throws InvalidPropertiesFormatException if the input data does not
753 * constitute an XML properties
755 * @throws NullPointerException if <code>in</code> is null.
758 /*public void loadFromXML(InputStream in)
759 throws IOException, InvalidPropertiesFormatException
762 throw new NullPointerException("Null input stream supplied.");
765 XMLInputFactory factory = XMLInputFactory.newInstance();
766 // Don't resolve external entity references
767 factory.setProperty("javax.xml.stream.isSupportingExternalEntities",
769 XMLStreamReader reader = factory.createXMLStreamReader(in);
770 String name, key = null;
771 CPStringBuilder buf = null;
772 while (reader.hasNext())
774 switch (reader.next())
776 case XMLStreamConstants.START_ELEMENT:
777 name = reader.getLocalName();
778 if (buf == null && "entry".equals(name))
780 key = reader.getAttributeValue(null, "key");
783 String msg = "missing 'key' attribute";
784 throw new InvalidPropertiesFormatException(msg);
786 buf = new CPStringBuilder();
788 else if (!"properties".equals(name) && !"comment".equals(name))
790 String msg = "unexpected element name '" + name + "'";
791 throw new InvalidPropertiesFormatException(msg);
794 case XMLStreamConstants.END_ELEMENT:
795 name = reader.getLocalName();
796 if (buf != null && "entry".equals(name))
798 put(key, buf.toString());
801 else if (!"properties".equals(name) && !"comment".equals(name))
803 String msg = "unexpected element name '" + name + "'";
804 throw new InvalidPropertiesFormatException(msg);
807 case XMLStreamConstants.CHARACTERS:
808 case XMLStreamConstants.SPACE:
809 case XMLStreamConstants.CDATA:
811 buf.append(reader.getText());
817 catch (XMLStreamException e)
819 throw (InvalidPropertiesFormatException)
820 new InvalidPropertiesFormatException("Error in parsing XML.").
825 } // class Properties