0001 /*
0002 * Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved.
0003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004 *
0005 * This code is free software; you can redistribute it and/or modify it
0006 * under the terms of the GNU General Public License version 2 only, as
0007 * published by the Free Software Foundation. Sun designates this
0008 * particular file as subject to the "Classpath" exception as provided
0009 * by Sun in the LICENSE file that accompanied this code.
0010 *
0011 * This code is distributed in the hope that it will be useful, but WITHOUT
0012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014 * version 2 for more details (a copy is included in the LICENSE file that
0015 * accompanied this code).
0016 *
0017 * You should have received a copy of the GNU General Public License version
0018 * 2 along with this work; if not, write to the Free Software Foundation,
0019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020 *
0021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022 * CA 95054 USA or visit www.sun.com if you need additional information or
0023 * have any questions.
0024 */
0025
0026 package java.util;
0027
0028 import java.io.IOException;
0029 import java.io.PrintStream;
0030 import java.io.PrintWriter;
0031 import java.io.InputStream;
0032 import java.io.OutputStream;
0033 import java.io.Reader;
0034 import java.io.Writer;
0035 import java.io.OutputStreamWriter;
0036 import java.io.BufferedWriter;
0037
0038 /**
0039 * The <code>Properties</code> class represents a persistent set of
0040 * properties. The <code>Properties</code> can be saved to a stream
0041 * or loaded from a stream. Each key and its corresponding value in
0042 * the property list is a string.
0043 * <p>
0044 * A property list can contain another property list as its
0045 * "defaults"; this second property list is searched if
0046 * the property key is not found in the original property list.
0047 * <p>
0048 * Because <code>Properties</code> inherits from <code>Hashtable</code>, the
0049 * <code>put</code> and <code>putAll</code> methods can be applied to a
0050 * <code>Properties</code> object. Their use is strongly discouraged as they
0051 * allow the caller to insert entries whose keys or values are not
0052 * <code>Strings</code>. The <code>setProperty</code> method should be used
0053 * instead. If the <code>store</code> or <code>save</code> method is called
0054 * on a "compromised" <code>Properties</code> object that contains a
0055 * non-<code>String</code> key or value, the call will fail. Similarly,
0056 * the call to the <code>propertyNames</code> or <code>list</code> method
0057 * will fail if it is called on a "compromised" <code>Properties</code>
0058 * object that contains a non-<code>String</code> key.
0059 *
0060 * <p>
0061 * The {@link #load(java.io.Reader) load(Reader)} <tt>/</tt>
0062 * {@link #store(java.io.Writer, java.lang.String) store(Writer, String)}
0063 * methods load and store properties from and to a character based stream
0064 * in a simple line-oriented format specified below.
0065 *
0066 * The {@link #load(java.io.InputStream) load(InputStream)} <tt>/</tt>
0067 * {@link #store(java.io.OutputStream, java.lang.String) store(OutputStream, String)}
0068 * methods work the same way as the load(Reader)/store(Writer, String) pair, except
0069 * the input/output stream is encoded in ISO 8859-1 character encoding.
0070 * Characters that cannot be directly represented in this encoding can be written using
0071 * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3">Unicode escapes</a>
0072 * ; only a single 'u' character is allowed in an escape
0073 * sequence. The native2ascii tool can be used to convert property files to and
0074 * from other character encodings.
0075 *
0076 * <p> The {@link #loadFromXML(InputStream)} and {@link
0077 * #storeToXML(OutputStream, String, String)} methods load and store properties
0078 * in a simple XML format. By default the UTF-8 character encoding is used,
0079 * however a specific encoding may be specified if required. An XML properties
0080 * document has the following DOCTYPE declaration:
0081 *
0082 * <pre>
0083 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
0084 * </pre>
0085 * Note that the system URI (http://java.sun.com/dtd/properties.dtd) is
0086 * <i>not</i> accessed when exporting or importing properties; it merely
0087 * serves as a string to uniquely identify the DTD, which is:
0088 * <pre>
0089 * <?xml version="1.0" encoding="UTF-8"?>
0090 *
0091 * <!-- DTD for properties -->
0092 *
0093 * <!ELEMENT properties ( comment?, entry* ) >
0094 *
0095 * <!ATTLIST properties version CDATA #FIXED "1.0">
0096 *
0097 * <!ELEMENT comment (#PCDATA) >
0098 *
0099 * <!ELEMENT entry (#PCDATA) >
0100 *
0101 * <!ATTLIST entry key CDATA #REQUIRED>
0102 * </pre>
0103 *
0104 * @see <a href="../../../technotes/tools/solaris/native2ascii.html">native2ascii tool for Solaris</a>
0105 * @see <a href="../../../technotes/tools/windows/native2ascii.html">native2ascii tool for Windows</a>
0106 *
0107 * <p>This class is thread-safe: multiple threads can share a single
0108 * <tt>Properties</tt> object without the need for external synchronization.
0109 *
0110 * @author Arthur van Hoff
0111 * @author Michael McCloskey
0112 * @author Xueming Shen
0113 * @version 1.102, 05/05/07
0114 * @since JDK1.0
0115 */
0116 public class Properties extends Hashtable<Object, Object> {
0117 /**
0118 * use serialVersionUID from JDK 1.1.X for interoperability
0119 */
0120 private static final long serialVersionUID = 4112578634029874840L;
0121
0122 /**
0123 * A property list that contains default values for any keys not
0124 * found in this property list.
0125 *
0126 * @serial
0127 */
0128 protected Properties defaults;
0129
0130 /**
0131 * Creates an empty property list with no default values.
0132 */
0133 public Properties() {
0134 this (null);
0135 }
0136
0137 /**
0138 * Creates an empty property list with the specified defaults.
0139 *
0140 * @param defaults the defaults.
0141 */
0142 public Properties(Properties defaults) {
0143 this .defaults = defaults;
0144 }
0145
0146 /**
0147 * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for
0148 * parallelism with the <tt>getProperty</tt> method. Enforces use of
0149 * strings for property keys and values. The value returned is the
0150 * result of the <tt>Hashtable</tt> call to <code>put</code>.
0151 *
0152 * @param key the key to be placed into this property list.
0153 * @param value the value corresponding to <tt>key</tt>.
0154 * @return the previous value of the specified key in this property
0155 * list, or <code>null</code> if it did not have one.
0156 * @see #getProperty
0157 * @since 1.2
0158 */
0159 public synchronized Object setProperty(String key, String value) {
0160 return put(key, value);
0161 }
0162
0163 /**
0164 * Reads a property list (key and element pairs) from the input
0165 * character stream in a simple line-oriented format.
0166 * <p>
0167 * Properties are processed in terms of lines. There are two
0168 * kinds of line, <i>natural lines</i> and <i>logical lines</i>.
0169 * A natural line is defined as a line of
0170 * characters that is terminated either by a set of line terminator
0171 * characters (<code>\n</code> or <code>\r</code> or <code>\r\n</code>)
0172 * or by the end of the stream. A natural line may be either a blank line,
0173 * a comment line, or hold all or some of a key-element pair. A logical
0174 * line holds all the data of a key-element pair, which may be spread
0175 * out across several adjacent natural lines by escaping
0176 * the line terminator sequence with a backslash character
0177 * <code>\</code>. Note that a comment line cannot be extended
0178 * in this manner; every natural line that is a comment must have
0179 * its own comment indicator, as described below. Lines are read from
0180 * input until the end of the stream is reached.
0181 *
0182 * <p>
0183 * A natural line that contains only white space characters is
0184 * considered blank and is ignored. A comment line has an ASCII
0185 * <code>'#'</code> or <code>'!'</code> as its first non-white
0186 * space character; comment lines are also ignored and do not
0187 * encode key-element information. In addition to line
0188 * terminators, this format considers the characters space
0189 * (<code>' '</code>, <code>'\u0020'</code>), tab
0190 * (<code>'\t'</code>, <code>'\u0009'</code>), and form feed
0191 * (<code>'\f'</code>, <code>'\u000C'</code>) to be white
0192 * space.
0193 *
0194 * <p>
0195 * If a logical line is spread across several natural lines, the
0196 * backslash escaping the line terminator sequence, the line
0197 * terminator sequence, and any white space at the start of the
0198 * following line have no affect on the key or element values.
0199 * The remainder of the discussion of key and element parsing
0200 * (when loading) will assume all the characters constituting
0201 * the key and element appear on a single natural line after
0202 * line continuation characters have been removed. Note that
0203 * it is <i>not</i> sufficient to only examine the character
0204 * preceding a line terminator sequence to decide if the line
0205 * terminator is escaped; there must be an odd number of
0206 * contiguous backslashes for the line terminator to be escaped.
0207 * Since the input is processed from left to right, a
0208 * non-zero even number of 2<i>n</i> contiguous backslashes
0209 * before a line terminator (or elsewhere) encodes <i>n</i>
0210 * backslashes after escape processing.
0211 *
0212 * <p>
0213 * The key contains all of the characters in the line starting
0214 * with the first non-white space character and up to, but not
0215 * including, the first unescaped <code>'='</code>,
0216 * <code>':'</code>, or white space character other than a line
0217 * terminator. All of these key termination characters may be
0218 * included in the key by escaping them with a preceding backslash
0219 * character; for example,<p>
0220 *
0221 * <code>\:\=</code><p>
0222 *
0223 * would be the two-character key <code>":="</code>. Line
0224 * terminator characters can be included using <code>\r</code> and
0225 * <code>\n</code> escape sequences. Any white space after the
0226 * key is skipped; if the first non-white space character after
0227 * the key is <code>'='</code> or <code>':'</code>, then it is
0228 * ignored and any white space characters after it are also
0229 * skipped. All remaining characters on the line become part of
0230 * the associated element string; if there are no remaining
0231 * characters, the element is the empty string
0232 * <code>""</code>. Once the raw character sequences
0233 * constituting the key and element are identified, escape
0234 * processing is performed as described above.
0235 *
0236 * <p>
0237 * As an example, each of the following three lines specifies the key
0238 * <code>"Truth"</code> and the associated element value
0239 * <code>"Beauty"</code>:
0240 * <p>
0241 * <pre>
0242 * Truth = Beauty
0243 * Truth:Beauty
0244 * Truth :Beauty
0245 * </pre>
0246 * As another example, the following three lines specify a single
0247 * property:
0248 * <p>
0249 * <pre>
0250 * fruits apple, banana, pear, \
0251 * cantaloupe, watermelon, \
0252 * kiwi, mango
0253 * </pre>
0254 * The key is <code>"fruits"</code> and the associated element is:
0255 * <p>
0256 * <pre>"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"</pre>
0257 * Note that a space appears before each <code>\</code> so that a space
0258 * will appear after each comma in the final result; the <code>\</code>,
0259 * line terminator, and leading white space on the continuation line are
0260 * merely discarded and are <i>not</i> replaced by one or more other
0261 * characters.
0262 * <p>
0263 * As a third example, the line:
0264 * <p>
0265 * <pre>cheeses
0266 * </pre>
0267 * specifies that the key is <code>"cheeses"</code> and the associated
0268 * element is the empty string <code>""</code>.<p>
0269 * <p>
0270 *
0271 * <a name="unicodeescapes"></a>
0272 * Characters in keys and elements can be represented in escape
0273 * sequences similar to those used for character and string literals
0274 * (see <a
0275 * href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3">§3.3</a>
0276 * and <a
0277 * href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.6">§3.10.6</a>
0278 * of the <i>Java Language Specification</i>).
0279 *
0280 * The differences from the character escape sequences and Unicode
0281 * escapes used for characters and strings are:
0282 *
0283 * <ul>
0284 * <li> Octal escapes are not recognized.
0285 *
0286 * <li> The character sequence <code>\b</code> does <i>not</i>
0287 * represent a backspace character.
0288 *
0289 * <li> The method does not treat a backslash character,
0290 * <code>\</code>, before a non-valid escape character as an
0291 * error; the backslash is silently dropped. For example, in a
0292 * Java string the sequence <code>"\z"</code> would cause a
0293 * compile time error. In contrast, this method silently drops
0294 * the backslash. Therefore, this method treats the two character
0295 * sequence <code>"\b"</code> as equivalent to the single
0296 * character <code>'b'</code>.
0297 *
0298 * <li> Escapes are not necessary for single and double quotes;
0299 * however, by the rule above, single and double quote characters
0300 * preceded by a backslash still yield single and double quote
0301 * characters, respectively.
0302 *
0303 * <li> Only a single 'u' character is allowed in a Uniocde escape
0304 * sequence.
0305 *
0306 * </ul>
0307 * <p>
0308 * The specified stream remains open after this method returns.
0309 *
0310 * @param reader the input character stream.
0311 * @throws IOException if an error occurred when reading from the
0312 * input stream.
0313 * @throws IllegalArgumentException if a malformed Unicode escape
0314 * appears in the input.
0315 * @since 1.6
0316 */
0317 public synchronized void load(Reader reader) throws IOException {
0318 load0(new LineReader(reader));
0319 }
0320
0321 /**
0322 * Reads a property list (key and element pairs) from the input
0323 * byte stream. The input stream is in a simple line-oriented
0324 * format as specified in
0325 * {@link #load(java.io.Reader) load(Reader)} and is assumed to use
0326 * the ISO 8859-1 character encoding; that is each byte is one Latin1
0327 * character. Characters not in Latin1, and certain special characters,
0328 * are represented in keys and elements using
0329 * <a href="http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.3">Unicode escapes</a>.
0330 * <p>
0331 * The specified stream remains open after this method returns.
0332 *
0333 * @param inStream the input stream.
0334 * @exception IOException if an error occurred when reading from the
0335 * input stream.
0336 * @throws IllegalArgumentException if the input stream contains a
0337 * malformed Unicode escape sequence.
0338 * @since 1.2
0339 */
0340 public synchronized void load(InputStream inStream)
0341 throws IOException {
0342 load0(new LineReader(inStream));
0343 }
0344
0345 private void load0(LineReader lr) throws IOException {
0346 char[] convtBuf = new char[1024];
0347 int limit;
0348 int keyLen;
0349 int valueStart;
0350 char c;
0351 boolean hasSep;
0352 boolean precedingBackslash;
0353
0354 while ((limit = lr.readLine()) >= 0) {
0355 c = 0;
0356 keyLen = 0;
0357 valueStart = limit;
0358 hasSep = false;
0359
0360 //System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
0361 precedingBackslash = false;
0362 while (keyLen < limit) {
0363 c = lr.lineBuf[keyLen];
0364 //need check if escaped.
0365 if ((c == '=' || c == ':') && !precedingBackslash) {
0366 valueStart = keyLen + 1;
0367 hasSep = true;
0368 break;
0369 } else if ((c == ' ' || c == '\t' || c == '\f')
0370 && !precedingBackslash) {
0371 valueStart = keyLen + 1;
0372 break;
0373 }
0374 if (c == '\\') {
0375 precedingBackslash = !precedingBackslash;
0376 } else {
0377 precedingBackslash = false;
0378 }
0379 keyLen++;
0380 }
0381 while (valueStart < limit) {
0382 c = lr.lineBuf[valueStart];
0383 if (c != ' ' && c != '\t' && c != '\f') {
0384 if (!hasSep && (c == '=' || c == ':')) {
0385 hasSep = true;
0386 } else {
0387 break;
0388 }
0389 }
0390 valueStart++;
0391 }
0392 String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
0393 String value = loadConvert(lr.lineBuf, valueStart, limit
0394 - valueStart, convtBuf);
0395 put(key, value);
0396 }
0397 }
0398
0399 /* Read in a "logical line" from an InputStream/Reader, skip all comment
0400 * and blank lines and filter out those leading whitespace characters
0401 * (\u0020, \u0009 and \u000c) from the beginning of a "natural line".
0402 * Method returns the char length of the "logical line" and stores
0403 * the line in "lineBuf".
0404 */
0405 class LineReader {
0406 public LineReader(InputStream inStream) {
0407 this .inStream = inStream;
0408 inByteBuf = new byte[8192];
0409 }
0410
0411 public LineReader(Reader reader) {
0412 this .reader = reader;
0413 inCharBuf = new char[8192];
0414 }
0415
0416 byte[] inByteBuf;
0417 char[] inCharBuf;
0418 char[] lineBuf = new char[1024];
0419 int inLimit = 0;
0420 int inOff = 0;
0421 InputStream inStream;
0422 Reader reader;
0423
0424 int readLine() throws IOException {
0425 int len = 0;
0426 char c = 0;
0427
0428 boolean skipWhiteSpace = true;
0429 boolean isCommentLine = false;
0430 boolean isNewLine = true;
0431 boolean appendedLineBegin = false;
0432 boolean precedingBackslash = false;
0433 boolean skipLF = false;
0434
0435 while (true) {
0436 if (inOff >= inLimit) {
0437 inLimit = (inStream == null) ? reader
0438 .read(inCharBuf) : inStream.read(inByteBuf);
0439 inOff = 0;
0440 if (inLimit <= 0) {
0441 if (len == 0 || isCommentLine) {
0442 return -1;
0443 }
0444 return len;
0445 }
0446 }
0447 if (inStream != null) {
0448 //The line below is equivalent to calling a
0449 //ISO8859-1 decoder.
0450 c = (char) (0xff & inByteBuf[inOff++]);
0451 } else {
0452 c = inCharBuf[inOff++];
0453 }
0454 if (skipLF) {
0455 skipLF = false;
0456 if (c == '\n') {
0457 continue;
0458 }
0459 }
0460 if (skipWhiteSpace) {
0461 if (c == ' ' || c == '\t' || c == '\f') {
0462 continue;
0463 }
0464 if (!appendedLineBegin && (c == '\r' || c == '\n')) {
0465 continue;
0466 }
0467 skipWhiteSpace = false;
0468 appendedLineBegin = false;
0469 }
0470 if (isNewLine) {
0471 isNewLine = false;
0472 if (c == '#' || c == '!') {
0473 isCommentLine = true;
0474 continue;
0475 }
0476 }
0477
0478 if (c != '\n' && c != '\r') {
0479 lineBuf[len++] = c;
0480 if (len == lineBuf.length) {
0481 int newLength = lineBuf.length * 2;
0482 if (newLength < 0) {
0483 newLength = Integer.MAX_VALUE;
0484 }
0485 char[] buf = new char[newLength];
0486 System.arraycopy(lineBuf, 0, buf, 0,
0487 lineBuf.length);
0488 lineBuf = buf;
0489 }
0490 //flip the preceding backslash flag
0491 if (c == '\\') {
0492 precedingBackslash = !precedingBackslash;
0493 } else {
0494 precedingBackslash = false;
0495 }
0496 } else {
0497 // reached EOL
0498 if (isCommentLine || len == 0) {
0499 isCommentLine = false;
0500 isNewLine = true;
0501 skipWhiteSpace = true;
0502 len = 0;
0503 continue;
0504 }
0505 if (inOff >= inLimit) {
0506 inLimit = (inStream == null) ? reader
0507 .read(inCharBuf) : inStream
0508 .read(inByteBuf);
0509 inOff = 0;
0510 if (inLimit <= 0) {
0511 return len;
0512 }
0513 }
0514 if (precedingBackslash) {
0515 len -= 1;
0516 //skip the leading whitespace characters in following line
0517 skipWhiteSpace = true;
0518 appendedLineBegin = true;
0519 precedingBackslash = false;
0520 if (c == '\r') {
0521 skipLF = true;
0522 }
0523 } else {
0524 return len;
0525 }
0526 }
0527 }
0528 }
0529 }
0530
0531 /*
0532 * Converts encoded \uxxxx to unicode chars
0533 * and changes special saved chars to their original forms
0534 */
0535 private String loadConvert(char[] in, int off, int len,
0536 char[] convtBuf) {
0537 if (convtBuf.length < len) {
0538 int newLen = len * 2;
0539 if (newLen < 0) {
0540 newLen = Integer.MAX_VALUE;
0541 }
0542 convtBuf = new char[newLen];
0543 }
0544 char aChar;
0545 char[] out = convtBuf;
0546 int outLen = 0;
0547 int end = off + len;
0548
0549 while (off < end) {
0550 aChar = in[off++];
0551 if (aChar == '\\') {
0552 aChar = in[off++];
0553 if (aChar == 'u') {
0554 // Read the xxxx
0555 int value = 0;
0556 for (int i = 0; i < 4; i++) {
0557 aChar = in[off++];
0558 switch (aChar) {
0559 case '0':
0560 case '1':
0561 case '2':
0562 case '3':
0563 case '4':
0564 case '5':
0565 case '6':
0566 case '7':
0567 case '8':
0568 case '9':
0569 value = (value << 4) + aChar - '0';
0570 break;
0571 case 'a':
0572 case 'b':
0573 case 'c':
0574 case 'd':
0575 case 'e':
0576 case 'f':
0577 value = (value << 4) + 10 + aChar - 'a';
0578 break;
0579 case 'A':
0580 case 'B':
0581 case 'C':
0582 case 'D':
0583 case 'E':
0584 case 'F':
0585 value = (value << 4) + 10 + aChar - 'A';
0586 break;
0587 default:
0588 throw new IllegalArgumentException(
0589 "Malformed \\uxxxx encoding.");
0590 }
0591 }
0592 out[outLen++] = (char) value;
0593 } else {
0594 if (aChar == 't')
0595 aChar = '\t';
0596 else if (aChar == 'r')
0597 aChar = '\r';
0598 else if (aChar == 'n')
0599 aChar = '\n';
0600 else if (aChar == 'f')
0601 aChar = '\f';
0602 out[outLen++] = aChar;
0603 }
0604 } else {
0605 out[outLen++] = (char) aChar;
0606 }
0607 }
0608 return new String(out, 0, outLen);
0609 }
0610
0611 /*
0612 * Converts unicodes to encoded \uxxxx and escapes
0613 * special characters with a preceding slash
0614 */
0615 private String saveConvert(String theString, boolean escapeSpace,
0616 boolean escapeUnicode) {
0617 int len = theString.length();
0618 int bufLen = len * 2;
0619 if (bufLen < 0) {
0620 bufLen = Integer.MAX_VALUE;
0621 }
0622 StringBuffer outBuffer = new StringBuffer(bufLen);
0623
0624 for (int x = 0; x < len; x++) {
0625 char aChar = theString.charAt(x);
0626 // Handle common case first, selecting largest block that
0627 // avoids the specials below
0628 if ((aChar > 61) && (aChar < 127)) {
0629 if (aChar == '\\') {
0630 outBuffer.append('\\');
0631 outBuffer.append('\\');
0632 continue;
0633 }
0634 outBuffer.append(aChar);
0635 continue;
0636 }
0637 switch (aChar) {
0638 case ' ':
0639 if (x == 0 || escapeSpace)
0640 outBuffer.append('\\');
0641 outBuffer.append(' ');
0642 break;
0643 case '\t':
0644 outBuffer.append('\\');
0645 outBuffer.append('t');
0646 break;
0647 case '\n':
0648 outBuffer.append('\\');
0649 outBuffer.append('n');
0650 break;
0651 case '\r':
0652 outBuffer.append('\\');
0653 outBuffer.append('r');
0654 break;
0655 case '\f':
0656 outBuffer.append('\\');
0657 outBuffer.append('f');
0658 break;
0659 case '=': // Fall through
0660 case ':': // Fall through
0661 case '#': // Fall through
0662 case '!':
0663 outBuffer.append('\\');
0664 outBuffer.append(aChar);
0665 break;
0666 default:
0667 if (((aChar < 0x0020) || (aChar > 0x007e))
0668 & escapeUnicode) {
0669 outBuffer.append('\\');
0670 outBuffer.append('u');
0671 outBuffer.append(toHex((aChar >> 12) & 0xF));
0672 outBuffer.append(toHex((aChar >> 8) & 0xF));
0673 outBuffer.append(toHex((aChar >> 4) & 0xF));
0674 outBuffer.append(toHex(aChar & 0xF));
0675 } else {
0676 outBuffer.append(aChar);
0677 }
0678 }
0679 }
0680 return outBuffer.toString();
0681 }
0682
0683 private static void writeComments(BufferedWriter bw, String comments)
0684 throws IOException {
0685 bw.write("#");
0686 int len = comments.length();
0687 int current = 0;
0688 int last = 0;
0689 char[] uu = new char[6];
0690 uu[0] = '\\';
0691 uu[1] = 'u';
0692 while (current < len) {
0693 char c = comments.charAt(current);
0694 if (c > '\u00ff' || c == '\n' || c == '\r') {
0695 if (last != current)
0696 bw.write(comments.substring(last, current));
0697 if (c > '\u00ff') {
0698 uu[2] = toHex((c >> 12) & 0xf);
0699 uu[3] = toHex((c >> 8) & 0xf);
0700 uu[4] = toHex((c >> 4) & 0xf);
0701 uu[5] = toHex(c & 0xf);
0702 bw.write(new String(uu));
0703 } else {
0704 bw.newLine();
0705 if (c == '\r' && current != len - 1
0706 && comments.charAt(current + 1) == '\n') {
0707 current++;
0708 }
0709 if (current == len - 1
0710 || (comments.charAt(current + 1) != '#' && comments
0711 .charAt(current + 1) != '!'))
0712 bw.write("#");
0713 }
0714 last = current + 1;
0715 }
0716 current++;
0717 }
0718 if (last != current)
0719 bw.write(comments.substring(last, current));
0720 bw.newLine();
0721 }
0722
0723 /**
0724 * Calls the <code>store(OutputStream out, String comments)</code> method
0725 * and suppresses IOExceptions that were thrown.
0726 *
0727 * @deprecated This method does not throw an IOException if an I/O error
0728 * occurs while saving the property list. The preferred way to save a
0729 * properties list is via the <code>store(OutputStream out,
0730 * String comments)</code> method or the
0731 * <code>storeToXML(OutputStream os, String comment)</code> method.
0732 *
0733 * @param out an output stream.
0734 * @param comments a description of the property list.
0735 * @exception ClassCastException if this <code>Properties</code> object
0736 * contains any keys or values that are not
0737 * <code>Strings</code>.
0738 */
0739 @Deprecated
0740 public synchronized void save(OutputStream out, String comments) {
0741 try {
0742 store(out, comments);
0743 } catch (IOException e) {
0744 }
0745 }
0746
0747 /**
0748 * Writes this property list (key and element pairs) in this
0749 * <code>Properties</code> table to the output character stream in a
0750 * format suitable for using the {@link #load(java.io.Reader) load(Reader)}
0751 * method.
0752 * <p>
0753 * Properties from the defaults table of this <code>Properties</code>
0754 * table (if any) are <i>not</i> written out by this method.
0755 * <p>
0756 * If the comments argument is not null, then an ASCII <code>#</code>
0757 * character, the comments string, and a line separator are first written
0758 * to the output stream. Thus, the <code>comments</code> can serve as an
0759 * identifying comment. Any one of a line feed ('\n'), a carriage
0760 * return ('\r'), or a carriage return followed immediately by a line feed
0761 * in comments is replaced by a line separator generated by the <code>Writer</code>
0762 * and if the next character in comments is not character <code>#</code> or
0763 * character <code>!</code> then an ASCII <code>#</code> is written out
0764 * after that line separator.
0765 * <p>
0766 * Next, a comment line is always written, consisting of an ASCII
0767 * <code>#</code> character, the current date and time (as if produced
0768 * by the <code>toString</code> method of <code>Date</code> for the
0769 * current time), and a line separator as generated by the <code>Writer</code>.
0770 * <p>
0771 * Then every entry in this <code>Properties</code> table is
0772 * written out, one per line. For each entry the key string is
0773 * written, then an ASCII <code>=</code>, then the associated
0774 * element string. For the key, all space characters are
0775 * written with a preceding <code>\</code> character. For the
0776 * element, leading space characters, but not embedded or trailing
0777 * space characters, are written with a preceding <code>\</code>
0778 * character. The key and element characters <code>#</code>,
0779 * <code>!</code>, <code>=</code>, and <code>:</code> are written
0780 * with a preceding backslash to ensure that they are properly loaded.
0781 * <p>
0782 * After the entries have been written, the output stream is flushed.
0783 * The output stream remains open after this method returns.
0784 * <p>
0785 *
0786 * @param writer an output character stream writer.
0787 * @param comments a description of the property list.
0788 * @exception IOException if writing this property list to the specified
0789 * output stream throws an <tt>IOException</tt>.
0790 * @exception ClassCastException if this <code>Properties</code> object
0791 * contains any keys or values that are not <code>Strings</code>.
0792 * @exception NullPointerException if <code>writer</code> is null.
0793 * @since 1.6
0794 */
0795 public void store(Writer writer, String comments)
0796 throws IOException {
0797 store0(
0798 (writer instanceof BufferedWriter) ? (BufferedWriter) writer
0799 : new BufferedWriter(writer), comments, false);
0800 }
0801
0802 /**
0803 * Writes this property list (key and element pairs) in this
0804 * <code>Properties</code> table to the output stream in a format suitable
0805 * for loading into a <code>Properties</code> table using the
0806 * {@link #load(InputStream) load(InputStream)} method.
0807 * <p>
0808 * Properties from the defaults table of this <code>Properties</code>
0809 * table (if any) are <i>not</i> written out by this method.
0810 * <p>
0811 * This method outputs the comments, properties keys and values in
0812 * the same format as specified in
0813 * {@link #store(java.io.Writer, java.lang.String) store(Writer)},
0814 * with the following differences:
0815 * <ul>
0816 * <li>The stream is written using the ISO 8859-1 character encoding.
0817 *
0818 * <li>Characters not in Latin-1 in the comments are written as
0819 * <code>\u</code><i>xxxx</i> for their appropriate unicode
0820 * hexadecimal value <i>xxxx</i>.
0821 *
0822 * <li>Characters less than <code>\u0020</code> and characters greater
0823 * than <code>\u007E</code> in property keys or values are written
0824 * as <code>\u</code><i>xxxx</i> for the appropriate hexadecimal
0825 * value <i>xxxx</i>.
0826 * </ul>
0827 * <p>
0828 * After the entries have been written, the output stream is flushed.
0829 * The output stream remains open after this method returns.
0830 * <p>
0831 * @param out an output stream.
0832 * @param comments a description of the property list.
0833 * @exception IOException if writing this property list to the specified
0834 * output stream throws an <tt>IOException</tt>.
0835 * @exception ClassCastException if this <code>Properties</code> object
0836 * contains any keys or values that are not <code>Strings</code>.
0837 * @exception NullPointerException if <code>out</code> is null.
0838 * @since 1.2
0839 */
0840 public void store(OutputStream out, String comments)
0841 throws IOException {
0842 store0(
0843 new BufferedWriter(
0844 new OutputStreamWriter(out, "8859_1")),
0845 comments, true);
0846 }
0847
0848 private void store0(BufferedWriter bw, String comments,
0849 boolean escUnicode) throws IOException {
0850 if (comments != null) {
0851 writeComments(bw, comments);
0852 }
0853 bw.write("#" + new Date().toString());
0854 bw.newLine();
0855 synchronized (this ) {
0856 for (Enumeration e = keys(); e.hasMoreElements();) {
0857 String key = (String) e.nextElement();
0858 String val = (String) get(key);
0859 key = saveConvert(key, true, escUnicode);
0860 /* No need to escape embedded and trailing spaces for value, hence
0861 * pass false to flag.
0862 */
0863 val = saveConvert(val, false, escUnicode);
0864 bw.write(key + "=" + val);
0865 bw.newLine();
0866 }
0867 }
0868 bw.flush();
0869 }
0870
0871 /**
0872 * Loads all of the properties represented by the XML document on the
0873 * specified input stream into this properties table.
0874 *
0875 * <p>The XML document must have the following DOCTYPE declaration:
0876 * <pre>
0877 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
0878 * </pre>
0879 * Furthermore, the document must satisfy the properties DTD described
0880 * above.
0881 *
0882 * <p>The specified stream is closed after this method returns.
0883 *
0884 * @param in the input stream from which to read the XML document.
0885 * @throws IOException if reading from the specified input stream
0886 * results in an <tt>IOException</tt>.
0887 * @throws InvalidPropertiesFormatException Data on input stream does not
0888 * constitute a valid XML document with the mandated document type.
0889 * @throws NullPointerException if <code>in</code> is null.
0890 * @see #storeToXML(OutputStream, String, String)
0891 * @since 1.5
0892 */
0893 public synchronized void loadFromXML(InputStream in)
0894 throws IOException, InvalidPropertiesFormatException {
0895 if (in == null)
0896 throw new NullPointerException();
0897 XMLUtils.load(this , in);
0898 in.close();
0899 }
0900
0901 /**
0902 * Emits an XML document representing all of the properties contained
0903 * in this table.
0904 *
0905 * <p> An invocation of this method of the form <tt>props.storeToXML(os,
0906 * comment)</tt> behaves in exactly the same way as the invocation
0907 * <tt>props.storeToXML(os, comment, "UTF-8");</tt>.
0908 *
0909 * @param os the output stream on which to emit the XML document.
0910 * @param comment a description of the property list, or <code>null</code>
0911 * if no comment is desired.
0912 * @throws IOException if writing to the specified output stream
0913 * results in an <tt>IOException</tt>.
0914 * @throws NullPointerException if <code>os</code> is null.
0915 * @throws ClassCastException if this <code>Properties</code> object
0916 * contains any keys or values that are not
0917 * <code>Strings</code>.
0918 * @see #loadFromXML(InputStream)
0919 * @since 1.5
0920 */
0921 public synchronized void storeToXML(OutputStream os, String comment)
0922 throws IOException {
0923 if (os == null)
0924 throw new NullPointerException();
0925 storeToXML(os, comment, "UTF-8");
0926 }
0927
0928 /**
0929 * Emits an XML document representing all of the properties contained
0930 * in this table, using the specified encoding.
0931 *
0932 * <p>The XML document will have the following DOCTYPE declaration:
0933 * <pre>
0934 * <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
0935 * </pre>
0936 *
0937 *<p>If the specified comment is <code>null</code> then no comment
0938 * will be stored in the document.
0939 *
0940 * <p>The specified stream remains open after this method returns.
0941 *
0942 * @param os the output stream on which to emit the XML document.
0943 * @param comment a description of the property list, or <code>null</code>
0944 * if no comment is desired.
0945 * @throws IOException if writing to the specified output stream
0946 * results in an <tt>IOException</tt>.
0947 * @throws NullPointerException if <code>os</code> is <code>null</code>,
0948 * or if <code>encoding</code> is <code>null</code>.
0949 * @throws ClassCastException if this <code>Properties</code> object
0950 * contains any keys or values that are not
0951 * <code>Strings</code>.
0952 * @see #loadFromXML(InputStream)
0953 * @since 1.5
0954 */
0955 public synchronized void storeToXML(OutputStream os,
0956 String comment, String encoding) throws IOException {
0957 if (os == null)
0958 throw new NullPointerException();
0959 XMLUtils.save(this , os, comment, encoding);
0960 }
0961
0962 /**
0963 * Searches for the property with the specified key in this property list.
0964 * If the key is not found in this property list, the default property list,
0965 * and its defaults, recursively, are then checked. The method returns
0966 * <code>null</code> if the property is not found.
0967 *
0968 * @param key the property key.
0969 * @return the value in this property list with the specified key value.
0970 * @see #setProperty
0971 * @see #defaults
0972 */
0973 public String getProperty(String key) {
0974 Object oval = super .get(key);
0975 String sval = (oval instanceof String) ? (String) oval : null;
0976 return ((sval == null) && (defaults != null)) ? defaults
0977 .getProperty(key) : sval;
0978 }
0979
0980 /**
0981 * Searches for the property with the specified key in this property list.
0982 * If the key is not found in this property list, the default property list,
0983 * and its defaults, recursively, are then checked. The method returns the
0984 * default value argument if the property is not found.
0985 *
0986 * @param key the hashtable key.
0987 * @param defaultValue a default value.
0988 *
0989 * @return the value in this property list with the specified key value.
0990 * @see #setProperty
0991 * @see #defaults
0992 */
0993 public String getProperty(String key, String defaultValue) {
0994 String val = getProperty(key);
0995 return (val == null) ? defaultValue : val;
0996 }
0997
0998 /**
0999 * Returns an enumeration of all the keys in this property list,
1000 * including distinct keys in the default property list if a key
1001 * of the same name has not already been found from the main
1002 * properties list.
1003 *
1004 * @return an enumeration of all the keys in this property list, including
1005 * the keys in the default property list.
1006 * @throws ClassCastException if any key in this property list
1007 * is not a string.
1008 * @see java.util.Enumeration
1009 * @see java.util.Properties#defaults
1010 * @see #stringPropertyNames
1011 */
1012 public Enumeration<?> propertyNames() {
1013 Hashtable h = new Hashtable();
1014 enumerate(h);
1015 return h.keys();
1016 }
1017
1018 /**
1019 * Returns a set of keys in this property list where
1020 * the key and its corresponding value are strings,
1021 * including distinct keys in the default property list if a key
1022 * of the same name has not already been found from the main
1023 * properties list. Properties whose key or value is not
1024 * of type <tt>String</tt> are omitted.
1025 * <p>
1026 * The returned set is not backed by the <tt>Properties</tt> object.
1027 * Changes to this <tt>Properties</tt> are not reflected in the set,
1028 * or vice versa.
1029 *
1030 * @return a set of keys in this property list where
1031 * the key and its corresponding value are strings,
1032 * including the keys in the default property list.
1033 * @see java.util.Properties#defaults
1034 * @since 1.6
1035 */
1036 public Set<String> stringPropertyNames() {
1037 Hashtable<String, String> h = new Hashtable<String, String>();
1038 enumerateStringProperties(h);
1039 return h.keySet();
1040 }
1041
1042 /**
1043 * Prints this property list out to the specified output stream.
1044 * This method is useful for debugging.
1045 *
1046 * @param out an output stream.
1047 * @throws ClassCastException if any key in this property list
1048 * is not a string.
1049 */
1050 public void list(PrintStream out) {
1051 out.println("-- listing properties --");
1052 Hashtable h = new Hashtable();
1053 enumerate(h);
1054 for (Enumeration e = h.keys(); e.hasMoreElements();) {
1055 String key = (String) e.nextElement();
1056 String val = (String) h.get(key);
1057 if (val.length() > 40) {
1058 val = val.substring(0, 37) + "...";
1059 }
1060 out.println(key + "=" + val);
1061 }
1062 }
1063
1064 /**
1065 * Prints this property list out to the specified output stream.
1066 * This method is useful for debugging.
1067 *
1068 * @param out an output stream.
1069 * @throws ClassCastException if any key in this property list
1070 * is not a string.
1071 * @since JDK1.1
1072 */
1073 /*
1074 * Rather than use an anonymous inner class to share common code, this
1075 * method is duplicated in order to ensure that a non-1.1 compiler can
1076 * compile this file.
1077 */
1078 public void list(PrintWriter out) {
1079 out.println("-- listing properties --");
1080 Hashtable h = new Hashtable();
1081 enumerate(h);
1082 for (Enumeration e = h.keys(); e.hasMoreElements();) {
1083 String key = (String) e.nextElement();
1084 String val = (String) h.get(key);
1085 if (val.length() > 40) {
1086 val = val.substring(0, 37) + "...";
1087 }
1088 out.println(key + "=" + val);
1089 }
1090 }
1091
1092 /**
1093 * Enumerates all key/value pairs in the specified hashtable.
1094 * @param h the hashtable
1095 * @throws ClassCastException if any of the property keys
1096 * is not of String type.
1097 */
1098 private synchronized void enumerate(Hashtable h) {
1099 if (defaults != null) {
1100 defaults.enumerate(h);
1101 }
1102 for (Enumeration e = keys(); e.hasMoreElements();) {
1103 String key = (String) e.nextElement();
1104 h.put(key, get(key));
1105 }
1106 }
1107
1108 /**
1109 * Enumerates all key/value pairs in the specified hashtable
1110 * and omits the property if the key or value is not a string.
1111 * @param h the hashtable
1112 */
1113 private synchronized void enumerateStringProperties(
1114 Hashtable<String, String> h) {
1115 if (defaults != null) {
1116 defaults.enumerateStringProperties(h);
1117 }
1118 for (Enumeration e = keys(); e.hasMoreElements();) {
1119 Object k = e.nextElement();
1120 Object v = get(k);
1121 if (k instanceof String && v instanceof String) {
1122 h.put((String) k, (String) v);
1123 }
1124 }
1125 }
1126
1127 /**
1128 * Convert a nibble to a hex character
1129 * @param nibble the nibble to convert.
1130 */
1131 private static char toHex(int nibble) {
1132 return hexDigit[(nibble & 0xF)];
1133 }
1134
1135 /** A table of hex digits */
1136 private static final char[] hexDigit = { '0', '1', '2', '3', '4',
1137 '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
1138 }
|