OSDN Git Service

5812173c91ff4bcb15273854248b573be2a430e0
[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 characaters 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                           }     // else throw exception?
189                         break;
190                       default:
191                         key.append(c);
192                         break;
193                       }
194                   }
195               }
196             else
197               key.append(c);
198           }
199
200         boolean isDelim = (c == ':' || c == '=');
201         while (pos < line.length()
202                && Character.isWhitespace(c = line.charAt(pos)))
203           pos++;
204
205         if (!isDelim && (c == ':' || c == '='))
206           {
207             pos++;
208             while (pos < line.length()
209                    && Character.isWhitespace(c = line.charAt(pos)))
210               pos++;
211           }
212
213         StringBuffer element = new StringBuffer(line.length() - pos);
214         while (pos < line.length())
215           {
216             c = line.charAt(pos++);
217             if (c == '\\')
218               {
219                 if (pos == line.length())
220                   {
221                     // The line continues on the next line.
222                     line = reader.readLine();
223                     pos = 0;
224                     while (pos < line.length()
225                            && Character.isWhitespace(c = line.charAt(pos)))
226                       pos++;
227                     element.ensureCapacity(line.length() - pos +
228                                            element.length());
229                   }
230                 else
231                   {
232                     c = line.charAt(pos++);
233                     switch (c)
234                       {
235                       case 'n':
236                         element.append('\n');
237                         break;
238                       case 't':
239                         element.append('\t');
240                         break;
241                       case 'r':
242                         element.append('\r');
243                         break;
244                       case 'u':
245                         if (pos + 4 <= line.length())
246                           {
247                             char uni = (char) Integer.parseInt
248                               (line.substring(pos, pos + 4), 16);
249                             element.append(uni);
250                           }     // else throw exception?
251                         break;
252                       default:
253                         element.append(c);
254                         break;
255                       }
256                   }
257               }
258             else
259               element.append(c);
260           }
261         put(key.toString(), element.toString());
262       }
263   }
264
265   /**
266    * Calls <code>store(OutputStream out, String header)</code> and
267    * ignores the IOException that may be thrown.
268    * @deprecated use store instead.
269    * @exception ClassCastException if this property contains any key or
270    * value that isn't a string.
271    */
272   public void save(OutputStream out, String header)
273   {
274     try
275       {
276         store(out, header);
277       }
278     catch (IOException ex)
279       {
280       }
281   }
282
283   /**
284    * Writes the key/value pairs to the given output stream. <br>
285    *
286    * If header is not null, this method writes a comment containing
287    * the header as first line to the stream.  The next line (or first
288    * line if header is null) contains a comment with the current date.
289    * Afterwards the key/value pairs are written to the stream in the
290    * following format. <br>
291    *
292    * Each line has the form <code>key = value</code>.  Newlines,
293    * Returns and tabs are written as <code>\n,\t,\r</code> resp.
294    * The characters <code>\, !, #, =</code> and <code>:</code> are
295    * preceeded by a backslash.  Spaces are preceded with a backslash,
296    * if and only if they are at the beginning of the key.  Characters
297    * that are not in the ascii range 33 to 127 are written in the
298    * <code>\</code><code>u</code>xxxx Form.
299    *
300    * @param out the output stream
301    * @param header the header written in the first line, may be null.
302    * @exception ClassCastException if this property contains any key or
303    * value that isn't a string.
304    */
305   public void store(OutputStream out, String header) throws IOException
306   {
307     // The spec says that the file must be encoded using ISO-8859-1.
308     PrintWriter writer
309       = new PrintWriter(new OutputStreamWriter (out, "ISO-8859-1"));
310     if (header != null)
311       writer.println("#" + header);
312     writer.println("#" + new Date().toString());
313     list(writer);
314     writer.flush();
315   }
316
317   /**
318    * Adds the given key/value pair to this properties.  This calls
319    * the hashtable method put.
320    * @param key the key for this property
321    * @param value the value for this property
322    * @return The old value for the given key.
323    * @since JDK1.2 */
324   public Object setProperty(String key, String value)
325   {
326     return put(key, value);
327   }
328
329   /**
330    * Gets the property with the specified key in this property list.
331    * If the key is not found, the default property list is searched.
332    * If the property is not found in default or the default of
333    * default, null is returned.
334    * @param key The key for this property.
335    * @param defaulValue A default value
336    * @return The value for the given key, or null if not found. 
337    * @exception ClassCastException if this property contains any key or
338    * value that isn't a string.
339    */
340   public String getProperty(String key)
341   {
342     return getProperty(key, null);
343   }
344
345   /**
346    * Gets the property with the specified key in this property list.  If
347    * the key is not found, the default property list is searched.  If the
348    * property is not found in default or the default of default, the 
349    * specified defaultValue is returned.
350    * @param key The key for this property.
351    * @param defaulValue A default value
352    * @return The value for the given key.
353    * @exception ClassCastException if this property contains any key or
354    * value that isn't a string.
355    */
356   public String getProperty(String key, String defaultValue)
357   {
358     Properties prop = this;
359     // Eliminate tail recursion.
360     do
361       {
362         String value = (String) prop.get(key);
363         if (value != null)
364           return value;
365         prop = prop.defaults;
366       }
367     while (prop != null);
368     return defaultValue;
369   }
370
371   private final void addHashEntries(Hashtable base)
372   {
373     if (defaults != null)
374       defaults.addHashEntries(base);
375     Enumeration keys = keys();
376     while (keys.hasMoreElements())
377       base.put(keys.nextElement(), base);
378   }
379
380   /**
381    * Returns an enumeration of all keys in this property list, including
382    * the keys in the default property list.
383    */
384   public Enumeration propertyNames()
385   {
386     // We make a new Hashtable that holds all the keys.  Then we
387     // return an enumeration for this hash.  We do this because we
388     // don't want modifications to be reflected in the enumeration
389     // (per JCL), and because there doesn't seem to be a
390     // particularly better way to ensure that duplicates are
391     // ignored.
392     Hashtable t = new Hashtable();
393     addHashEntries(t);
394     return t.keys();
395   }
396
397   /**
398    * Formats a key/value pair for output in a properties file.
399    * See store for a description of the format.
400    * @param key the key.
401    * @param value the value.
402    * @see #store
403    */
404   private String formatForOutput(String key, String value)
405   {
406     // This is a simple approximation of the expected line size.
407     StringBuffer result =
408       new StringBuffer(key.length() + value.length() + 16);
409     boolean head = true;
410     for (int i = 0; i < key.length(); i++)
411       {
412         char c = key.charAt(i);
413         switch (c)
414           {
415           case '\n':
416             result.append("\\n");
417             break;
418           case '\r':
419             result.append("\\r");
420             break;
421           case '\t':
422             result.append("\\t");
423             break;
424           case '\\':
425             result.append("\\\\");
426             break;
427           case '!':
428             result.append("\\!");
429             break;
430           case '#':
431             result.append("\\#");
432             break;
433           case '=':
434             result.append("\\=");
435             break;
436           case ':':
437             result.append("\\:");
438             break;
439           case ' ':
440             result.append("\\ ");
441             break;
442           default:
443             if (c < 32 || c > '~')
444               {
445                 String hex = Integer.toHexString(c);
446                 result.append("\\u0000".substring(0, 6 - hex.length()));
447                 result.append(hex);
448               }
449             else
450                 result.append(c);
451           }
452         if (c != 32)
453           head = false;
454       }
455     result.append('=');
456     head = true;
457     for (int i = 0; i < value.length(); i++)
458       {
459         char c = value.charAt(i);
460         switch (c)
461           {
462           case '\n':
463             result.append("\\n");
464             break;
465           case '\r':
466             result.append("\\r");
467             break;
468           case '\t':
469             result.append("\\t");
470             break;
471           case '\\':
472             result.append("\\\\");
473             break;
474           case '!':
475             result.append("\\!");
476             break;
477           case '#':
478             result.append("\\#");
479             break;
480           case ' ':
481             result.append(head ? "\\ " : " ");
482             break;
483           default:
484             if (c < 32 || c > '~')
485               {
486                 String hex = Integer.toHexString(c);
487                 result.append("\\u0000".substring(0, 6 - hex.length()));
488                 result.append(hex);
489               }
490             else
491               result.append(c);
492           }
493         if (c != 32)
494           head = false;
495       }
496     return result.toString();
497   }
498
499   /**
500    * Writes the key/value pairs to the given print stream.  They are
501    * written in the way, described in the method store.
502    * @param out the stream, where the key/value pairs are written to.
503    * @exception ClassCastException if this property contains any key or
504    * value that isn't a string.
505    * @see #store
506    */
507   public void list(PrintStream out)
508   {
509     Enumeration keys = keys();
510     Enumeration elts = elements();
511     while (keys.hasMoreElements())
512       {
513         String key = (String) keys.nextElement();
514         String elt = (String) elts.nextElement();
515         String output = formatForOutput(key, elt);
516         out.println(output);
517       }
518   }
519
520   /**
521    * Writes the key/value pairs to the given print writer.  They are
522    * written in the way, described in the method store.
523    * @param out the writer, where the key/value pairs are written to.
524    * @exception ClassCastException if this property contains any key or
525    * value that isn't a string.
526    * @see #store
527    * @see #list(java.io.PrintStream)
528    * @since JDK1.1
529    */
530   public void list(PrintWriter out)
531   {
532     Enumeration keys = keys();
533     Enumeration elts = elements();
534     while (keys.hasMoreElements())
535       {
536         String key = (String) keys.nextElement();
537         String elt = (String) elts.nextElement();
538         String output = formatForOutput(key, elt);
539         out.println(output);
540       }
541   }
542 }