OSDN Git Service

f00615ba0bfa8341ab1c62921938f33579415eec
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / util / Properties.java
1 /* Properties.java -- a set of persistent properties
2    Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
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.
10
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.
15
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.
20
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.
25
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. */
37
38
39 package java.util;
40
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;
49
50 /**
51  * A set of persistent properties, which can be saved or loaded from a stream.
52  * A property list may also contain defaults, searched if the main list
53  * does not contain a property for a given key.
54  *
55  * An example of a properties file for the german language is given
56  * here.  This extends the example given in ListResourceBundle.
57  * Create a file MyResource_de.properties with the following contents
58  * and put it in the CLASSPATH.  (The character
59  * <code>\</code><code>u00e4</code> is the german umlaut)
60  *
61  * 
62 <pre>s1=3
63 s2=MeineDisk
64 s3=3. M\<code></code>u00e4rz 96
65 s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
66 s5=0
67 s6=keine Dateien
68 s7=1
69 s8=eine Datei
70 s9=2
71 s10={0,number} Dateien
72 s11=Das Formatieren schlug fehl mit folgender Exception: {0}
73 s12=FEHLER
74 s13=Ergebnis
75 s14=Dialog
76 s15=Auswahlkriterium
77 s16=1,3</pre>
78  *
79  * <p>Although this is a sub class of a hash table, you should never
80  * insert anything other than strings to this property, or several
81  * methods, that need string keys and values, will fail.  To ensure
82  * this, you should use the <code>get/setProperty</code> method instead
83  * of <code>get/put</code>.
84  *
85  * Properties are saved in ISO 8859-1 encoding, using Unicode escapes with
86  * a single <code>u</code> for any character which cannot be represented.
87  *
88  * @author Jochen Hoenicke
89  * @author Eric Blake (ebb9@email.byu.edu)
90  * @see PropertyResourceBundle
91  * @status updated to 1.4
92  */
93 public class Properties extends Hashtable
94 {
95   // WARNING: Properties is a CORE class in the bootstrap cycle. See the
96   // comments in vm/reference/java/lang/Runtime for implications of this fact.
97
98   /**
99    * The property list that contains default values for any keys not
100    * in this property list.
101    *
102    * @serial the default properties
103    */
104   protected Properties defaults;
105
106   /**
107    * Compatible with JDK 1.0+.
108    */
109   private static final long serialVersionUID = 4112578634029874840L;
110
111   /**
112    * Creates a new empty property list with no default values.
113    */
114   public Properties()
115   {
116   }
117
118   /**
119    * Create a new empty property list with the specified default values.
120    *
121    * @param defaults a Properties object containing the default values
122    */
123   public Properties(Properties defaults)
124   {
125     this.defaults = defaults;
126   }
127
128   /**
129    * Adds the given key/value pair to this properties.  This calls
130    * the hashtable method put.
131    *
132    * @param key the key for this property
133    * @param value the value for this property
134    * @return The old value for the given key
135    * @see #getProperty(String)
136    * @since 1.2
137    */
138   public Object setProperty(String key, String value)
139   {
140     return put(key, value);
141   }
142
143   /**
144    * Reads a property list from an input stream.  The stream should
145    * have the following format: <br>
146    *
147    * An empty line or a line starting with <code>#</code> or
148    * <code>!</code> is ignored.  An backslash (<code>\</code>) at the
149    * end of the line makes the line continueing on the next line
150    * (but make sure there is no whitespace after the backslash).
151    * Otherwise, each line describes a key/value pair. <br>
152    *
153    * The chars up to the first whitespace, = or : are the key.  You
154    * can include this caracters in the key, if you precede them with
155    * a backslash (<code>\</code>). The key is followed by optional
156    * whitespaces, optionally one <code>=</code> or <code>:</code>,
157    * and optionally some more whitespaces.  The rest of the line is
158    * the resource belonging to the key. <br>
159    *
160    * Escape sequences <code>\t, \n, \r, \\, \", \', \!, \#, \ </code>(a
161    * space), and unicode characters with the
162    * <code>\\u</code><em>xxxx</em> notation are detected, and
163    * converted to the corresponding single character. <br>
164    *
165    * 
166 <pre># This is a comment
167 key     = value
168 k\:5      \ a string starting with space and ending with newline\n
169 # This is a multiline specification; note that the value contains
170 # no white space.
171 weekdays: Sunday,Monday,Tuesday,Wednesday,\\
172           Thursday,Friday,Saturday
173 # The safest way to include a space at the end of a value:
174 label   = Name:\\u0020</pre>
175    *
176    * @param inStream the input stream
177    * @throws IOException if an error occurred when reading the input
178    * @throws NullPointerException if in is null
179    */
180   public void load(InputStream inStream) throws IOException
181   {
182     // The spec says that the file must be encoded using ISO-8859-1.
183     BufferedReader reader =
184       new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1"));
185     String line;
186
187     while ((line = reader.readLine()) != null)
188       {
189         char c = 0;
190         int pos = 0;
191         // Leading whitespaces must be deleted first.
192         while (pos < line.length()
193                && Character.isWhitespace(c = line.charAt(pos)))
194           pos++;
195
196         // If empty line or begins with a comment character, skip this line.
197         if ((line.length() - pos) == 0
198             || line.charAt(pos) == '#' || line.charAt(pos) == '!')
199           continue;
200
201         // The characters up to the next Whitespace, ':', or '='
202         // describe the key.  But look for escape sequences.
203         StringBuffer key = new StringBuffer();
204         while (pos < line.length()
205                && ! Character.isWhitespace(c = line.charAt(pos++))
206                && c != '=' && c != ':')
207           {
208             if (c == '\\')
209               {
210                 if (pos == line.length())
211                   {
212                     // The line continues on the next line.  If there
213                     // is no next line, just treat it as a key with an
214                     // empty value.
215                     line = reader.readLine();
216                     if (line == null)
217                       line = "";
218                     pos = 0;
219                     while (pos < line.length()
220                            && Character.isWhitespace(c = line.charAt(pos)))
221                       pos++;
222                   }
223                 else
224                   {
225                     c = line.charAt(pos++);
226                     switch (c)
227                       {
228                       case 'n':
229                         key.append('\n');
230                         break;
231                       case 't':
232                         key.append('\t');
233                         break;
234                       case 'r':
235                         key.append('\r');
236                         break;
237                       case 'u':
238                         if (pos + 4 <= line.length())
239                           {
240                             char uni = (char) Integer.parseInt
241                               (line.substring(pos, pos + 4), 16);
242                             key.append(uni);
243                             pos += 4;
244                           }        // else throw exception?
245                         break;
246                       default:
247                         key.append(c);
248                         break;
249                       }
250                   }
251               }
252             else
253               key.append(c);
254           }
255
256         boolean isDelim = (c == ':' || c == '=');
257         while (pos < line.length()
258                && Character.isWhitespace(c = line.charAt(pos)))
259           pos++;
260
261         if (! isDelim && (c == ':' || c == '='))
262           {
263             pos++;
264             while (pos < line.length()
265                    && Character.isWhitespace(c = line.charAt(pos)))
266               pos++;
267           }
268
269         StringBuffer element = new StringBuffer(line.length() - pos);
270         while (pos < line.length())
271           {
272             c = line.charAt(pos++);
273             if (c == '\\')
274               {
275                 if (pos == line.length())
276                   {
277                     // The line continues on the next line.
278                     line = reader.readLine();
279
280                     // We might have seen a backslash at the end of
281                     // the file.  The JDK ignores the backslash in
282                     // this case, so we follow for compatibility.
283                     if (line == null)
284                       break;
285
286                     pos = 0;
287                     while (pos < line.length()
288                            && Character.isWhitespace(c = line.charAt(pos)))
289                       pos++;
290                     element.ensureCapacity(line.length() - pos +
291                                            element.length());
292                   }
293                 else
294                   {
295                     c = line.charAt(pos++);
296                     switch (c)
297                       {
298                       case 'n':
299                         element.append('\n');
300                         break;
301                       case 't':
302                         element.append('\t');
303                         break;
304                       case 'r':
305                         element.append('\r');
306                         break;
307                       case 'u':
308                         if (pos + 4 <= line.length())
309                           {
310                             char uni = (char) Integer.parseInt
311                               (line.substring(pos, pos + 4), 16);
312                             element.append(uni);
313                             pos += 4;
314                           }        // else throw exception?
315                         break;
316                       default:
317                         element.append(c);
318                         break;
319                       }
320                   }
321               }
322             else
323               element.append(c);
324           }
325         put(key.toString(), element.toString());
326       }
327   }
328
329   /**
330    * Calls <code>store(OutputStream out, String header)</code> and
331    * ignores the IOException that may be thrown.
332    *
333    * @param out the stream to write to
334    * @param header a description of the property list
335    * @throws ClassCastException if this property contains any key or
336    *         value that are not strings
337    * @deprecated use {@link #store(OutputStream, String)} instead
338    */
339   public void save(OutputStream out, String header)
340   {
341     try
342       {
343         store(out, header);
344       }
345     catch (IOException ex)
346       {
347       }
348   }
349
350   /**
351    * Writes the key/value pairs to the given output stream, in a format
352    * suitable for <code>load</code>.<br>
353    *
354    * If header is not null, this method writes a comment containing
355    * the header as first line to the stream.  The next line (or first
356    * line if header is null) contains a comment with the current date.
357    * Afterwards the key/value pairs are written to the stream in the
358    * following format.<br>
359    *
360    * Each line has the form <code>key = value</code>.  Newlines,
361    * Returns and tabs are written as <code>\n,\t,\r</code> resp.
362    * The characters <code>\, !, #, =</code> and <code>:</code> are
363    * preceeded by a backslash.  Spaces are preceded with a backslash,
364    * if and only if they are at the beginning of the key.  Characters
365    * that are not in the ascii range 33 to 127 are written in the
366    * <code>\</code><code>u</code>xxxx Form.<br>
367    *
368    * Following the listing, the output stream is flushed but left open.
369    *
370    * @param out the output stream
371    * @param header the header written in the first line, may be null
372    * @throws ClassCastException if this property contains any key or
373    *         value that isn't a string
374    * @throws IOException if writing to the stream fails
375    * @throws NullPointerException if out is null
376    * @since 1.2
377    */
378   public void store(OutputStream out, String header) throws IOException
379   {
380     // The spec says that the file must be encoded using ISO-8859-1.
381     PrintWriter writer
382       = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1"));
383     if (header != null)
384       writer.println("#" + header);
385     writer.println ("#" + Calendar.getInstance ().getTime ());
386     
387     Iterator iter = entrySet ().iterator ();
388     int i = size ();
389     StringBuffer s = new StringBuffer (); // Reuse the same buffer.
390     while (--i >= 0)
391       {
392         Map.Entry entry = (Map.Entry) iter.next ();
393         formatForOutput ((String) entry.getKey (), s, true);
394         s.append ('=');
395         formatForOutput ((String) entry.getValue (), s, false);
396         writer.println (s);
397       }
398
399     writer.flush ();
400   }
401
402   /**
403    * Gets the property with the specified key in this property list.
404    * If the key is not found, the default property list is searched.
405    * If the property is not found in the default, null is returned.
406    *
407    * @param key The key for this property
408    * @return the value for the given key, or null if not found
409    * @throws ClassCastException if this property contains any key or
410    *         value that isn't a string
411    * @see #defaults
412    * @see #setProperty(String, String)
413    * @see #getProperty(String, String)
414    */
415   public String getProperty(String key)
416   {
417     Properties prop = this;
418     // Eliminate tail recursion.
419     do
420       {
421         String value = (String) prop.get(key);
422         if (value != null)
423           return value;
424         prop = prop.defaults;
425       }
426     while (prop != null);
427     return null;
428   }
429
430   /**
431    * Gets the property with the specified key in this property list.  If
432    * the key is not found, the default property list is searched.  If the
433    * property is not found in the default, the specified defaultValue is
434    * returned.
435    *
436    * @param key The key for this property
437    * @param defaultValue A default value
438    * @return The value for the given key
439    * @throws ClassCastException if this property contains any key or
440    *         value that isn't a string
441    * @see #defaults
442    * @see #setProperty(String, String)
443    */
444   public String getProperty(String key, String defaultValue)
445   {
446     String prop = getProperty(key);
447     if (prop == null)
448       prop = defaultValue;
449     return prop;
450   }
451
452   /**
453    * Returns an enumeration of all keys in this property list, including
454    * the keys in the default property list.
455    *
456    * @return an Enumeration of all defined keys
457    */
458   public Enumeration propertyNames()
459   {
460     // We make a new Set that holds all the keys, then return an enumeration
461     // for that. This prevents modifications from ruining the enumeration,
462     // as well as ignoring duplicates.
463     Properties prop = this;
464     Set s = new HashSet();
465     // Eliminate tail recursion.
466     do
467       {
468         s.addAll(prop.keySet());
469         prop = prop.defaults;
470       }
471     while (prop != null);
472     return Collections.enumeration(s);
473   }
474
475   /**
476    * Prints the key/value pairs to the given print stream.  This is 
477    * mainly useful for debugging purposes.
478    *
479    * @param out the print stream, where the key/value pairs are written to
480    * @throws ClassCastException if this property contains a key or a
481    *         value that isn't a string
482    * @see #list(PrintWriter)
483    */
484   public void list(PrintStream out)
485   {
486     PrintWriter writer = new PrintWriter (out);
487     list (writer);
488   }
489
490   /**
491    * Prints the key/value pairs to the given print writer.  This is
492    * mainly useful for debugging purposes.
493    *
494    * @param out the print writer where the key/value pairs are written to
495    * @throws ClassCastException if this property contains a key or a
496    *         value that isn't a string
497    * @see #list(PrintStream)
498    * @since 1.1
499    */
500   public void list(PrintWriter out)
501   {
502     out.println ("-- listing properties --");
503
504     Iterator iter = entrySet ().iterator ();
505     int i = size ();
506     while (--i >= 0)
507       {
508         Map.Entry entry = (Map.Entry) iter.next ();
509         out.print ((String) entry.getKey () + "=");
510
511         // JDK 1.3/1.4 restrict the printed value, but not the key,
512         // to 40 characters, including the truncating ellipsis.
513         String s = (String ) entry.getValue ();
514         if (s != null && s.length () > 40)
515           out.println (s.substring (0, 37) + "...");
516         else
517           out.println (s);
518       }
519     out.flush ();
520   }
521
522   /**
523    * Formats a key or value for output in a properties file.
524    * See store for a description of the format.
525    *
526    * @param str the string to format
527    * @param buffer the buffer to add it to
528    * @param key true if all ' ' must be escaped for the key, false if only
529    *        leading spaces must be escaped for the value
530    * @see #store(OutputStream, String)
531    */
532   private void formatForOutput(String str, StringBuffer buffer, boolean key)
533   {
534     if (key)
535       {
536         buffer.setLength(0);
537         buffer.ensureCapacity(str.length());
538       }
539     else
540       buffer.ensureCapacity(buffer.length() + str.length());
541     boolean head = true;
542     int size = str.length();
543     for (int i = 0; i < size; i++)
544       {
545         char c = str.charAt(i);
546         switch (c)
547           {
548           case '\n':
549             buffer.append("\\n");
550             break;
551           case '\r':
552             buffer.append("\\r");
553             break;
554           case '\t':
555             buffer.append("\\t");
556             break;
557           case ' ':
558             buffer.append(head ? "\\ " : " ");
559             break;
560           case '\\':
561           case '!':
562           case '#':
563           case '=':
564           case ':':
565             buffer.append('\\').append(c);
566             break;
567           default:
568             if (c < ' ' || c > '~')
569               {
570                 String hex = Integer.toHexString(c);
571                 buffer.append("\\u0000".substring(0, 6 - hex.length()));
572                 buffer.append(hex);
573               }
574             else
575               buffer.append(c);
576           }
577         if (c != ' ')
578           head = key;
579       }
580   }
581 } // class Properties