1. /*
  2. * @(#)Properties.java 1.52 01/11/29
  3. *
  4. * Copyright 2002 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. *
  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. *
  37. * @author Arthur van Hoff
  38. * @author Michael McCloskey
  39. * @version 1.52, 01/11/29
  40. * @since JDK1.0
  41. */
  42. public
  43. class Properties extends Hashtable {
  44. /**
  45. * use serialVersionUID from JDK 1.1.X for interoperability
  46. */
  47. private static final long serialVersionUID = 4112578634029874840L;
  48. /**
  49. * A property list that contains default values for any keys not
  50. * found in this property list.
  51. *
  52. * @serial
  53. */
  54. protected Properties defaults;
  55. /**
  56. * Creates an empty property list with no default values.
  57. */
  58. public Properties() {
  59. this(null);
  60. }
  61. /**
  62. * Creates an empty property list with the specified defaults.
  63. *
  64. * @param defaults the defaults.
  65. */
  66. public Properties(Properties defaults) {
  67. this.defaults = defaults;
  68. }
  69. /**
  70. * Calls the hashtable method <code>put</code>. Provided for
  71. * parallelism with the getProperties method. Enforces use of
  72. * strings for property keys and values.
  73. * @since JDK1.2
  74. */
  75. public synchronized Object setProperty(String key, String value) {
  76. return put(key, value);
  77. }
  78. private static final String keyValueSeparators = "=: \t\r\n\f";
  79. private static final String strictKeyValueSeparators = "=:";
  80. private static final String specialSaveChars = "=: \t\r\n\f#!";
  81. private static final String whiteSpaceChars = " \t\r\n\f";
  82. /**
  83. * Reads a property list (key and element pairs) from the input stream.
  84. * <p>
  85. * Every property occupies one line of the input stream. Each line
  86. * is terminated by a line terminator (<code>\n</code> or <code>\r</code>
  87. * or <code>\r\n</code>). Lines from the input stream are processed until
  88. * end of file is reached on the input stream.
  89. * <p>
  90. * A line that contains only whitespace or whose first non-whitespace
  91. * character is an ASCII <code>#</code> or <code>!</code> is ignored
  92. * (thus, <code>#</code> or <code>!</code> indicate comment lines).
  93. * <p>
  94. * Every line other than a blank line or a comment line describes one
  95. * property to be added to the table (except that if a line ends with \,
  96. * then the following line, if it exists, is treated as a continuation
  97. * line, as described
  98. * below). The key consists of all the characters in the line starting
  99. * with the first non-whitespace character and up to, but not including,
  100. * the first ASCII <code>=</code>, <code>:</code>, or whitespace
  101. * character. All of the key termination characters may be included in
  102. * the key by preceding them with a \.
  103. * Any whitespace after the key is skipped; if the first non-whitespace
  104. * character after the key is <code>=</code> or <code>:</code>, then it
  105. * is ignored and any whitespace characters after it are also skipped.
  106. * All remaining characters on the line become part of the associated
  107. * element string. Within the element string, the ASCII
  108. * escape sequences <code>\t</code>, <code>\n</code>,
  109. * <code>\r</code>, <code>\\</code>, <code>\"</code>, <code>\'</code>,
  110. * <code>\ </code> (a backslash and a space), and
  111. * <code>\\u</code><i>xxxx</i> are recognized and converted to single
  112. * characters. Moreover, if the last character on the line is
  113. * <code>\</code>, then the next line is treated as a continuation of the
  114. * current line; the <code>\</code> and line terminator are simply
  115. * discarded, and any leading whitespace characters on the continuation
  116. * line are also discarded and are not part of the element string.
  117. * <p>
  118. * As an example, each of the following four lines specifies the key
  119. * <code>"Truth"</code> and the associated element value
  120. * <code>"Beauty"</code>:
  121. * <p>
  122. * <pre>
  123. * Truth = Beauty
  124. * Truth:Beauty
  125. * Truth :Beauty
  126. * </pre>
  127. * As another example, the following three lines specify a single
  128. * property:
  129. * <p>
  130. * <pre>
  131. * fruits apple, banana, pear, \
  132. * cantaloupe, watermelon, \
  133. * kiwi, mango
  134. * </pre>
  135. * The key is <code>"fruits"</code> and the associated element is:
  136. * <p>
  137. * <pre>"apple, banana, pear, cantaloupe, watermelon,kiwi, mango"</pre>
  138. * Note that a space appears before each <code>\</code> so that a space
  139. * will appear after each comma in the final result; the <code>\</code>,
  140. * line terminator, and leading whitespace on the continuation line are
  141. * merely discarded and are <i>not</i> replaced by one or more other
  142. * characters.
  143. * <p>
  144. * As a third example, the line:
  145. * <p>
  146. * <pre>cheeses
  147. * </pre>
  148. * specifies that the key is <code>"cheeses"</code> and the associated
  149. * element is the empty string.<p>
  150. *
  151. * @param in the input stream.
  152. * @exception IOException if an error occurred when reading from the
  153. * input stream.
  154. */
  155. public synchronized void load(InputStream inStream) throws IOException {
  156. BufferedReader in = new BufferedReader(new InputStreamReader(inStream, "8859_1"));
  157. while (true) {
  158. // Get next line
  159. String line = in.readLine();
  160. if(line == null)
  161. return;
  162. if (line.length() > 0) {
  163. // Continue lines that end in slashes if they are not comments
  164. char firstChar = line.charAt(0);
  165. if ((firstChar != '#') && (firstChar != '!')) {
  166. while (continueLine(line)) {
  167. String nextLine = in.readLine();
  168. if(nextLine == null)
  169. nextLine = new String("");
  170. String loppedLine = line.substring(0, line.length()-1);
  171. // Advance beyond whitespace on new line
  172. int startIndex=0;
  173. for(startIndex=0; startIndex<nextLine.length(); startIndex++)
  174. if (whiteSpaceChars.indexOf(nextLine.charAt(startIndex)) == -1)
  175. break;
  176. nextLine = nextLine.substring(startIndex,nextLine.length());
  177. line = new String(loppedLine+nextLine);
  178. }
  179. // Find start of key
  180. int len = line.length();
  181. int keyStart;
  182. for(keyStart=0; keyStart<len; keyStart++) {
  183. if(whiteSpaceChars.indexOf(line.charAt(keyStart)) == -1)
  184. break;
  185. }
  186. // Find separation between key and value
  187. int separatorIndex;
  188. for(separatorIndex=keyStart; separatorIndex<len; separatorIndex++) {
  189. char currentChar = line.charAt(separatorIndex);
  190. if (currentChar == '\\')
  191. separatorIndex++;
  192. else if(keyValueSeparators.indexOf(currentChar) != -1)
  193. break;
  194. }
  195. // Skip over whitespace after key if any
  196. int valueIndex;
  197. for (valueIndex=separatorIndex; valueIndex<len; valueIndex++)
  198. if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
  199. break;
  200. // Skip over one non whitespace key value separators if any
  201. if (valueIndex < len)
  202. if (strictKeyValueSeparators.indexOf(line.charAt(valueIndex)) != -1)
  203. valueIndex++;
  204. // Skip over white space after other separators if any
  205. while (valueIndex < len) {
  206. if (whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
  207. break;
  208. valueIndex++;
  209. }
  210. String key = line.substring(keyStart, separatorIndex);
  211. String value = (separatorIndex < len) ? line.substring(valueIndex, len) : "";
  212. // Convert then store key and value
  213. key = loadConvert(key);
  214. value = loadConvert(value);
  215. put(key, value);
  216. }
  217. }
  218. }
  219. }
  220. /*
  221. * Returns true if the given line is a line that must
  222. * be appended to the next line
  223. */
  224. private boolean continueLine (String line) {
  225. int slashCount = 0;
  226. int index = line.length() - 1;
  227. while((index >= 0) && (line.charAt(index--) == '\\'))
  228. slashCount++;
  229. return (slashCount % 2 == 1);
  230. }
  231. /*
  232. * Converts encoded \\uxxxx to unicode chars
  233. * and changes special saved chars to their original forms
  234. */
  235. private String loadConvert (String theString) {
  236. char aChar;
  237. int len = theString.length();
  238. StringBuffer outBuffer = new StringBuffer(len);
  239. for(int x=0; x<len; ) {
  240. aChar = theString.charAt(x++);
  241. if (aChar == '\\') {
  242. aChar = theString.charAt(x++);
  243. if(aChar == 'u') {
  244. // Read the xxxx
  245. int value=0;
  246. for (int i=0; i<4; i++) {
  247. aChar = theString.charAt(x++);
  248. switch (aChar) {
  249. case '0': case '1': case '2': case '3': case '4':
  250. case '5': case '6': case '7': case '8': case '9':
  251. value = (value << 4) + aChar - '0';
  252. break;
  253. case 'a': case 'b': case 'c':
  254. case 'd': case 'e': case 'f':
  255. value = (value << 4) + 10 + aChar - 'a';
  256. break;
  257. case 'A': case 'B': case 'C':
  258. case 'D': case 'E': case 'F':
  259. value = (value << 4) + 10 + aChar - 'A';
  260. break;
  261. default:
  262. throw new IllegalArgumentException(
  263. "Malformed \\uxxxx encoding.");
  264. }
  265. }
  266. outBuffer.append((char)value);
  267. } else {
  268. if (aChar == 't') aChar = '\t';
  269. else if (aChar == 'r') aChar = '\r';
  270. else if (aChar == 'n') aChar = '\n';
  271. else if (aChar == 'f') aChar = '\f';
  272. outBuffer.append(aChar);
  273. }
  274. } else
  275. outBuffer.append(aChar);
  276. }
  277. return outBuffer.toString();
  278. }
  279. /*
  280. * Converts unicodes to encoded \\uxxxx
  281. * and writes out any of the characters in specialSaveChars
  282. * with a preceding slash
  283. */
  284. private String saveConvert(String theString) {
  285. char aChar;
  286. int len = theString.length();
  287. StringBuffer outBuffer = new StringBuffer(len*2);
  288. for(int x=0; x<len; ) {
  289. aChar = theString.charAt(x++);
  290. switch(aChar) {
  291. case '\\':outBuffer.append('\\'); outBuffer.append('\\');
  292. continue;
  293. case '\t':outBuffer.append('\\'); outBuffer.append('t');
  294. continue;
  295. case '\n':outBuffer.append('\\'); outBuffer.append('n');
  296. continue;
  297. case '\r':outBuffer.append('\\'); outBuffer.append('r');
  298. continue;
  299. case '\f':outBuffer.append('\\'); outBuffer.append('f');
  300. continue;
  301. default:
  302. if ((aChar < 20) || (aChar > 127)) {
  303. outBuffer.append('\\');
  304. outBuffer.append('u');
  305. outBuffer.append(toHex((aChar >> 12) & 0xF));
  306. outBuffer.append(toHex((aChar >> 8) & 0xF));
  307. outBuffer.append(toHex((aChar >> 4) & 0xF));
  308. outBuffer.append(toHex((aChar >> 0) & 0xF));
  309. }
  310. else {
  311. if (specialSaveChars.indexOf(aChar) != -1)
  312. outBuffer.append('\\');
  313. outBuffer.append(aChar);
  314. }
  315. }
  316. }
  317. return outBuffer.toString();
  318. }
  319. /**
  320. * Calls the <code>store(OutputStream out, String header)</code> method
  321. * and suppresses IOExceptions that were thrown.
  322. *
  323. * @deprecated This method does not throw an IOException if an I/O error
  324. * occurs while saving the property list. As of JDK 1.2, the preferred
  325. * way to save a properties list is via the <code>store(OutputStream out,
  326. * String header)</code> method.
  327. *
  328. * @param out an output stream.
  329. * @param header a description of the property list.
  330. * @exception ClassCastException if this <code>Properties</code> object
  331. * contains any keys or values that are not <code>Strings</code>.
  332. */
  333. public synchronized void save(OutputStream out, String header) {
  334. try {
  335. store(out, header);
  336. } catch (IOException e) {
  337. }
  338. }
  339. /**
  340. * Writes this property list (key and element pairs) in this
  341. * <code>Properties</code> table to the output stream in a format suitable
  342. * for loading into a <code>Properties</code> table using the
  343. * <code>load</code> method.
  344. * <p>
  345. * Properties from the defaults table of this <code>Properties</code>
  346. * table (if any) are <i>not</i> written out by this method.
  347. * <p>
  348. * If the header argument is not null, then an ASCII <code>#</code>
  349. * character, the header string, and a line separator are first written
  350. * to the output stream. Thus, the <code>header</code> can serve as an
  351. * identifying comment.
  352. * <p>
  353. * Next, a comment line is always written, consisting of an ASCII
  354. * <code>#</code> character, the current date and time (as if produced
  355. * by the <code>toString</code> method of <code>Date</code> for the
  356. * current time), and a line separator as generated by the Writer.
  357. * <p>
  358. * Then every entry in this <code>Properties</code> table is written out,
  359. * one per line. For each entry the key string is written, then an ASCII
  360. * <code>=</code>, then the associated element string. Each character of
  361. * the element string is examined to see whether it should be rendered as
  362. * an escape sequence. The ASCII characters <code>\</code>, tab, newline,
  363. * and carriage return are written as <code>\\</code>, <code>\t</code>,
  364. * <code>\n</code>, and <code>\r</code>, respectively. Characters less
  365. * than <code>\u0020</code> and characters greater than
  366. * <code>\u007E</code> are written as <code>\\u</code><i>xxxx</i> for
  367. * the appropriate hexadecimal value <i>xxxx</i>. Space characters, but
  368. * not embedded or trailing space characters, are written with a preceding
  369. * <code>\</code>. The key and value characters <code>#</code>,
  370. * <code>!</code>, <code>=</code>, and <code>:</code> are written with a
  371. * preceding slash to ensure that they are properly loaded.
  372. * <p>
  373. * After the entries have been written, the output stream is flushed. The
  374. * output stream remains open after this method returns.
  375. *
  376. * @param out an output stream.
  377. * @param header a description of the property list.
  378. * @exception ClassCastException if this <code>Properties</code> object
  379. * contains any keys or values that are not <code>Strings</code>.
  380. */
  381. public synchronized void store(OutputStream out, String header)
  382. throws IOException
  383. {
  384. BufferedWriter awriter;
  385. awriter = new BufferedWriter(new OutputStreamWriter(out, "8859_1"));
  386. if (header != null)
  387. writeln(awriter, "#" + header);
  388. writeln(awriter, "#" + new Date().toString());
  389. for (Enumeration e = keys(); e.hasMoreElements();) {
  390. String key = (String)e.nextElement();
  391. String val = (String)get(key);
  392. key = saveConvert(key);
  393. val = saveConvert(val);
  394. writeln(awriter, key + "=" + val);
  395. }
  396. awriter.flush();
  397. }
  398. private static void writeln(BufferedWriter bw, String s) throws IOException {
  399. bw.write(s);
  400. bw.newLine();
  401. }
  402. /**
  403. * Searches for the property with the specified key in this property list.
  404. * If the key is not found in this property list, the default property list,
  405. * and its defaults, recursively, are then checked. The method returns
  406. * <code>null</code> if the property is not found.
  407. *
  408. * @param key the property key.
  409. * @return the value in this property list with the specified key value.
  410. * @see java.util.Properties#defaults
  411. */
  412. public String getProperty(String key) {
  413. Object oval = super.get(key);
  414. String sval = (oval instanceof String) ? (String)oval : null;
  415. return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
  416. }
  417. /**
  418. * Searches for the property with the specified key in this property list.
  419. * If the key is not found in this property list, the default property list,
  420. * and its defaults, recursively, are then checked. The method returns the
  421. * default value argument if the property is not found.
  422. *
  423. * @param key the hashtable key.
  424. * @param defaultValue a default value.
  425. *
  426. * @return the value in this property list with the specified key value.
  427. * @see java.util.Properties#defaults
  428. */
  429. public String getProperty(String key, String defaultValue) {
  430. String val = getProperty(key);
  431. return (val == null) ? defaultValue : val;
  432. }
  433. /**
  434. * Returns an enumeration of all the keys in this property list, including
  435. * the keys in the default property list.
  436. *
  437. * @return an enumeration of all the keys in this property list, including
  438. * the keys in the default property list.
  439. * @see java.util.Enumeration
  440. * @see java.util.Properties#defaults
  441. */
  442. public Enumeration propertyNames() {
  443. Hashtable h = new Hashtable();
  444. enumerate(h);
  445. return h.keys();
  446. }
  447. /**
  448. * Prints this property list out to the specified output stream.
  449. * This method is useful for debugging.
  450. *
  451. * @param out an output stream.
  452. */
  453. public void list(PrintStream out) {
  454. out.println("-- listing properties --");
  455. Hashtable h = new Hashtable();
  456. enumerate(h);
  457. for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
  458. String key = (String)e.nextElement();
  459. String val = (String)h.get(key);
  460. if (val.length() > 40) {
  461. val = val.substring(0, 37) + "...";
  462. }
  463. out.println(key + "=" + val);
  464. }
  465. }
  466. /**
  467. * Prints this property list out to the specified output stream.
  468. * This method is useful for debugging.
  469. *
  470. * @param out an output stream.
  471. * @since JDK1.1
  472. */
  473. /*
  474. * Rather than use an anonymous inner class to share common code, this
  475. * method is duplicated in order to ensure that a non-1.1 compiler can
  476. * compile this file.
  477. */
  478. public void list(PrintWriter out) {
  479. out.println("-- listing properties --");
  480. Hashtable h = new Hashtable();
  481. enumerate(h);
  482. for (Enumeration e = h.keys() ; e.hasMoreElements() ;) {
  483. String key = (String)e.nextElement();
  484. String val = (String)h.get(key);
  485. if (val.length() > 40) {
  486. val = val.substring(0, 37) + "...";
  487. }
  488. out.println(key + "=" + val);
  489. }
  490. }
  491. /**
  492. * Enumerates all key/value pairs in the specified hastable.
  493. * @param h the hashtable
  494. */
  495. private synchronized void enumerate(Hashtable h) {
  496. if (defaults != null) {
  497. defaults.enumerate(h);
  498. }
  499. for (Enumeration e = keys() ; e.hasMoreElements() ;) {
  500. String key = (String)e.nextElement();
  501. h.put(key, get(key));
  502. }
  503. }
  504. /**
  505. * Convert a nibble to a hex character
  506. * @param nibble the nibble to convert.
  507. */
  508. private static char toHex(int nibble) {
  509. return hexDigit[(nibble & 0xF)];
  510. }
  511. /** A table of hex digits */
  512. private static final char[] hexDigit = {
  513. '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
  514. };
  515. }