View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.helyx.basics4me.util;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.OutputStreamWriter;
24  import java.io.PrintStream;
25  import java.io.Writer;
26  import java.util.Date;
27  import java.util.Enumeration;
28  import java.util.Hashtable;
29  
30  import org.helyx.basics4me.lang.CharUtil;
31  
32  /**
33   * Properties is a Hashtable where the keys and values must be Strings. Each
34   * Properties can have a default Properties which specifies the default values
35   * which are used if the key is not in this Properties.
36   * 
37   * @see Hashtable
38   * @see java.lang.System#getProperties
39   */
40  public class Properties extends Hashtable {
41  
42      private static final long serialVersionUID = 4112578634029874840L;
43  
44      /**
45       * The default values for this Properties.
46       */
47      protected Properties defaults;
48  
49      private static final int NONE = 0, SLASH = 1, UNICODE = 2, CONTINUE = 3,
50              KEY_DONE = 4, IGNORE = 5;
51  
52      /**
53       * Constructs a new Properties object.
54       */
55      public Properties() {
56          super();
57      }
58  
59      /**
60       * Constructs a new Properties object using the specified default
61       * properties.
62       * 
63       * @param properties
64       *            the default properties
65       */
66      public Properties(Properties properties) {
67          defaults = properties;
68      }
69  
70      private void dumpString(StringBuffer buffer, String string, boolean key) {
71          int i = 0;
72          if (!key && i < string.length() && string.charAt(i) == ' ') {
73              buffer.append("\\ "); //$NON-NLS-1$
74              i++;
75          }
76  
77          for (; i < string.length(); i++) {
78              char ch = string.charAt(i);
79              switch (ch) {
80              case '\t':
81                  buffer.append("\\t"); //$NON-NLS-1$
82                  break;
83              case '\n':
84                  buffer.append("\\n"); //$NON-NLS-1$
85                  break;
86              case '\f':
87                  buffer.append("\\f"); //$NON-NLS-1$
88                  break;
89              case '\r':
90                  buffer.append("\\r"); //$NON-NLS-1$
91                  break;
92              default:
93                  if ("\\#!=:".indexOf(ch) >= 0 || (key && ch == ' ')) {
94                      buffer.append('\\');
95                  }
96                  if (ch >= ' ' && ch <= '~') {
97                      buffer.append(ch);
98                  } else {
99                      String hex = Integer.toHexString(ch);
100                     buffer.append("\\u"); //$NON-NLS-1$
101                     for (int j = 0; j < 4 - hex.length(); j++) {
102                         buffer.append("0"); //$NON-NLS-1$
103                     }
104                     buffer.append(hex);
105                 }
106             }
107         }
108     }
109 
110     /**
111      * Searches for the property with the specified name. If the property is not
112      * found, look in the default properties. If the property is not found in
113      * the default properties, answer null.
114      * 
115      * @param name
116      *            the name of the property to find
117      * @return the named property value
118      */
119     public String getProperty(String name) {
120         Object result = super.get(name);
121         String property = result instanceof String ? (String) result : null;
122         if (property == null && defaults != null) {
123             property = defaults.getProperty(name);
124         }
125         return property;
126     }
127 
128     /**
129      * Searches for the property with the specified name. If the property is not
130      * found, look in the default properties. If the property is not found in
131      * the default properties, answer the specified default.
132      * 
133      * @param name
134      *            the name of the property to find
135      * @param defaultValue
136      *            the default value
137      * @return the named property value
138      */
139     public String getProperty(String name, String defaultValue) {
140         Object result = super.get(name);
141         String property = result instanceof String ? (String) result : null;
142         if (property == null && defaults != null) {
143             property = defaults.getProperty(name);
144         }
145         if (property == null) {
146             return defaultValue;
147         }
148         return property;
149     }
150 
151     /**
152      * Lists the mappings in this Properties to the specified PrintStream in a
153      * human readable form.
154      * 
155      * @param out
156      *            the PrintStream
157      */
158     public void list(PrintStream out) {
159         if (out == null) {
160             throw new NullPointerException();
161         }
162         StringBuffer buffer = new StringBuffer(80);
163         Enumeration keys = propertyNames();
164         while (keys.hasMoreElements()) {
165             String key = (String) keys.nextElement();
166             buffer.append(key);
167             buffer.append('=');
168             String property = (String) super.get(key);
169             Properties def = defaults;
170             while (property == null) {
171                 property = (String) def.get(key);
172                 def = def.defaults;
173             }
174             if (property.length() > 40) {
175                 buffer.append(property.substring(0, 37));
176                 buffer.append("..."); //$NON-NLS-1$
177             } else {
178                 buffer.append(property);
179             }
180             out.println(buffer.toString());
181             buffer.setLength(0);
182         }
183     }
184 
185     /**
186      * Lists the mappings in this Properties to the specified PrintWriter in a
187      * human readable form.
188      * 
189      * @param writer
190      *            the PrintWriter
191      * @throws IOException 
192      */
193     public void list(Writer writer) throws IOException {
194         if (writer == null) {
195             throw new NullPointerException();
196         }
197         StringBuffer buffer = new StringBuffer(80);
198         Enumeration keys = propertyNames();
199         while (keys.hasMoreElements()) {
200             String key = (String) keys.nextElement();
201             buffer.append(key);
202             buffer.append('=');
203             String property = (String) super.get(key);
204             Properties def = defaults;
205             while (property == null) {
206                 property = (String) def.get(key);
207                 def = def.defaults;
208             }
209             if (property.length() > 40) {
210                 buffer.append(property.substring(0, 37));
211                 buffer.append("..."); //$NON-NLS-1$
212             } else {
213                 buffer.append(property);
214             }
215             writer.write(buffer.toString() + "\n");
216             buffer.setLength(0);
217         }
218     }
219 
220     /**
221      * Loads properties from the specified InputStream. The properties are of
222      * the form <code>key=value</code>, one property per line.
223      * 
224      * @param in
225      *            the input stream
226      * @throws IOException
227      */
228     public synchronized void load(InputStream in) throws IOException {
229         int mode = NONE, unicode = 0, count = 0;
230         char nextChar, buf[] = new char[40];
231         int offset = 0, keyLength = -1;
232         boolean firstChar = true;
233         byte[] inbuf = new byte[256];
234         int inbufCount = 0, inbufPos = 0;
235 
236         while (true) {
237             if (inbufPos == inbufCount) {
238                 if ((inbufCount = in.read(inbuf)) == -1) {
239                     break;
240                 }
241                 inbufPos = 0;
242             }
243             nextChar = (char) (inbuf[inbufPos++] & 0xff);
244 
245             if (offset == buf.length) {
246                 char[] newBuf = new char[buf.length * 2];
247                 System.arraycopy(buf, 0, newBuf, 0, offset);
248                 buf = newBuf;
249             }
250             if (mode == UNICODE) {
251                 int digit = Character.digit(nextChar, 16);
252                 if (digit >= 0) {
253                     unicode = (unicode << 4) + digit;
254                     if (++count < 4) {
255                         continue;
256                     }
257                 } else if (count <= 4) {
258                     // luni.09=Invalid Unicode sequence: illegal character
259                     throw new IllegalArgumentException("Invalid Unicode sequence: illegal character");
260                 }
261                 mode = NONE;
262                 buf[offset++] = (char) unicode;
263                 if (nextChar != '\n') {
264                     continue;
265                 }
266             }
267             if (mode == SLASH) {
268                 mode = NONE;
269                 switch (nextChar) {
270                 case '\r':
271                     mode = CONTINUE; // Look for a following \n
272                     continue;
273                 case '\n':
274                     mode = IGNORE; // Ignore whitespace on the next line
275                     continue;
276                 case 'b':
277                     nextChar = '\b';
278                     break;
279                 case 'f':
280                     nextChar = '\f';
281                     break;
282                 case 'n':
283                     nextChar = '\n';
284                     break;
285                 case 'r':
286                     nextChar = '\r';
287                     break;
288                 case 't':
289                     nextChar = '\t';
290                     break;
291                 case 'u':
292                     mode = UNICODE;
293                     unicode = count = 0;
294                     continue;
295                 }
296             } else {
297                 switch (nextChar) {
298                 case '#':
299                 case '!':
300                     if (firstChar) {
301                         while (true) {
302                             if (inbufPos == inbufCount) {
303                                 if ((inbufCount = in.read(inbuf)) == -1) {
304                                     inbufPos = -1;
305                                     break;
306                                 }
307                                 inbufPos = 0;
308                             }
309                             nextChar = (char) inbuf[inbufPos++]; // & 0xff
310                             // not
311                             // required
312                             if (nextChar == '\r' || nextChar == '\n') {
313                                 break;
314                             }
315                         }
316                         continue;
317                     }
318                     break;
319                 case '\n':
320                     if (mode == CONTINUE) { // Part of a \r\n sequence
321                         mode = IGNORE; // Ignore whitespace on the next line
322                         continue;
323                     }
324                     // fall into the next case
325                 case '\r':
326                     mode = NONE;
327                     firstChar = true;
328                     if (offset > 0) {
329                         if (keyLength == -1) {
330                             keyLength = offset;
331                         }
332                         String temp = new String(buf, 0, offset);
333                         put(temp.substring(0, keyLength), temp
334                                 .substring(keyLength));
335                     }
336                     keyLength = -1;
337                     offset = 0;
338                     continue;
339                 case '\\':
340                     if (mode == KEY_DONE) {
341                         keyLength = offset;
342                     }
343                     mode = SLASH;
344                     continue;
345                 case ':':
346                 case '=':
347                     if (keyLength == -1) { // if parsing the key
348                         mode = NONE;
349                         keyLength = offset;
350                         continue;
351                     }
352                     break;
353                 }
354                 if (CharUtil.isWhitespace(nextChar)) {
355                     if (mode == CONTINUE) {
356                         mode = IGNORE;
357                     }
358                     // if key length == 0 or value length == 0
359                     if (offset == 0 || offset == keyLength || mode == IGNORE) {
360                         continue;
361                     }
362                     if (keyLength == -1) { // if parsing the key
363                         mode = KEY_DONE;
364                         continue;
365                     }
366                 }
367                 if (mode == IGNORE || mode == CONTINUE) {
368                     mode = NONE;
369                 }
370             }
371             firstChar = false;
372             if (mode == KEY_DONE) {
373                 keyLength = offset;
374                 mode = NONE;
375             }
376             buf[offset++] = nextChar;
377         }
378         if (mode == UNICODE && count <= 4) {
379             // luni.08=Invalid Unicode sequence: expected format \\uxxxx
380             throw new IllegalArgumentException("Invalid unicode character");
381         }
382         if (keyLength == -1 && offset > 0) {
383             keyLength = offset;
384         }
385         if (keyLength >= 0) {
386             String temp = new String(buf, 0, offset);
387             String key = temp.substring(0, keyLength);
388             String value = temp.substring(keyLength);
389             if (mode == SLASH) {
390                 value += "\u0000";
391             }
392             put(key, value);
393         }
394     }
395 
396     /**
397      * Answers all of the property names that this Properties contains.
398      * 
399      * @return an Enumeration containing the names of all properties
400      */
401     public Enumeration propertyNames() {
402         if (defaults == null) {
403             return keys();
404         }
405 
406         Hashtable set = new Hashtable(defaults.size() + size());
407         Enumeration keys = defaults.propertyNames();
408         while (keys.hasMoreElements()) {
409             set.put(keys.nextElement(), set);
410         }
411         keys = keys();
412         while (keys.hasMoreElements()) {
413             set.put(keys.nextElement(), set);
414         }
415         return set.keys();
416     }
417 
418     /**
419      * Saves the mappings in this Properties to the specified OutputStream,
420      * putting the specified comment at the beginning. The output from this
421      * method is suitable for being read by the load() method.
422      * 
423      * @param out
424      *            the OutputStream
425      * @param comment
426      *            the comment
427      * 
428      * @exception ClassCastException
429      *                when the key or value of a mapping is not a String
430      * 
431      * @deprecated Does not throw an IOException, use store()
432      */
433     public void save(OutputStream out, String comment) {
434         try {
435             store(out, comment);
436         } catch (IOException e) {
437         }
438     }
439 
440     /**
441      * Maps the specified key to the specified value. If the key already exists,
442      * the old value is replaced. The key and value cannot be null.
443      * 
444      * @param name
445      *            the key
446      * @param value
447      *            the value
448      * @return the old value mapped to the key, or null
449      */
450     public Object setProperty(String name, String value) {
451         return put(name, value);
452     }
453 
454     private static String lineSeparator;
455 
456     /**
457      * Stores the mappings in this Properties to the specified OutputStream,
458      * putting the specified comment at the beginning. The output from this
459      * method is suitable for being read by the load() method.
460      * 
461      * @param out
462      *            the OutputStream
463      * @param comment
464      *            the comment
465      * @throws IOException
466      * 
467      * @exception ClassCastException
468      *                when the key or value of a mapping is not a String
469      */
470     public synchronized void store(OutputStream out, String comment)
471             throws IOException {
472         if (lineSeparator == null) {
473         	lineSeparator = "\n";
474 //            lineSeparator = AccessController
475 //                    .doPrivileged(new PriviAction<String>("line.separator")); //$NON-NLS-1$
476         }
477 
478         StringBuffer buffer = new StringBuffer(200);
479         OutputStreamWriter writer = new OutputStreamWriter(out, "ISO8859_1"); //$NON-NLS-1$
480         if (comment != null) {
481             writer.write("#"); //$NON-NLS-1$
482             writer.write(comment);
483             writer.write(lineSeparator);
484         }
485         writer.write("#"); //$NON-NLS-1$
486         writer.write(new Date().toString());
487         writer.write(lineSeparator);
488 
489         
490         Enumeration _enum = keys();
491        while (_enum.hasMoreElements()) {
492             String key = (String)_enum.nextElement();
493             
494             dumpString(buffer, key, true);
495             buffer.append('=');
496             dumpString(buffer, (String)get(key), false);
497             buffer.append(lineSeparator);
498             writer.write(buffer.toString());
499             buffer.setLength(0);
500         }
501         writer.flush();
502     }
503     
504 }