OSDN Git Service

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