Source for java.util.Properties

   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.
 213:                     line = reader.readLine();
 214:                     pos = 0;
 215:                     while (pos < line.length()
 216:                            && Character.isWhitespace(c = line.charAt(pos)))
 217:                       pos++;
 218:                   }
 219:                 else
 220:                   {
 221:                     c = line.charAt(pos++);
 222:                     switch (c)
 223:                       {
 224:                       case 'n':
 225:                         key.append('\n');
 226:                         break;
 227:                       case 't':
 228:                         key.append('\t');
 229:                         break;
 230:                       case 'r':
 231:                         key.append('\r');
 232:                         break;
 233:                       case 'u':
 234:                         if (pos + 4 <= line.length())
 235:                           {
 236:                             char uni = (char) Integer.parseInt
 237:                               (line.substring(pos, pos + 4), 16);
 238:                             key.append(uni);
 239:                             pos += 4;
 240:                           }        // else throw exception?
 241:                         break;
 242:                       default:
 243:                         key.append(c);
 244:                         break;
 245:                       }
 246:                   }
 247:               }
 248:             else
 249:               key.append(c);
 250:           }
 251: 
 252:         boolean isDelim = (c == ':' || c == '=');
 253:         while (pos < line.length()
 254:                && Character.isWhitespace(c = line.charAt(pos)))
 255:           pos++;
 256: 
 257:         if (! isDelim && (c == ':' || c == '='))
 258:           {
 259:             pos++;
 260:             while (pos < line.length()
 261:                    && Character.isWhitespace(c = line.charAt(pos)))
 262:               pos++;
 263:           }
 264: 
 265:         StringBuffer element = new StringBuffer(line.length() - pos);
 266:         while (pos < line.length())
 267:           {
 268:             c = line.charAt(pos++);
 269:             if (c == '\\')
 270:               {
 271:                 if (pos == line.length())
 272:                   {
 273:                     // The line continues on the next line.
 274:                     line = reader.readLine();
 275: 
 276:             // We might have seen a backslash at the end of
 277:             // the file.  The JDK ignores the backslash in
 278:             // this case, so we follow for compatibility.
 279:             if (line == null)
 280:               break;
 281: 
 282:                     pos = 0;
 283:                     while (pos < line.length()
 284:                            && Character.isWhitespace(c = line.charAt(pos)))
 285:                       pos++;
 286:                     element.ensureCapacity(line.length() - pos +
 287:                                            element.length());
 288:                   }
 289:                 else
 290:                   {
 291:                     c = line.charAt(pos++);
 292:                     switch (c)
 293:                       {
 294:                       case 'n':
 295:                         element.append('\n');
 296:                         break;
 297:                       case 't':
 298:                         element.append('\t');
 299:                         break;
 300:                       case 'r':
 301:                         element.append('\r');
 302:                         break;
 303:                       case 'u':
 304:                         if (pos + 4 <= line.length())
 305:                           {
 306:                             char uni = (char) Integer.parseInt
 307:                               (line.substring(pos, pos + 4), 16);
 308:                             element.append(uni);
 309:                             pos += 4;
 310:                           }        // else throw exception?
 311:                         break;
 312:                       default:
 313:                         element.append(c);
 314:                         break;
 315:                       }
 316:                   }
 317:               }
 318:             else
 319:               element.append(c);
 320:           }
 321:         put(key.toString(), element.toString());
 322:       }
 323:   }
 324: 
 325:   /**
 326:    * Calls <code>store(OutputStream out, String header)</code> and
 327:    * ignores the IOException that may be thrown.
 328:    *
 329:    * @param out the stream to write to
 330:    * @param header a description of the property list
 331:    * @throws ClassCastException if this property contains any key or
 332:    *         value that are not strings
 333:    * @deprecated use {@link #store(OutputStream, String)} instead
 334:    */
 335:   public void save(OutputStream out, String header)
 336:   {
 337:     try
 338:       {
 339:         store(out, header);
 340:       }
 341:     catch (IOException ex)
 342:       {
 343:       }
 344:   }
 345: 
 346:   /**
 347:    * Writes the key/value pairs to the given output stream, in a format
 348:    * suitable for <code>load</code>.<br>
 349:    *
 350:    * If header is not null, this method writes a comment containing
 351:    * the header as first line to the stream.  The next line (or first
 352:    * line if header is null) contains a comment with the current date.
 353:    * Afterwards the key/value pairs are written to the stream in the
 354:    * following format.<br>
 355:    *
 356:    * Each line has the form <code>key = value</code>.  Newlines,
 357:    * Returns and tabs are written as <code>\n,\t,\r</code> resp.
 358:    * The characters <code>\, !, #, =</code> and <code>:</code> are
 359:    * preceeded by a backslash.  Spaces are preceded with a backslash,
 360:    * if and only if they are at the beginning of the key.  Characters
 361:    * that are not in the ascii range 33 to 127 are written in the
 362:    * <code>\</code><code>u</code>xxxx Form.<br>
 363:    *
 364:    * Following the listing, the output stream is flushed but left open.
 365:    *
 366:    * @param out the output stream
 367:    * @param header the header written in the first line, may be null
 368:    * @throws ClassCastException if this property contains any key or
 369:    *         value that isn't a string
 370:    * @throws IOException if writing to the stream fails
 371:    * @throws NullPointerException if out is null
 372:    * @since 1.2
 373:    */
 374:   public void store(OutputStream out, String header) throws IOException
 375:   {
 376:     // The spec says that the file must be encoded using ISO-8859-1.
 377:     PrintWriter writer
 378:       = new PrintWriter(new OutputStreamWriter(out, "ISO-8859-1"));
 379:     if (header != null)
 380:       writer.println("#" + header);
 381:     writer.println ("#" + Calendar.getInstance ().getTime ());
 382:     
 383:     Iterator iter = entrySet ().iterator ();
 384:     int i = size ();
 385:     StringBuffer s = new StringBuffer (); // Reuse the same buffer.
 386:     while (--i >= 0)
 387:       {
 388:         Map.Entry entry = (Map.Entry) iter.next ();
 389:         formatForOutput ((String) entry.getKey (), s, true);
 390:         s.append ('=');
 391:         formatForOutput ((String) entry.getValue (), s, false);
 392:         writer.println (s);
 393:       }
 394: 
 395:     writer.flush ();
 396:   }
 397: 
 398:   /**
 399:    * Gets the property with the specified key in this property list.
 400:    * If the key is not found, the default property list is searched.
 401:    * If the property is not found in the default, null is returned.
 402:    *
 403:    * @param key The key for this property
 404:    * @return the value for the given key, or null if not found
 405:    * @throws ClassCastException if this property contains any key or
 406:    *         value that isn't a string
 407:    * @see #defaults
 408:    * @see #setProperty(String, String)
 409:    * @see #getProperty(String, String)
 410:    */
 411:   public String getProperty(String key)
 412:   {
 413:     return getProperty(key, null);
 414:   }
 415: 
 416:   /**
 417:    * Gets the property with the specified key in this property list.  If
 418:    * the key is not found, the default property list is searched.  If the
 419:    * property is not found in the default, the specified defaultValue is
 420:    * returned.
 421:    *
 422:    * @param key The key for this property
 423:    * @param defaultValue A default value
 424:    * @return The value for the given key
 425:    * @throws ClassCastException if this property contains any key or
 426:    *         value that isn't a string
 427:    * @see #defaults
 428:    * @see #setProperty(String, String)
 429:    */
 430:   public String getProperty(String key, String defaultValue)
 431:   {
 432:     Properties prop = this;
 433:     // Eliminate tail recursion.
 434:     do
 435:       {
 436:         String value = (String) prop.get(key);
 437:         if (value != null)
 438:           return value;
 439:         prop = prop.defaults;
 440:       }
 441:     while (prop != null);
 442:     return defaultValue;
 443:   }
 444: 
 445:   /**
 446:    * Returns an enumeration of all keys in this property list, including
 447:    * the keys in the default property list.
 448:    *
 449:    * @return an Enumeration of all defined keys
 450:    */
 451:   public Enumeration propertyNames()
 452:   {
 453:     // We make a new Set that holds all the keys, then return an enumeration
 454:     // for that. This prevents modifications from ruining the enumeration,
 455:     // as well as ignoring duplicates.
 456:     Properties prop = this;
 457:     Set s = new HashSet();
 458:     // Eliminate tail recursion.
 459:     do
 460:       {
 461:         s.addAll(prop.keySet());
 462:         prop = prop.defaults;
 463:       }
 464:     while (prop != null);
 465:     return Collections.enumeration(s);
 466:   }
 467: 
 468:   /**
 469:    * Prints the key/value pairs to the given print stream.  This is 
 470:    * mainly useful for debugging purposes.
 471:    *
 472:    * @param out the print stream, where the key/value pairs are written to
 473:    * @throws ClassCastException if this property contains a key or a
 474:    *         value that isn't a string
 475:    * @see #list(PrintWriter)
 476:    */
 477:   public void list(PrintStream out)
 478:   {
 479:     PrintWriter writer = new PrintWriter (out);
 480:     list (writer);
 481:   }
 482: 
 483:   /**
 484:    * Prints the key/value pairs to the given print writer.  This is
 485:    * mainly useful for debugging purposes.
 486:    *
 487:    * @param out the print writer where the key/value pairs are written to
 488:    * @throws ClassCastException if this property contains a key or a
 489:    *         value that isn't a string
 490:    * @see #list(PrintStream)
 491:    * @since 1.1
 492:    */
 493:   public void list(PrintWriter out)
 494:   {
 495:     out.println ("-- listing properties --");
 496: 
 497:     Iterator iter = entrySet ().iterator ();
 498:     int i = size ();
 499:     while (--i >= 0)
 500:       {
 501:         Map.Entry entry = (Map.Entry) iter.next ();
 502:         out.print ((String) entry.getKey () + "=");
 503: 
 504:         // JDK 1.3/1.4 restrict the printed value, but not the key,
 505:         // to 40 characters, including the truncating ellipsis.
 506:         String s = (String ) entry.getValue ();
 507:         if (s != null && s.length () > 40)
 508:           out.println (s.substring (0, 37) + "...");
 509:         else
 510:           out.println (s);
 511:       }
 512:     out.flush ();
 513:   }
 514: 
 515:   /**
 516:    * Formats a key or value for output in a properties file.
 517:    * See store for a description of the format.
 518:    *
 519:    * @param str the string to format
 520:    * @param buffer the buffer to add it to
 521:    * @param key true if all ' ' must be escaped for the key, false if only
 522:    *        leading spaces must be escaped for the value
 523:    * @see #store(OutputStream, String)
 524:    */
 525:   private void formatForOutput(String str, StringBuffer buffer, boolean key)
 526:   {
 527:     if (key)
 528:       {
 529:         buffer.setLength(0);
 530:         buffer.ensureCapacity(str.length());
 531:       }
 532:     else
 533:       buffer.ensureCapacity(buffer.length() + str.length());
 534:     boolean head = true;
 535:     int size = str.length();
 536:     for (int i = 0; i < size; i++)
 537:       {
 538:         char c = str.charAt(i);
 539:         switch (c)
 540:           {
 541:           case '\n':
 542:             buffer.append("\\n");
 543:             break;
 544:           case '\r':
 545:             buffer.append("\\r");
 546:             break;
 547:           case '\t':
 548:             buffer.append("\\t");
 549:             break;
 550:           case ' ':
 551:             buffer.append(head ? "\\ " : " ");
 552:             break;
 553:           case '\\':
 554:           case '!':
 555:           case '#':
 556:           case '=':
 557:           case ':':
 558:             buffer.append('\\').append(c);
 559:             break;
 560:           default:
 561:             if (c < ' ' || c > '~')
 562:               {
 563:                 String hex = Integer.toHexString(c);
 564:                 buffer.append("\\u0000".substring(0, 6 - hex.length()));
 565:                 buffer.append(hex);
 566:               }
 567:             else
 568:               buffer.append(c);
 569:           }
 570:         if (c != ' ')
 571:           head = key;
 572:       }
 573:   }
 574: } // class Properties