OSDN Git Service

* java/util/Properties.java (load): Correctly read \u sequences.
[pf3gnuchains/gcc-fork.git] / libjava / java / util / Properties.java
1 /* java.util.Properties
2    Copyright (C) 1998, 1999, 2000, 2001 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., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20
21 As a special exception, if you link this library with other files to
22 produce an executable, this library does not by itself cause the
23 resulting executable to be covered by the GNU General Public License.
24 This exception does not however invalidate any other reasons why the
25 executable file might be covered by the GNU General Public License. */
26
27
28 package java.util;
29 import java.io.*;
30
31 /**
32  * An example of a properties file for the german language is given
33  * here.  This extends the example given in ListResourceBundle.
34  * Create a file MyResource_de.properties with the following contents
35  * and put it in the CLASSPATH.  (The character
36  * <code>\</code><code>u00e4</code> is the german &auml;)
37  * 
38  * <pre>
39  * s1=3
40  * s2=MeineDisk
41  * s3=3. M\<code></code>u00e4rz 96
42  * s4=Die Diskette ''{1}'' enth\<code></code>u00e4lt {0} in {2}.
43  * s5=0
44  * s6=keine Dateien
45  * s7=1
46  * s8=eine Datei
47  * s9=2
48  * s10={0,number} Dateien
49  * s11=Das Formatieren schlug fehl mit folgender Exception: {0}
50  * s12=FEHLER
51  * s13=Ergebnis
52  * s14=Dialog
53  * s15=Auswahlkriterium
54  * s16=1,3
55  * </pre>
56  *
57  * Although this is a sub class of a hash table, you should never
58  * insert anything other than strings to this property, or several
59  * methods, that need string keys and values, will fail.  To ensure
60  * this, you should use the <code>get/setProperty</code> method instead
61  * of <code>get/put</code>.
62  *
63  * @see PropertyResourceBundle
64  * @author Jochen Hoenicke
65  */
66 public class Properties extends Hashtable
67 {
68   /**
69    * The property list that contains default values for any keys not
70    * in this property list.  
71    */
72   protected Properties defaults;
73
74   private static final long serialVersionUID = 4112578634029874840L;
75
76   /**
77    * Creates a new empty property list.
78    */
79   public Properties()
80   {
81     this.defaults = null;
82   }
83
84   /**
85    * Create a new empty property list with the specified default values.
86    * @param defaults a Properties object containing the default values.
87    */
88   public Properties(Properties defaults)
89   {
90     this.defaults = defaults;
91   }
92
93   /**
94    * Reads a property list from an input stream.  The stream should
95    * have the following format: <br>
96    *
97    * An empty line or a line starting with <code>#</code> or
98    * <code>!</code> is ignored.  An backslash (<code>\</code>) at the
99    * end of the line makes the line continueing on the next line
100    * (but make sure there is no whitespace after the backslash).
101    * Otherwise, each line describes a key/value pair. <br>
102    *
103    * The chars up to the first whitespace, = or : are the key.  You
104    * can include this caracters in the key, if you precede them with
105    * a backslash (<code>\</code>). The key is followed by optional
106    * whitespaces, optionally one <code>=</code> or <code>:</code>,
107    * and optionally some more whitespaces.  The rest of the line is
108    * the resource belonging to the key. <br>
109    *
110    * Escape sequences <code>\t, \n, \r, \\, \", \', \!, \#, \ </code>(a
111    * space), and unicode characters with the
112    * <code>\</code><code>u</code>xxxx notation are detected, and 
113    * converted to the corresponding single character. <br>
114    *
115    * <pre>
116    * # This is a comment
117    * key     = value
118    * k\:5      \ a string starting with space and ending with newline\n
119    * # This is a multiline specification; note that the value contains
120    * # no white space.
121    * weekdays: Sunday,Monday,Tuesday,Wednesday,\
122    *           Thursday,Friday,Saturday
123    * # The safest way to include a space at the end of a value:
124    * label   = Name:\<code></code>u0020
125    * </pre>
126    *
127    * @param in the input stream
128    * @exception IOException if an error occured when reading
129    * from the input.  */
130   public void load(InputStream inStream) throws IOException
131   {
132     // The spec says that the file must be encoded using ISO-8859-1.
133     BufferedReader reader =
134       new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1"));
135     String line;
136     
137     while ((line = reader.readLine()) != null)
138       {
139         char c = 0;
140         int pos = 0;
141         while (pos < line.length()
142                && Character.isWhitespace(c = line.charAt(pos)))
143           pos++;
144
145         // If line is empty or begins with a comment character,
146         // skip this line.
147         if (pos == line.length() || c == '#' || c == '!')
148           continue;
149
150         // The characters up to the next Whitespace, ':', or '='
151         // describe the key.  But look for escape sequences.
152         StringBuffer key = new StringBuffer();
153         while (pos < line.length()
154                && !Character.isWhitespace(c = line.charAt(pos++))
155                && c != '=' && c != ':')
156           {
157             if (c == '\\')
158               {
159                 if (pos == line.length())
160                   {
161                     // The line continues on the next line.
162                     line = reader.readLine();
163                     pos = 0;
164                     while (pos < line.length()
165                            && Character.isWhitespace(c = line.charAt(pos)))
166                       pos++;
167                   }
168                 else
169                   {
170                     c = line.charAt(pos++);
171                     switch (c)
172                       {
173                       case 'n':
174                         key.append('\n');
175                         break;
176                       case 't':
177                         key.append('\t');
178                         break;
179                       case 'r':
180                         key.append('\r');
181                         break;
182                       case 'u':
183                         if (pos + 4 <= line.length())
184                           {
185                             char uni = (char) Integer.parseInt
186                               (line.substring(pos, pos + 4), 16);
187                             key.append(uni);
188                             pos += 4;
189                           }     // else throw exception?
190                         break;
191                       default:
192                         key.append(c);
193                         break;
194                       }
195                   }
196               }
197             else
198               key.append(c);
199           }
200
201         boolean isDelim = (c == ':' || c == '=');
202         while (pos < line.length()
203                && Character.isWhitespace(c = line.charAt(pos)))
204           pos++;
205
206         if (!isDelim && (c == ':' || c == '='))
207           {
208             pos++;
209             while (pos < line.length()
210                    && Character.isWhitespace(c = line.charAt(pos)))
211               pos++;
212           }
213
214         StringBuffer element = new StringBuffer(line.length() - pos);
215         while (pos < line.length())
216           {
217             c = line.charAt(pos++);
218             if (c == '\\')
219               {
220                 if (pos == line.length())
221                   {
222                     // The line continues on the next line.
223                     line = reader.readLine();
224                     pos = 0;
225                     while (pos < line.length()
226                            && Character.isWhitespace(c = line.charAt(pos)))
227                       pos++;
228                     element.ensureCapacity(line.length() - pos +
229                                            element.length());
230                   }
231                 else
232                   {
233                     c = line.charAt(pos++);
234                     switch (c)
235                       {
236                       case 'n':
237                         element.append('\n');
238                         break;
239                       case 't':
240                         element.append('\t');
241                         break;
242                       case 'r':
243                         element.append('\r');
244                         break;
245                       case 'u':
246                         if (pos + 4 <= line.length())
247                           {
248                             char uni = (char) Integer.parseInt
249                               (line.substring(pos, pos + 4), 16);
250                             element.append(uni);
251                             pos += 4;
252                           }     // else throw exception?
253                         break;
254                       default:
255                         element.append(c);
256                         break;
257                       }
258                   }
259               }
260             else
261               element.append(c);
262           }
263         put(key.toString(), element.toString());
264       }
265   }
266
267   /**
268    * Calls <code>store(OutputStream out, String header)</code> and
269    * ignores the IOException that may be thrown.
270    * @deprecated use store instead.
271    * @exception ClassCastException if this property contains any key or
272    * value that isn't a string.
273    */
274   public void save(OutputStream out, String header)
275   {
276     try
277       {
278         store(out, header);
279       }
280     catch (IOException ex)
281       {
282       }
283   }
284
285   /**
286    * Writes the key/value pairs to the given output stream. <br>
287    *
288    * If header is not null, this method writes a comment containing
289    * the header as first line to the stream.  The next line (or first
290    * line if header is null) contains a comment with the current date.
291    * Afterwards the key/value pairs are written to the stream in the
292    * following format. <br>
293    *
294    * Each line has the form <code>key = value</code>.  Newlines,
295    * Returns and tabs are written as <code>\n,\t,\r</code> resp.
296    * The characters <code>\, !, #, =</code> and <code>:</code> are
297    * preceeded by a backslash.  Spaces are preceded with a backslash,
298    * if and only if they are at the beginning of the key.  Characters
299    * that are not in the ascii range 33 to 127 are written in the
300    * <code>\</code><code>u</code>xxxx Form.
301    *
302    * @param out the output stream
303    * @param header the header written in the first line, may be null.
304    * @exception ClassCastException if this property contains any key or
305    * value that isn't a string.
306    */
307   public void store(OutputStream out, String header) throws IOException
308   {
309     // The spec says that the file must be encoded using ISO-8859-1.
310     PrintWriter writer
311       = new PrintWriter(new OutputStreamWriter (out, "ISO-8859-1"));
312     if (header != null)
313       writer.println("#" + header);
314     writer.println("#" + new Date().toString());
315     list(writer);
316     writer.flush();
317   }
318
319   /**
320    * Adds the given key/value pair to this properties.  This calls
321    * the hashtable method put.
322    * @param key the key for this property
323    * @param value the value for this property
324    * @return The old value for the given key.
325    * @since JDK1.2 */
326   public Object setProperty(String key, String value)
327   {
328     return put(key, value);
329   }
330
331   /**
332    * Gets the property with the specified key in this property list.
333    * If the key is not found, the default property list is searched.
334    * If the property is not found in default or the default of
335    * default, null is returned.
336    * @param key The key for this property.
337    * @param defaulValue A default value
338    * @return The value for the given key, or null if not found. 
339    * @exception ClassCastException if this property contains any key or
340    * value that isn't a string.
341    */
342   public String getProperty(String key)
343   {
344     return getProperty(key, null);
345   }
346
347   /**
348    * Gets the property with the specified key in this property list.  If
349    * the key is not found, the default property list is searched.  If the
350    * property is not found in default or the default of default, the 
351    * specified defaultValue is returned.
352    * @param key The key for this property.
353    * @param defaulValue A default value
354    * @return The value for the given key.
355    * @exception ClassCastException if this property contains any key or
356    * value that isn't a string.
357    */
358   public String getProperty(String key, String defaultValue)
359   {
360     Properties prop = this;
361     // Eliminate tail recursion.
362     do
363       {
364         String value = (String) prop.get(key);
365         if (value != null)
366           return value;
367         prop = prop.defaults;
368       }
369     while (prop != null);
370     return defaultValue;
371   }
372
373   private final void addHashEntries(Hashtable base)
374   {
375     if (defaults != null)
376       defaults.addHashEntries(base);
377     Enumeration keys = keys();
378     while (keys.hasMoreElements())
379       base.put(keys.nextElement(), base);
380   }
381
382   /**
383    * Returns an enumeration of all keys in this property list, including
384    * the keys in the default property list.
385    */
386   public Enumeration propertyNames()
387   {
388     // We make a new Hashtable that holds all the keys.  Then we
389     // return an enumeration for this hash.  We do this because we
390     // don't want modifications to be reflected in the enumeration
391     // (per JCL), and because there doesn't seem to be a
392     // particularly better way to ensure that duplicates are
393     // ignored.
394     Hashtable t = new Hashtable();
395     addHashEntries(t);
396     return t.keys();
397   }
398
399   /**
400    * Formats a key/value pair for output in a properties file.
401    * See store for a description of the format.
402    * @param key the key.
403    * @param value the value.
404    * @see #store
405    */
406   private String formatForOutput(String key, String value)
407   {
408     // This is a simple approximation of the expected line size.
409     StringBuffer result =
410       new StringBuffer(key.length() + value.length() + 16);
411     boolean head = true;
412     for (int i = 0; i < key.length(); i++)
413       {
414         char c = key.charAt(i);
415         switch (c)
416           {
417           case '\n':
418             result.append("\\n");
419             break;
420           case '\r':
421             result.append("\\r");
422             break;
423           case '\t':
424             result.append("\\t");
425             break;
426           case '\\':
427             result.append("\\\\");
428             break;
429           case '!':
430             result.append("\\!");
431             break;
432           case '#':
433             result.append("\\#");
434             break;
435           case '=':
436             result.append("\\=");
437             break;
438           case ':':
439             result.append("\\:");
440             break;
441           case ' ':
442             result.append("\\ ");
443             break;
444           default:
445             if (c < 32 || c > '~')
446               {
447                 String hex = Integer.toHexString(c);
448                 result.append("\\u0000".substring(0, 6 - hex.length()));
449                 result.append(hex);
450               }
451             else
452                 result.append(c);
453           }
454         if (c != 32)
455           head = false;
456       }
457     result.append('=');
458     head = true;
459     for (int i = 0; i < value.length(); i++)
460       {
461         char c = value.charAt(i);
462         switch (c)
463           {
464           case '\n':
465             result.append("\\n");
466             break;
467           case '\r':
468             result.append("\\r");
469             break;
470           case '\t':
471             result.append("\\t");
472             break;
473           case '\\':
474             result.append("\\\\");
475             break;
476           case '!':
477             result.append("\\!");
478             break;
479           case '#':
480             result.append("\\#");
481             break;
482           case ' ':
483             result.append(head ? "\\ " : " ");
484             break;
485           default:
486             if (c < 32 || c > '~')
487               {
488                 String hex = Integer.toHexString(c);
489                 result.append("\\u0000".substring(0, 6 - hex.length()));
490                 result.append(hex);
491               }
492             else
493               result.append(c);
494           }
495         if (c != 32)
496           head = false;
497       }
498     return result.toString();
499   }
500
501   /**
502    * Writes the key/value pairs to the given print stream.  They are
503    * written in the way, described in the method store.
504    * @param out the stream, where the key/value pairs are written to.
505    * @exception ClassCastException if this property contains any key or
506    * value that isn't a string.
507    * @see #store
508    */
509   public void list(PrintStream out)
510   {
511     Enumeration keys = keys();
512     Enumeration elts = elements();
513     while (keys.hasMoreElements())
514       {
515         String key = (String) keys.nextElement();
516         String elt = (String) elts.nextElement();
517         String output = formatForOutput(key, elt);
518         out.println(output);
519       }
520   }
521
522   /**
523    * Writes the key/value pairs to the given print writer.  They are
524    * written in the way, described in the method store.
525    * @param out the writer, where the key/value pairs are written to.
526    * @exception ClassCastException if this property contains any key or
527    * value that isn't a string.
528    * @see #store
529    * @see #list(java.io.PrintStream)
530    * @since JDK1.1
531    */
532   public void list(PrintWriter out)
533   {
534     Enumeration keys = keys();
535     Enumeration elts = elements();
536     while (keys.hasMoreElements())
537       {
538         String key = (String) keys.nextElement();
539         String elt = (String) elts.nextElement();
540         String output = formatForOutput(key, elt);
541         out.println(output);
542       }
543   }
544 }