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