1. /*
  2. * @(#)Properties.java 1.73 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.util;
  8. import java.io.IOException;
  9. import java.io.PrintStream;
  10. import java.io.PrintWriter;
  11. import java.io.InputStream;
  12. import java.io.InputStreamReader;
  13. import java.io.BufferedReader;
  14. import java.io.OutputStream;
  15. import java.io.OutputStreamWriter;
  16. import java.io.BufferedWriter;
  17. import java.util.Hashtable;
  18. /**
  19. * The <code>Properties</code> class represents a persistent set of
  20. * properties. The <code>Properties</code> can be saved to a stream
  21. * or loaded from a stream. Each key and its corresponding value in
  22. * the property list is a string.
  23. * <p>
  24. * A property list can contain another property list as its
  25. * "defaults"; this second property list is searched if
  26. * the property key is not found in the original property list.
  27. * <p>
  28. * Because <code>Properties</code> inherits from <code>Hashtable</code>, the
  29. * <code>put</code> and <code>putAll</code> methods can be applied to a
  30. * <code>Properties</code> object. Their use is strongly discouraged as they
  31. * allow the caller to insert entries whose keys or values are not
  32. * <code>Strings</code>. The <code>setProperty</code> method should be used
  33. * instead. If the <code>store</code> or <code>save</code> method is called
  34. * on a "compromised" <code>Properties</code> object that contains a
  35. * non-<code>String</code> key or value, the call will fail.
  36. * <p>
  37. * <a name="encoding"></a>
  38. * When saving properties to a stream or loading them from a stream, the
  39. * ISO 8859-1 character encoding is used. For characters that cannot be directly
  40. * represented in this encoding,
  41. * <a href="http://java.sun.com/docs/books/jls/html/3.doc.html#100850">Unicode escapes</a>
  42. * are used; however, only a single 'u' character is allowed in an escape sequence.
  43. * The native2ascii tool can be used to convert property files to and from
  44. * other character encodings.
  45. *
  46. * @see <a href="../../../tooldocs/solaris/native2ascii.html">native2ascii tool for Solaris</a>
  47. * @see <a href="../../../tooldocs/windows/native2ascii.html">native2ascii tool for Windows</a>
  48. *
  49. * @author Arthur van Hoff
  50. * @author Michael McCloskey
  51. * @version 1.64, 06/26/00
  52. * @since JDK1.0
  53. */
  54. public
  55. class Properties extends Hashtable {
  56. /**
  57. * use serialVersionUID from JDK 1.1.X for interoperability
  58. */
  59. private static final long serialVersionUID = 4112578634029874840L;
  60. /**
  61. * A property list that contains default values for any keys not
  62. * found in this property list.
  63. *
  64. * @serial
  65. */
  66. protected Properties defaults;
  67. /**
  68. * Creates an empty property list with no default values.
  69. */
  70. public Properties() {
  71. this(null);
  72. }
  73. /**
  74. * Creates an empty property list with the specified defaults.
  75. *
  76. * @param defaults the defaults.
  77. */
  78. public Properties(Properties defaults) {
  79. this.defaults = defaults;
  80. }
  81. /**
  82. * Calls the <tt>Hashtable</tt> method <code>put</code>. Provided for
  83. * parallelism with the <tt>getProperty</tt> method. Enforces use of
  84. * strings for property keys and values. The value returned is the
  85. * result of the <tt>Hashtable</tt> call to <code>put</code>.
  86. *
  87. * @param key the key to be placed into this property list.
  88. * @param value the value corresponding to <tt>key</tt>.
  89. * @return the previous value of the specified key in this property
  90. * list, or <code>null</code> if it did not have one.
  91. * @see #getProperty
  92. * @since 1.2
  93. */
  94. public synchronized Object setProperty(String key, String value) {
  95. return put(key, value);
  96. }
  97. private static final String keyValueSeparators = "=: \t\r\n\f";
  98. private static final String strictKeyValueSeparators = "=:";
  99. private static final String specialSaveChars = "=: \t\r\n\f#!";
  100. private static final String whiteSpaceChars = " \t\r\n\f";
  101. /**
  102. * Reads a property list (key and element pairs) from the input
  103. * stream. The stream is assumed to be using the ISO 8859-1
  104. * character encoding; that is each byte is one Latin1 character.
  105. * Characters not in Latin1, and certain special characters, can
  106. * be represented in keys and elements using escape sequences
  107. * similar to those used for character and string literals (see <a
  108. * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#100850">§3.3</a>
  109. * and <a
  110. * href="http://java.sun.com/docs/books/jls/second_edition/html/lexical.doc.html#101089">§3.10.6</a>
  111. * of the <i>Java Language Specification</i>).
  112. *
  113. * The differences from the character escape sequences used for
  114. * characters and strings are:
  115. *
  116. * <ul>
  117. * <li> Octal escapes are not recognized.
  118. *
  119. * <li> The character sequence <code>\b</code> does <i>not</i>
  120. * represent a backspace character.
  121. *
  122. * <li> The method does not treat a backslash character,
  123. * <code>\</code>, before a non-valid escape character as an
  124. * error; the backslash is silently dropped. For example, in a
  125. * Java string the sequence <code>"\z"</code> would cause a
  126. * compile time error. In contrast, this method silently drops
  127. * the backslash. Therefore, this method treats the two character
  128. * sequence <code>"\b"</code> as equivalent to the single
  129. * character <code>'b'</code>.
  130. *
  131. * <li> Escapes are not necessary for single and double quotes;
  132. * however, by the rule above, single and double quote characters
  133. * preceded by a backslash still yield single and double quote
  134. * characters, respectively.
  135. *
  136. * </ul>
  137. *
  138. * An <code>IllegalArgumentException</code> is thrown if a
  139. * malformed Unicode escape appears in the input.
  140. *
  141. * <p>
  142. * This method processes input in terms of lines. A natural line
  143. * of input is terminated either by a set of line terminator
  144. * characters (<code>\n</code> or <code>\r</code> or
  145. * <code>\r\n</code>) or by the end of the file. A natural line
  146. * may be either a blank line, a comment line, or hold some part
  147. * of a key-element pair. The logical line holding all the data
  148. * for a key-element pair may be spread out across several adjacent
  149. * natural lines by escaping the line terminator sequence with a
  150. * backslash character, <code>\</code>. Note that a comment line
  151. * cannot be extended in this manner; every natural line that is a
  152. * comment must have its own comment indicator, as described
  153. * below. If a logical line is continued over several natural
  154. * lines, the continuation lines receive further processing, also
  155. * described below. Lines are read from the input stream until
  156. * end of file is reached.
  157. *
  158. * <p>
  159. * A natural line that contains only white space characters is
  160. * considered blank and is ignored. A comment line has an ASCII
  161. * <code>'#'</code> or <code>'!'</code> as its first non-white
  162. * space character; comment lines are also ignored and do not
  163. * encode key-element information. In addition to line
  164. * terminators, this method considers the characters space
  165. * (<code>' '</code>, <code>'\u0020'</code>), tab
  166. * (<code>'\t'</code>, <code>'\u0009'</code>), and form feed
  167. * (<code>'\f'</code>, <code>'\u000C'</code>) to be white
  168. * space.
  169. *
  170. * <p>
  171. * If a logical line is spread across several natural lines, the
  172. * backslash escaping the line terminator sequence, the line
  173. * terminator sequence, and any white space at the start the
  174. * following line have no affect on the key or element values.
  175. * The remainder of the discussion of key and element parsing will
  176. * assume all the characters constituting the key and element
  177. * appear on a single natural line after line continuation
  178. * characters have been removed. Note that it is <i>not</i>
  179. * sufficient to only examine the character preceding a line
  180. * terminator sequence to to see if the line terminator is
  181. * escaped; there must be an odd number of contiguous backslashes
  182. * for the line terminator to be escaped. Since the input is
  183. * processed from left to right, a non-zero even number of
  184. * 2<i>n</i> contiguous backslashes before a line terminator (or
  185. * elsewhere) encodes <i>n</i> backslashes after escape
  186. * processing.
  187. *
  188. * <p>
  189. * The key contains all of the characters in the line starting
  190. * with the first non-white space character and up to, but not
  191. * including, the first unescaped <code>'='</code>,
  192. * <code>':'</code>, or white space character other than a line
  193. * terminator. All of these key termination characters may be
  194. * included in the key by escaping them with a preceding backslash
  195. * character; for example,<p>
  196. *
  197. * <code>\:\=</code><p>
  198. *
  199. * would be the two-character key <code>":="</code>. Line
  200. * terminator characters can be included using <code>\r</code> and
  201. * <code>\n</code> escape sequences. Any white space after the
  202. * key is skipped; if the first non-white space character after
  203. * the key is <code>'='</code> or <code>':'</code>, then it is
  204. * ignored and any white space characters after it are also
  205. * skipped. All remaining characters on the line become part of
  206. * the associated element string; if there are no remaining
  207. * characters, the element is the empty string
  208. * <code>""</code>. Once the raw character sequences
  209. * constituting the key and element are identified, escape
  210. * processing is performed as described above.
  211. *
  212. * <p>
  213. * As an example, each of the following three lines specifies the key
  214. * <code>"Truth"</code> and the associated element value
  215. * <code>"Beauty"</code>:
  216. * <p>
  217. * <pre>
  218. * Truth = Beauty
  219. * Truth:Beauty
  220. * Truth :Beauty
  221. * </pre>
  222. * As another example, the following three lines specify a single
  223. * property:
  224. * <p>
  225. * <pre>
  226. * fruits apple, banana, pear, \
  227. * cantaloupe, watermelon, \
  228. * kiwi, mango
  229. * </pre>
  230. * The key is <code>"fruits"</code> and the associated element is:
  231. * <p>
  232. * <pre>"apple, banana, pear, cantaloupe, watermelon, kiwi, mango"</pre>
  233. * Note that a space appears before each <code>\</code> so that a space
  234. * will appear after each comma in the final result; the <code>\</code>,
  235. * line terminator, and leading white space on the continuation line are
  236. * merely discarded and are <i>not</i> replaced by one or more other
  237. * characters.
  238. * <p>
  239. * As a third example, the line:
  240. * <p>
  241. * <pre>cheeses
  242. * </pre>
  243. * specifies that the key is <code>"cheeses"</code> and the associated
  244. * element is the empty string <code>""</code>.<p>
  245. *
  246. * @param inStream the input stream.
  247. * @exception IOException if an error occurred when reading from the
  248. * input stream.
  249. * @throws IllegalArgumentException if the input stream contains a
  250. * malformed Unicode escape sequence.
  251. */
  252. public synchronized void load(InputStream inStream) throws IOException {
  253. BufferedReader in = new BufferedReader(new InputStreamReader(inStream, "8859_1"));
  254. while (true) {
  255. // Get next line
  256. String line = in.readLine();
  257. if (line == null)
  258. return;
  259. if (line.length() > 0) {
  260. // Find start of key
  261. int len = line.length();
  262. int keyStart;
  263. for (keyStart=0; keyStart<len; keyStart++)
  264. if (whiteSpaceChars.indexOf(line.charAt(keyStart)) == -1)
  265. break;
  266. // Blank lines are ignored
  267. if (keyStart == len)
  268. continue;
  269. // Continue lines that end in slashes if they are not comments
  270. char firstChar = line.charAt(keyStart);
  271. if ((firstChar != '#') && (firstChar != '!')) {
  272. while (continueLine(line)) {
  273. String nextLine = in.readLine();
  274. if (nextLine == null)
  275. nextLine = "";
  276. String loppedLine = line.substring(0, len-1);
  277. // Advance beyond whitespace on new line
  278. int startIndex;
  279. for (startIndex=0; startIndex<nextLine.length(); startIndex++)
  280. if (whiteSpaceChars.indexOf(nextLine.charAt(startIndex)) == -1)
  281. break;
  282. nextLine = nextLine.substring(startIndex,nextLine.length());
  283. line = new String(loppedLine+nextLine);
  284. len = line.length();
  285. }
  286. // Find separation between key and value
  287. int separatorIndex;
  288. for (separatorIndex=keyStart; separatorIndex<len; separatorIndex++) {
  289. char currentChar = line.charAt(separatorIndex);
  290. if (currentChar == '\\')
  291. separatorIndex++;
  292. else if (keyValueSeparators.indexOf(currentChar) != -1)
  293. break;
  294. }
  295. // Skip over whitespace after key if any
  296. int valueIndex;
  297. for (valueIndex=separatorIndex; valueIndex<len; valueIndex++)
  298. if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
  299. break;
  300. // Skip over one non whitespace key value separators if any
  301. if (valueIndex < len)
  302. if (strictKeyValueSeparators.indexOf(line.charAt(valueIndex)) != -1)
  303. valueIndex++;
  304. // Skip over white space after other separators if any
  305. while (valueIndex < len) {
  306. if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
  307. break;
  308. valueIndex++;
  309. }
  310. String key = line.substring(keyStart, separatorIndex);
  311. String value = (separatorIndex < len) ? line.substring(valueIndex, len) : "";
  312. // Convert then store key and value
  313. key = loadConvert(key);
  314. value = loadConvert(value);
  315. put(key, value);
  316. }
  317. }
  318. }
  319. }
  320. /*
  321. * Returns true if the given line is a line that must
  322. * be appended to the next line
  323. */
  324. private boolean continueLine(String line) {
  325. int slashCount = 0;
  326. int index = line.length() - 1;
  327. while ((index >= 0) && (line.charAt(index--) == '\\'))
  328. slashCount++;
  329. return (slashCount % 2 == 1);
  330. }
  331. /*
  332. * Converts encoded \uxxxx to unicode chars
  333. * and changes special saved chars to their original forms
  334. */
  335. private String loadConvert(String theString) {
  336. char aChar;
  337. int len = theString.length();
  338. StringBuffer outBuffer = new StringBuffer(len);
  339. for (int x=0; x<len; ) {
  340. aChar = theString.charAt(x++);
  341. if (aChar == '\\') {
  342. aChar = theString.charAt(x++);
  343. if (aChar == 'u') {
  344. // Read the xxxx
  345. int value=0;
  346. for (int i=0; i<4; i++) {
  347. aChar = theString.charAt(x++);
  348. switch (aChar) {
  349. case '0': case '1': case '2': case '3': case '4':
  350. case '5': case '6': case '7': case '8': case '9':
  351. value = (value << 4) + aChar - '0';
  352. break;
  353. case 'a': case 'b': case 'c':
  354. case 'd': case 'e': case 'f':
  355. value = (value << 4) + 10 + aChar - 'a';
  356. break;
  357. case 'A': case 'B': case 'C':
  358. case 'D': case 'E': case 'F':
  359. value = (value << 4) + 10 + aChar - 'A';
  360. break;
  361. default:
  362. throw new IllegalArgumentException(
  363. "Malformed \\uxxxx encoding.");
  364. }
  365. }
  366. outBuffer.append((char)value);
  367. } else {
  368. if (aChar == 't') aChar = '\t';
  369. else if (aChar == 'r') aChar = '\r';
  370. else if (aChar == 'n') aChar = '\n';
  371. else if (aChar == 'f') aChar = '\f';
  372. outBuffer.append(aChar);
  373. }
  374. } else
  375. outBuffer.append(aChar);
  376. }
  377. return outBuffer.toString();
  378. }
  379. /*
  380. * Converts unicodes to encoded \uxxxx
  381. * and writes out any of the characters in specialSaveChars
  382. * with a preceding slash
  383. */
  384. private String saveConvert(String theString, boolean escapeSpace) {
  385. int len = theString.length();
  386. StringBuffer outBuffer = new StringBuffer(len*2);
  387. for(int x=0; x<len; x++) {
  388. char aChar = theString.charAt(x);
  389. switch(aChar) {
  390. case ' ':
  391. if (x == 0 || escapeSpace)
  392. outBuffer.append('\\');
  393. outBuffer.append(' ');
  394. break;
  395. case '\\':outBuffer.append('\\'); outBuffer.append('\\');
  396. break;
  397. case '\t':outBuffer.append('\\'); outBuffer.append('t');
  398. break;
  399. case '\n':outBuffer.append('\\'); outBuffer.append('n');
  400. break;
  401. case '\r':outBuffer.append('\\'); outBuffer.append('r');
  402. break;
  403. case '\f':outBuffer.append('\\'); outBuffer.append('f');
  404. break;
  405. default:
  406. if ((aChar < 0x0020) || (aChar > 0x007e)) {
  407. outBuffer.append('\\');
  408. outBuffer.append('u');
  409. outBuffer.append(toHex((aChar >> 12) & 0xF));
  410. outBuffer.append(toHex((aChar >> 8) & 0xF));
  411. outBuffer.append(toHex((aChar >> 4) & 0xF));
  412. outBuffer.append(toHex( aChar & 0xF));
  413. } else {
  414. if (specialSaveChars.indexOf(aChar) != -1)
  415. outBuffer.append('\\');
  416. outBuffer.append(aChar);
  417. }
  418. }
  419. }
  420. return outBuffer.toString();
  421. }
  422. /**
  423. * Calls the <code>store(OutputStream out, String header)</code> method
  424. * and suppresses IOExceptions that were thrown.
  425. *
  426. * @deprecated This method does not throw an IOException if an I/O error
  427. * occurs while saving the property list. As of the Java 2 platform v1.2, the preferred
  428. * way to save a properties list is via the <code>store(OutputStream out,
  429. * String header)</code> method.
  430. *
  431. * @param out an output stream.
  432. * @param header a description of the property list.
  433. * @exception ClassCastException if this <code>Properties</code> object
  434. * contains any keys or values that are not <code>Strings</code>.
  435. */
  436. public synchronized void save(OutputStream out, String header) {
  437. try {
  438. store(out, header);
  439. } catch (IOException e) {
  440. }
  441. }
  442. /**
  443. * Writes this property list (key and element pairs) in this
  444. * <code>Properties</code> table to the output stream in a format suitable
  445. * for loading into a <code>Properties</code> table using the
  446. * {@link #load(InputStream) load} method.
  447. * The stream is written using the ISO 8859-1 character encoding.
  448. * <p>
  449. * Properties from the defaults table of this <code>Properties</code>
  450. * table (if any) are <i>not</i> written out by this method.
  451. * <p>
  452. * If the header argument is not null, then an ASCII <code>#</code>
  453. * character, the header string, and a line separator are first written
  454. * to the output stream. Thus, the <code>header</code> can serve as an
  455. * identifying comment.
  456. * <p>
  457. * Next, a comment line is always written, consisting of an ASCII
  458. * <code>#</code> character, the current date and time (as if produced
  459. * by the <code>toString</code> method of <code>Date</code> for the
  460. * current time), and a line separator as generated by the Writer.
  461. * <p>
  462. * Then every entry in this <code>Properties</code> table is
  463. * written out, one per line. For each entry the key string is
  464. * written, then an ASCII <code>=</code>, then the associated
  465. * element string. Each character of the key and element strings
  466. * is examined to see whether it should be rendered as an escape
  467. * sequence. The ASCII characters <code>\</code>, tab, form feed,
  468. * newline, and carriage return are written as <code>\\</code>,
  469. * <code>\t</code>, <code>\f</code> <code>\n</code>, and
  470. * <code>\r</code>, respectively. Characters less than
  471. * <code>\u0020</code> and characters greater than
  472. * <code>\u007E</code> are written as
  473. * <code>\u</code><i>xxxx</i> for the appropriate hexadecimal
  474. * value <i>xxxx</i>. For the key, all space characters are
  475. * written with a preceding <code>\</code> character. For the
  476. * element, leading space characters, but not embedded or trailing
  477. * space characters, are written with a preceding <code>\</code>
  478. * character. The key and element characters <code>#</code>,
  479. * <code>!</code>, <code>=</code>, and <code>:</code> are written
  480. * with a preceding backslash to ensure that they are properly loaded.
  481. * <p>
  482. * After the entries have been written, the output stream is flushed. The
  483. * output stream remains open after this method returns.
  484. *
  485. * @param out an output stream.
  486. * @param header a description of the property list.
  487. * @exception IOException if writing this property list to the specified
  488. * output stream throws an <tt>IOException</tt>.
  489. * @exception ClassCastException if this <code>Properties</code> object
  490. * contains any keys or values that are not <code>Strings</code>.
  491. * @exception NullPointerException if <code>out</code> is null.
  492. * @since 1.2
  493. */
  494. public synchronized void store(OutputStream out, String header)
  495. throws IOException
  496. {
  497. BufferedWriter awriter;
  498. awriter = new BufferedWriter(new OutputStreamWriter(out, "8859_1"));
  499. if (header != null)
  500. writeln(awriter, "#" + header);
  501. writeln(awriter, "#" + new Date().toString());
  502. for (Enumeration e = keys(); e.hasMoreElements();) {
  503. String key = (String)e.nextElement();
  504. String val = (String)get(key);
  505. key = saveConvert(key, true);
  506. /* No need to escape embedded and trailing spaces for value, hence
  507. * pass false to flag.
  508. */
  509. val = saveConvert(val, false);
  510. writeln(awriter, key + "=" + val);
  511. }
  512. awriter.flush();
  513. }
  514. private static void writeln(BufferedWriter bw, String s) throws IOException {
  515. bw.write(s);
  516. bw.newLine();
  517. }
  518. /**
  519. * Searches for the property with the specified key in this property list.
  520. * If the key is not found in this property list, the default property list,
  521. * and its defaults, recursively, are then checked. The method returns
  522. * <code>null</code> if the property is not found.
  523. *
  524. * @param key the property key.
  525. * @return the value in this property list with the specified key value.
  526. * @see #setProperty
  527. * @see #defaults
  528. */
  529. public String getProperty(String key) {
  530. Object oval = super.get(key);
  531. String sval = (oval instanceof String) ? (String)oval : null;
  532. return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
  533. }
  534. /**
  535. * Searches for the property with the specified key in this property list.
  536. * If the key is not found in this property list, the default property list,
  537. * and its defaults, recursively, are then checked. The method returns the
  538. * default value argument if the property is not found.
  539. *
  540. * @param key the hashtable key.
  541. * @param defaultValue a default value.
  542. *
  543. * @return the value in this property list with the specified key value.
  544. * @see #setProperty
  545. * @see #defaults
  546. */
  547. public String getProperty(String key, String defaultValue) {
  548. String val = getProperty(key);
  549. return (val == null) ? defaultValue : val;
  550. }
  551. /**
  552. * Returns an enumeration of all the keys in this property list,
  553. * including distinct keys in the default property list if a key
  554. * of the same name has not already been found from the main
  555. * properties list.
  556. *
  557. * @return an enumeration of all the keys in this property list, including
  558. * the keys in the default property list.
  559. * @see java.util.Enumeration
  560. * @see java.util.Properties#defaults
  561. */
  562. public Enumeration propertyNames() {
  563. Hashtable h = new Hashtable();
  564. enumerate(h);
  565. return h.keys();
  566. }
  567. /**
  568. * Prints this property list out to the specified output stream.
  569. * This method is useful for debugging.
  570. *
  571. * @param out an output stream.
  572. */
  573. public void list(PrintStream out) {
  574. out.println("-- listing properties --");
  575. Hashtable h = new Hashtable();
  576. enumerate(h);
  577. for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
  578. String key = (String)e.nextElement();
  579. String val = (String)h.get(key);
  580. if (val.length() > 40) {
  581. val = val.substring(0, 37) + "...";
  582. }
  583. out.println(key + "=" + val);
  584. }
  585. }
  586. /**
  587. * Prints this property list out to the specified output stream.
  588. * This method is useful for debugging.
  589. *
  590. * @param out an output stream.
  591. * @since JDK1.1
  592. */
  593. /*
  594. * Rather than use an anonymous inner class to share common code, this
  595. * method is duplicated in order to ensure that a non-1.1 compiler can
  596. * compile this file.
  597. */
  598. public void list(PrintWriter out) {
  599. out.println("-- listing properties --");
  600. Hashtable h = new Hashtable();
  601. enumerate(h);
  602. for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
  603. String key = (String)e.nextElement();
  604. String val = (String)h.get(key);
  605. if (val.length() > 40) {
  606. val = val.substring(0, 37) + "...";
  607. }
  608. out.println(key + "=" + val);
  609. }
  610. }
  611. /**
  612. * Enumerates all key/value pairs in the specified hastable.
  613. * @param h the hashtable
  614. */
  615. private synchronized void enumerate(Hashtable h) {
  616. if (defaults != null) {
  617. defaults.enumerate(h);
  618. }
  619. for (Enumeration e = keys() ; e.hasMoreElements() ;) {
  620. String key = (String)e.nextElement();
  621. h.put(key, get(key));
  622. }
  623. }
  624. /**
  625. * Convert a nibble to a hex character
  626. * @param nibble the nibble to convert.
  627. */
  628. private static char toHex(int nibble) {
  629. return hexDigit[(nibble & 0xF)];
  630. }
  631. /** A table of hex digits */
  632. private static final char[] hexDigit = {
  633. '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
  634. };
  635. }