1. /*
  2. * Copyright 2001-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package org.apache.commons.collections;
  17. import java.io.File;
  18. import java.io.FileInputStream;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.InputStreamReader;
  22. import java.io.LineNumberReader;
  23. import java.io.OutputStream;
  24. import java.io.PrintWriter;
  25. import java.io.Reader;
  26. import java.io.UnsupportedEncodingException;
  27. import java.util.ArrayList;
  28. import java.util.Enumeration;
  29. import java.util.Hashtable;
  30. import java.util.Iterator;
  31. import java.util.List;
  32. import java.util.NoSuchElementException;
  33. import java.util.Properties;
  34. import java.util.StringTokenizer;
  35. import java.util.Vector;
  36. /**
  37. * This class extends normal Java properties by adding the possibility
  38. * to use the same key many times concatenating the value strings
  39. * instead of overwriting them.
  40. * <p>
  41. * <b>Please consider using the <code>PropertiesConfiguration</code> class in
  42. * Commons-Configuration as soon as it is released.</b>
  43. * <p>
  44. * The Extended Properties syntax is explained here:
  45. *
  46. * <ul>
  47. * <li>
  48. * Each property has the syntax <code>key = value</code>
  49. * </li>
  50. * <li>
  51. * The <i>key</i> may use any character but the equal sign '='.
  52. * </li>
  53. * <li>
  54. * <i>value</i> may be separated on different lines if a backslash
  55. * is placed at the end of the line that continues below.
  56. * </li>
  57. * <li>
  58. * If <i>value</i> is a list of strings, each token is separated
  59. * by a comma ','.
  60. * </li>
  61. * <li>
  62. * Commas in each token are escaped placing a backslash right before
  63. * the comma.
  64. * </li>
  65. * <li>
  66. * Backslashes are escaped by using two consecutive backslashes i.e. \\
  67. * </li>
  68. * <li>
  69. * If a <i>key</i> is used more than once, the values are appended
  70. * like if they were on the same line separated with commas.
  71. * </li>
  72. * <li>
  73. * Blank lines and lines starting with character '#' are skipped.
  74. * </li>
  75. * <li>
  76. * If a property is named "include" (or whatever is defined by
  77. * setInclude() and getInclude() and the value of that property is
  78. * the full path to a file on disk, that file will be included into
  79. * the ConfigurationsRepository. You can also pull in files relative
  80. * to the parent configuration file. So if you have something
  81. * like the following:
  82. *
  83. * include = additional.properties
  84. *
  85. * Then "additional.properties" is expected to be in the same
  86. * directory as the parent configuration file.
  87. *
  88. * Duplicate name values will be replaced, so be careful.
  89. *
  90. * </li>
  91. * </ul>
  92. *
  93. * <p>Here is an example of a valid extended properties file:
  94. *
  95. * <p><pre>
  96. * # lines starting with # are comments
  97. *
  98. * # This is the simplest property
  99. * key = value
  100. *
  101. * # A long property may be separated on multiple lines
  102. * longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
  103. * aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
  104. *
  105. * # This is a property with many tokens
  106. * tokens_on_a_line = first token, second token
  107. *
  108. * # This sequence generates exactly the same result
  109. * tokens_on_multiple_lines = first token
  110. * tokens_on_multiple_lines = second token
  111. *
  112. * # commas may be escaped in tokens
  113. * commas.escaped = Hi\, what'up?
  114. * </pre>
  115. *
  116. * <p><b>NOTE</b>: this class has <b>not</b> been written for
  117. * performance nor low memory usage. In fact, it's way slower than it
  118. * could be and generates too much memory garbage. But since
  119. * performance is not an issue during intialization (and there is not
  120. * much time to improve it), I wrote it this way. If you don't like
  121. * it, go ahead and tune it up!
  122. *
  123. * @since Commons Collections 1.0
  124. * @version $Revision: 1.23 $ $Date: 2004/06/21 23:39:25 $
  125. *
  126. * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
  127. * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>
  128. * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>
  129. * @author <a href="mailto:jvanzyl@periapt.com">Jason van Zyl</a>
  130. * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
  131. * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
  132. * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>
  133. * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>
  134. * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>
  135. * @author Janek Bogucki
  136. * @author Mohan Kishore
  137. * @author Stephen Colebourne
  138. */
  139. public class ExtendedProperties extends Hashtable {
  140. /**
  141. * Default configurations repository.
  142. */
  143. private ExtendedProperties defaults;
  144. /**
  145. * The file connected to this repository (holding comments and
  146. * such).
  147. *
  148. * @serial
  149. */
  150. protected String file;
  151. /**
  152. * Base path of the configuration file used to create
  153. * this ExtendedProperties object.
  154. */
  155. protected String basePath;
  156. /**
  157. * File separator.
  158. */
  159. protected String fileSeparator = System.getProperty("file.separator");
  160. /**
  161. * Has this configuration been intialized.
  162. */
  163. protected boolean isInitialized = false;
  164. /**
  165. * This is the name of the property that can point to other
  166. * properties file for including other properties files.
  167. */
  168. protected static String include = "include";
  169. /**
  170. * These are the keys in the order they listed
  171. * in the configuration file. This is useful when
  172. * you wish to perform operations with configuration
  173. * information in a particular order.
  174. */
  175. protected ArrayList keysAsListed = new ArrayList();
  176. protected final static String START_TOKEN="${";
  177. protected final static String END_TOKEN="}";
  178. /**
  179. * Interpolate key names to handle ${key} stuff
  180. *
  181. * @param base string to interpolate
  182. * @return returns the key name with the ${key} substituted
  183. */
  184. protected String interpolate(String base) {
  185. // COPIED from [configuration] 2003-12-29
  186. return (interpolateHelper(base, null));
  187. }
  188. /**
  189. * Recursive handler for multiple levels of interpolation.
  190. *
  191. * When called the first time, priorVariables should be null.
  192. *
  193. * @param base string with the ${key} variables
  194. * @param priorVariables serves two purposes: to allow checking for
  195. * loops, and creating a meaningful exception message should a loop
  196. * occur. It's 0'th element will be set to the value of base from
  197. * the first call. All subsequent interpolated variables are added
  198. * afterward.
  199. *
  200. * @return the string with the interpolation taken care of
  201. */
  202. protected String interpolateHelper(String base, List priorVariables) {
  203. // COPIED from [configuration] 2003-12-29
  204. if (base == null) {
  205. return null;
  206. }
  207. // on the first call initialize priorVariables
  208. // and add base as the first element
  209. if (priorVariables == null) {
  210. priorVariables = new ArrayList();
  211. priorVariables.add(base);
  212. }
  213. int begin = -1;
  214. int end = -1;
  215. int prec = 0 - END_TOKEN.length();
  216. String variable = null;
  217. StringBuffer result = new StringBuffer();
  218. // FIXME: we should probably allow the escaping of the start token
  219. while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length())) > -1)
  220. && ((end = base.indexOf(END_TOKEN, begin)) > -1)) {
  221. result.append(base.substring(prec + END_TOKEN.length(), begin));
  222. variable = base.substring(begin + START_TOKEN.length(), end);
  223. // if we've got a loop, create a useful exception message and throw
  224. if (priorVariables.contains(variable)) {
  225. String initialBase = priorVariables.remove(0).toString();
  226. priorVariables.add(variable);
  227. StringBuffer priorVariableSb = new StringBuffer();
  228. // create a nice trace of interpolated variables like so:
  229. // var1->var2->var3
  230. for (Iterator it = priorVariables.iterator(); it.hasNext();) {
  231. priorVariableSb.append(it.next());
  232. if (it.hasNext()) {
  233. priorVariableSb.append("->");
  234. }
  235. }
  236. throw new IllegalStateException(
  237. "infinite loop in property interpolation of " + initialBase + ": " + priorVariableSb.toString());
  238. }
  239. // otherwise, add this variable to the interpolation list.
  240. else {
  241. priorVariables.add(variable);
  242. }
  243. //QUESTION: getProperty or getPropertyDirect
  244. Object value = getProperty(variable);
  245. if (value != null) {
  246. result.append(interpolateHelper(value.toString(), priorVariables));
  247. // pop the interpolated variable off the stack
  248. // this maintains priorVariables correctness for
  249. // properties with multiple interpolations, e.g.
  250. // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
  251. priorVariables.remove(priorVariables.size() - 1);
  252. } else if (defaults != null && defaults.getString(variable, null) != null) {
  253. result.append(defaults.getString(variable));
  254. } else {
  255. //variable not defined - so put it back in the value
  256. result.append(START_TOKEN).append(variable).append(END_TOKEN);
  257. }
  258. prec = end;
  259. }
  260. result.append(base.substring(prec + END_TOKEN.length(), base.length()));
  261. return result.toString();
  262. }
  263. /**
  264. * Inserts a backslash before every comma and backslash.
  265. */
  266. private static String escape(String s) {
  267. StringBuffer buf = new StringBuffer(s);
  268. for (int i = 0; i < buf.length(); i++) {
  269. char c = buf.charAt(i);
  270. if (c == ',' || c == '\\') {
  271. buf.insert(i, '\\');
  272. i++;
  273. }
  274. }
  275. return buf.toString();
  276. }
  277. /**
  278. * Removes a backslash from every pair of backslashes.
  279. */
  280. private static String unescape(String s) {
  281. StringBuffer buf = new StringBuffer(s);
  282. for (int i = 0; i < buf.length() - 1; i++) {
  283. char c1 = buf.charAt(i);
  284. char c2 = buf.charAt(i + 1);
  285. if (c1 == '\\' && c2 == '\\') {
  286. buf.deleteCharAt(i);
  287. }
  288. }
  289. return buf.toString();
  290. }
  291. /**
  292. * Counts the number of successive times 'ch' appears in the
  293. * 'line' before the position indicated by the 'index'.
  294. */
  295. private static int countPreceding(String line, int index, char ch) {
  296. int i;
  297. for (i = index - 1; i >= 0; i--) {
  298. if (line.charAt(i) != ch) {
  299. break;
  300. }
  301. }
  302. return index - 1 - i;
  303. }
  304. /**
  305. * Checks if the line ends with odd number of backslashes
  306. */
  307. private static boolean endsWithSlash(String line) {
  308. if (!line.endsWith("\\")) {
  309. return false;
  310. }
  311. return (countPreceding(line, line.length() - 1, '\\') % 2 == 0);
  312. }
  313. /**
  314. * This class is used to read properties lines. These lines do
  315. * not terminate with new-line chars but rather when there is no
  316. * backslash sign a the end of the line. This is used to
  317. * concatenate multiple lines for readability.
  318. */
  319. static class PropertiesReader extends LineNumberReader {
  320. /**
  321. * Constructor.
  322. *
  323. * @param reader A Reader.
  324. */
  325. public PropertiesReader(Reader reader) {
  326. super(reader);
  327. }
  328. /**
  329. * Read a property.
  330. *
  331. * @return a String property
  332. * @throws IOException if there is difficulty reading the source.
  333. */
  334. public String readProperty() throws IOException {
  335. StringBuffer buffer = new StringBuffer();
  336. try {
  337. while (true) {
  338. String line = readLine().trim();
  339. if ((line.length() != 0) && (line.charAt(0) != '#')) {
  340. if (endsWithSlash(line)) {
  341. line = line.substring(0, line.length() - 1);
  342. buffer.append(line);
  343. } else {
  344. buffer.append(line);
  345. break;
  346. }
  347. }
  348. }
  349. } catch (NullPointerException ex) {
  350. return null;
  351. }
  352. return buffer.toString();
  353. }
  354. }
  355. /**
  356. * This class divides into tokens a property value. Token
  357. * separator is "," but commas into the property value are escaped
  358. * using the backslash in front.
  359. */
  360. static class PropertiesTokenizer extends StringTokenizer {
  361. /**
  362. * The property delimiter used while parsing (a comma).
  363. */
  364. static final String DELIMITER = ",";
  365. /**
  366. * Constructor.
  367. *
  368. * @param string A String.
  369. */
  370. public PropertiesTokenizer(String string) {
  371. super(string, DELIMITER);
  372. }
  373. /**
  374. * Check whether the object has more tokens.
  375. *
  376. * @return True if the object has more tokens.
  377. */
  378. public boolean hasMoreTokens() {
  379. return super.hasMoreTokens();
  380. }
  381. /**
  382. * Get next token.
  383. *
  384. * @return A String.
  385. */
  386. public String nextToken() {
  387. StringBuffer buffer = new StringBuffer();
  388. while (hasMoreTokens()) {
  389. String token = super.nextToken();
  390. if (endsWithSlash(token)) {
  391. buffer.append(token.substring(0, token.length() - 1));
  392. buffer.append(DELIMITER);
  393. } else {
  394. buffer.append(token);
  395. break;
  396. }
  397. }
  398. return buffer.toString().trim();
  399. }
  400. }
  401. /**
  402. * Creates an empty extended properties object.
  403. */
  404. public ExtendedProperties() {
  405. super();
  406. }
  407. /**
  408. * Creates and loads the extended properties from the specified file.
  409. *
  410. * @param file the filename to load
  411. * @throws IOException if a file error occurs
  412. */
  413. public ExtendedProperties(String file) throws IOException {
  414. this(file, null);
  415. }
  416. /**
  417. * Creates and loads the extended properties from the specified file.
  418. *
  419. * @param file the filename to load
  420. * @param defaultFile a second filename to load default values from
  421. * @throws IOException if a file error occurs
  422. */
  423. public ExtendedProperties(String file, String defaultFile) throws IOException {
  424. this.file = file;
  425. basePath = new File(file).getAbsolutePath();
  426. basePath = basePath.substring(0, basePath.lastIndexOf(fileSeparator) + 1);
  427. FileInputStream in = null;
  428. try {
  429. in = new FileInputStream(file);
  430. this.load(in);
  431. } finally {
  432. try {
  433. if (in != null) {
  434. in.close();
  435. }
  436. } catch (IOException ex) {}
  437. }
  438. if (defaultFile != null) {
  439. defaults = new ExtendedProperties(defaultFile);
  440. }
  441. }
  442. /**
  443. * Indicate to client code whether property
  444. * resources have been initialized or not.
  445. */
  446. public boolean isInitialized() {
  447. return isInitialized;
  448. }
  449. /**
  450. * Gets the property value for including other properties files.
  451. * By default it is "include".
  452. *
  453. * @return A String.
  454. */
  455. public String getInclude() {
  456. return include;
  457. }
  458. /**
  459. * Sets the property value for including other properties files.
  460. * By default it is "include".
  461. *
  462. * @param inc A String.
  463. */
  464. public void setInclude(String inc) {
  465. include = inc;
  466. }
  467. /**
  468. * Load the properties from the given input stream.
  469. *
  470. * @param input the InputStream to load from
  471. * @throws IOException if an IO error occurs
  472. */
  473. public void load(InputStream input) throws IOException {
  474. load(input, null);
  475. }
  476. /**
  477. * Load the properties from the given input stream
  478. * and using the specified encoding.
  479. *
  480. * @param input the InputStream to load from
  481. * @param enc the encoding to use
  482. * @throws IOException if an IO error occurs
  483. */
  484. public synchronized void load(InputStream input, String enc) throws IOException {
  485. PropertiesReader reader = null;
  486. if (enc != null) {
  487. try {
  488. reader = new PropertiesReader(new InputStreamReader(input, enc));
  489. } catch (UnsupportedEncodingException ex) {
  490. // Another try coming up....
  491. }
  492. }
  493. if (reader == null) {
  494. try {
  495. reader = new PropertiesReader(new InputStreamReader(input, "8859_1"));
  496. } catch (UnsupportedEncodingException ex) {
  497. // ISO8859-1 support is required on java platforms but....
  498. // If it's not supported, use the system default encoding
  499. reader = new PropertiesReader(new InputStreamReader(input));
  500. }
  501. }
  502. try {
  503. while (true) {
  504. String line = reader.readProperty();
  505. int equalSign = line.indexOf('=');
  506. if (equalSign > 0) {
  507. String key = line.substring(0, equalSign).trim();
  508. String value = line.substring(equalSign + 1).trim();
  509. // Configure produces lines like this ... just ignore them
  510. if ("".equals(value)) {
  511. continue;
  512. }
  513. if (getInclude() != null && key.equalsIgnoreCase(getInclude())) {
  514. // Recursively load properties files.
  515. File file = null;
  516. if (value.startsWith(fileSeparator)) {
  517. // We have an absolute path so we'll use this
  518. file = new File(value);
  519. } else {
  520. // We have a relative path, and we have two
  521. // possible forms here. If we have the "./" form
  522. // then just strip that off first before continuing.
  523. if (value.startsWith("." + fileSeparator)) {
  524. value = value.substring(2);
  525. }
  526. file = new File(basePath + value);
  527. }
  528. if (file != null && file.exists() && file.canRead()) {
  529. load(new FileInputStream(file));
  530. }
  531. } else {
  532. addProperty(key, value);
  533. }
  534. }
  535. }
  536. } catch (NullPointerException ex) {
  537. // Should happen only when EOF is reached.
  538. return;
  539. } finally {
  540. // Loading is initializing
  541. isInitialized = true;
  542. }
  543. }
  544. /**
  545. * Gets a property from the configuration.
  546. *
  547. * @param key property to retrieve
  548. * @return value as object. Will return user value if exists,
  549. * if not then default value if exists, otherwise null
  550. */
  551. public Object getProperty(String key) {
  552. // first, try to get from the 'user value' store
  553. Object obj = this.get(key);
  554. if (obj == null) {
  555. // if there isn't a value there, get it from the
  556. // defaults if we have them
  557. if (defaults != null) {
  558. obj = defaults.get(key);
  559. }
  560. }
  561. return obj;
  562. }
  563. /**
  564. * Add a property to the configuration. If it already
  565. * exists then the value stated here will be added
  566. * to the configuration entry. For example, if
  567. *
  568. * <code>resource.loader = file</code>
  569. *
  570. * is already present in the configuration and you
  571. *
  572. * <code>addProperty("resource.loader", "classpath")</code>
  573. *
  574. * Then you will end up with a Vector like the
  575. * following:
  576. *
  577. * <code>["file", "classpath"]</code>
  578. *
  579. * @param key the key to add
  580. * @param value the value to add
  581. */
  582. public void addProperty(String key, Object value) {
  583. if (value instanceof String) {
  584. String str = (String) value;
  585. if (str.indexOf(PropertiesTokenizer.DELIMITER) > 0) {
  586. // token contains commas, so must be split apart then added
  587. PropertiesTokenizer tokenizer = new PropertiesTokenizer(str);
  588. while (tokenizer.hasMoreTokens()) {
  589. String token = tokenizer.nextToken();
  590. addPropertyInternal(key, unescape(token));
  591. }
  592. } else {
  593. // token contains no commas, so can be simply added
  594. addPropertyInternal(key, unescape(str));
  595. }
  596. } else {
  597. addPropertyInternal(key, value);
  598. }
  599. // Adding a property connotes initialization
  600. isInitialized = true;
  601. }
  602. /**
  603. * Adds a key/value pair to the map. This routine does
  604. * no magic morphing. It ensures the keylist is maintained
  605. *
  606. * @param key the key to store at
  607. * @param value the decoded object to store
  608. */
  609. private void addPropertyDirect(String key, Object value) {
  610. // safety check
  611. if (!containsKey(key)) {
  612. keysAsListed.add(key);
  613. }
  614. put(key, value);
  615. }
  616. /**
  617. * Adds a decoded property to the map w/o checking for commas - used
  618. * internally when a property has been broken up into
  619. * strings that could contain escaped commas to prevent
  620. * the inadvertent vectorization.
  621. * <p>
  622. * Thanks to Leon Messerschmidt for this one.
  623. *
  624. * @param key the key to store at
  625. * @param value the decoded object to store
  626. */
  627. private void addPropertyInternal(String key, Object value) {
  628. Object current = this.get(key);
  629. if (current instanceof String) {
  630. // one object already in map - convert it to a vector
  631. Vector v = new Vector(2);
  632. v.addElement(current);
  633. v.addElement(value);
  634. put(key, v);
  635. } else if (current instanceof Vector) {
  636. // already a vector - just add the new token
  637. ((Vector) current).addElement(value);
  638. } else {
  639. // brand new key - store in keysAsListed to retain order
  640. if (!containsKey(key)) {
  641. keysAsListed.add(key);
  642. }
  643. put(key, value);
  644. }
  645. }
  646. /**
  647. * Set a property, this will replace any previously
  648. * set values. Set values is implicitly a call
  649. * to clearProperty(key), addProperty(key,value).
  650. *
  651. * @param key the key to set
  652. * @param value the value to set
  653. */
  654. public void setProperty(String key, Object value) {
  655. clearProperty(key);
  656. addProperty(key, value);
  657. }
  658. /**
  659. * Save the properties to the given output stream.
  660. * <p>
  661. * The stream is not closed, but it is flushed.
  662. *
  663. * @param output an OutputStream, may be null
  664. * @param header a textual comment to act as a file header
  665. * @throws IOException if an IO error occurs
  666. */
  667. public synchronized void save(OutputStream output, String header) throws IOException {
  668. if (output == null) {
  669. return;
  670. }
  671. PrintWriter theWrtr = new PrintWriter(output);
  672. if (header != null) {
  673. theWrtr.println(header);
  674. }
  675. Enumeration theKeys = keys();
  676. while (theKeys.hasMoreElements()) {
  677. String key = (String) theKeys.nextElement();
  678. Object value = get(key);
  679. if (value != null) {
  680. if (value instanceof String) {
  681. StringBuffer currentOutput = new StringBuffer();
  682. currentOutput.append(key);
  683. currentOutput.append("=");
  684. currentOutput.append(escape((String) value));
  685. theWrtr.println(currentOutput.toString());
  686. } else if (value instanceof Vector) {
  687. Vector values = (Vector) value;
  688. Enumeration valuesEnum = values.elements();
  689. while (valuesEnum.hasMoreElements()) {
  690. String currentElement = (String) valuesEnum.nextElement();
  691. StringBuffer currentOutput = new StringBuffer();
  692. currentOutput.append(key);
  693. currentOutput.append("=");
  694. currentOutput.append(escape(currentElement));
  695. theWrtr.println(currentOutput.toString());
  696. }
  697. }
  698. }
  699. theWrtr.println();
  700. theWrtr.flush();
  701. }
  702. }
  703. /**
  704. * Combines an existing Hashtable with this Hashtable.
  705. * <p>
  706. * Warning: It will overwrite previous entries without warning.
  707. *
  708. * @param props the properties to combine
  709. */
  710. public void combine(ExtendedProperties props) {
  711. for (Iterator it = props.getKeys(); it.hasNext();) {
  712. String key = (String) it.next();
  713. setProperty(key, props.get(key));
  714. }
  715. }
  716. /**
  717. * Clear a property in the configuration.
  718. *
  719. * @param key the property key to remove along with corresponding value
  720. */
  721. public void clearProperty(String key) {
  722. if (containsKey(key)) {
  723. // we also need to rebuild the keysAsListed or else
  724. // things get *very* confusing
  725. for (int i = 0; i < keysAsListed.size(); i++) {
  726. if (( keysAsListed.get(i)).equals(key)) {
  727. keysAsListed.remove(i);
  728. break;
  729. }
  730. }
  731. remove(key);
  732. }
  733. }
  734. /**
  735. * Get the list of the keys contained in the configuration
  736. * repository.
  737. *
  738. * @return an Iterator over the keys
  739. */
  740. public Iterator getKeys() {
  741. return keysAsListed.iterator();
  742. }
  743. /**
  744. * Get the list of the keys contained in the configuration
  745. * repository that match the specified prefix.
  746. *
  747. * @param prefix the prefix to match
  748. * @return an Iterator of keys that match the prefix
  749. */
  750. public Iterator getKeys(String prefix) {
  751. Iterator keys = getKeys();
  752. ArrayList matchingKeys = new ArrayList();
  753. while (keys.hasNext()) {
  754. Object key = keys.next();
  755. if (key instanceof String && ((String) key).startsWith(prefix)) {
  756. matchingKeys.add(key);
  757. }
  758. }
  759. return matchingKeys.iterator();
  760. }
  761. /**
  762. * Create an ExtendedProperties object that is a subset
  763. * of this one. Take into account duplicate keys
  764. * by using the setProperty() in ExtendedProperties.
  765. *
  766. * @param prefix the prefix to get a subset for
  767. * @return a new independent ExtendedProperties
  768. */
  769. public ExtendedProperties subset(String prefix) {
  770. ExtendedProperties c = new ExtendedProperties();
  771. Iterator keys = getKeys();
  772. boolean validSubset = false;
  773. while (keys.hasNext()) {
  774. Object key = keys.next();
  775. if (key instanceof String && ((String) key).startsWith(prefix)) {
  776. if (!validSubset) {
  777. validSubset = true;
  778. }
  779. /*
  780. * Check to make sure that c.subset(prefix) doesn't
  781. * blow up when there is only a single property
  782. * with the key prefix. This is not a useful
  783. * subset but it is a valid subset.
  784. */
  785. String newKey = null;
  786. if (((String) key).length() == prefix.length()) {
  787. newKey = prefix;
  788. } else {
  789. newKey = ((String) key).substring(prefix.length() + 1);
  790. }
  791. /*
  792. * use addPropertyDirect() - this will plug the data as
  793. * is into the Map, but will also do the right thing
  794. * re key accounting
  795. */
  796. c.addPropertyDirect(newKey, get(key));
  797. }
  798. }
  799. if (validSubset) {
  800. return c;
  801. } else {
  802. return null;
  803. }
  804. }
  805. /**
  806. * Display the configuration for debugging purposes to System.out.
  807. */
  808. public void display() {
  809. Iterator i = getKeys();
  810. while (i.hasNext()) {
  811. String key = (String) i.next();
  812. Object value = get(key);
  813. System.out.println(key + " => " + value);
  814. }
  815. }
  816. /**
  817. * Get a string associated with the given configuration key.
  818. *
  819. * @param key The configuration key.
  820. * @return The associated string.
  821. * @throws ClassCastException is thrown if the key maps to an
  822. * object that is not a String.
  823. */
  824. public String getString(String key) {
  825. return getString(key, null);
  826. }
  827. /**
  828. * Get a string associated with the given configuration key.
  829. *
  830. * @param key The configuration key.
  831. * @param defaultValue The default value.
  832. * @return The associated string if key is found,
  833. * default value otherwise.
  834. * @throws ClassCastException is thrown if the key maps to an
  835. * object that is not a String.
  836. */
  837. public String getString(String key, String defaultValue) {
  838. Object value = get(key);
  839. if (value instanceof String) {
  840. return interpolate((String) value);
  841. } else if (value == null) {
  842. if (defaults != null) {
  843. return interpolate(defaults.getString(key, defaultValue));
  844. } else {
  845. return interpolate(defaultValue);
  846. }
  847. } else if (value instanceof Vector) {
  848. return interpolate((String) ((Vector) value).get(0));
  849. } else {
  850. throw new ClassCastException('\'' + key + "' doesn't map to a String object");
  851. }
  852. }
  853. /**
  854. * Get a list of properties associated with the given
  855. * configuration key.
  856. *
  857. * @param key The configuration key.
  858. * @return The associated properties if key is found.
  859. * @throws ClassCastException is thrown if the key maps to an
  860. * object that is not a String/Vector.
  861. * @throws IllegalArgumentException if one of the tokens is
  862. * malformed (does not contain an equals sign).
  863. */
  864. public Properties getProperties(String key) {
  865. return getProperties(key, new Properties());
  866. }
  867. /**
  868. * Get a list of properties associated with the given
  869. * configuration key.
  870. *
  871. * @param key The configuration key.
  872. * @return The associated properties if key is found.
  873. * @throws ClassCastException is thrown if the key maps to an
  874. * object that is not a String/Vector.
  875. * @throws IllegalArgumentException if one of the tokens is
  876. * malformed (does not contain an equals sign).
  877. */
  878. public Properties getProperties(String key, Properties defaults) {
  879. /*
  880. * Grab an array of the tokens for this key.
  881. */
  882. String[] tokens = getStringArray(key);
  883. // Each token is of the form 'key=value'.
  884. Properties props = new Properties(defaults);
  885. for (int i = 0; i < tokens.length; i++) {
  886. String token = tokens[i];
  887. int equalSign = token.indexOf('=');
  888. if (equalSign > 0) {
  889. String pkey = token.substring(0, equalSign).trim();
  890. String pvalue = token.substring(equalSign + 1).trim();
  891. props.put(pkey, pvalue);
  892. } else {
  893. throw new IllegalArgumentException('\'' + token + "' does not contain " + "an equals sign");
  894. }
  895. }
  896. return props;
  897. }
  898. /**
  899. * Get an array of strings associated with the given configuration
  900. * key.
  901. *
  902. * @param key The configuration key.
  903. * @return The associated string array if key is found.
  904. * @throws ClassCastException is thrown if the key maps to an
  905. * object that is not a String/Vector.
  906. */
  907. public String[] getStringArray(String key) {
  908. Object value = get(key);
  909. // What's your vector, Victor?
  910. Vector vector;
  911. if (value instanceof String) {
  912. vector = new Vector(1);
  913. vector.addElement(value);
  914. } else if (value instanceof Vector) {
  915. vector = (Vector) value;
  916. } else if (value == null) {
  917. if (defaults != null) {
  918. return defaults.getStringArray(key);
  919. } else {
  920. return new String[0];
  921. }
  922. } else {
  923. throw new ClassCastException('\'' + key + "' doesn't map to a String/Vector object");
  924. }
  925. String[] tokens = new String[vector.size()];
  926. for (int i = 0; i < tokens.length; i++) {
  927. tokens[i] = (String) vector.elementAt(i);
  928. }
  929. return tokens;
  930. }
  931. /**
  932. * Get a Vector of strings associated with the given configuration
  933. * key.
  934. *
  935. * @param key The configuration key.
  936. * @return The associated Vector.
  937. * @throws ClassCastException is thrown if the key maps to an
  938. * object that is not a Vector.
  939. */
  940. public Vector getVector(String key) {
  941. return getVector(key, null);
  942. }
  943. /**
  944. * Get a Vector of strings associated with the given configuration
  945. * key.
  946. *
  947. * @param key The configuration key.
  948. * @param defaultValue The default value.
  949. * @return The associated Vector.
  950. * @throws ClassCastException is thrown if the key maps to an
  951. * object that is not a Vector.
  952. */
  953. public Vector getVector(String key, Vector defaultValue) {
  954. Object value = get(key);
  955. if (value instanceof Vector) {
  956. return (Vector) value;
  957. } else if (value instanceof String) {
  958. Vector v = new Vector(1);
  959. v.addElement(value);
  960. put(key, v);
  961. return v;
  962. } else if (value == null) {
  963. if (defaults != null) {
  964. return defaults.getVector(key, defaultValue);
  965. } else {
  966. return ((defaultValue == null) ? new Vector() : defaultValue);
  967. }
  968. } else {
  969. throw new ClassCastException('\'' + key + "' doesn't map to a Vector object");
  970. }
  971. }
  972. /**
  973. * Get a boolean associated with the given configuration key.
  974. *
  975. * @param key The configuration key.
  976. * @return The associated boolean.
  977. * @throws NoSuchElementException is thrown if the key doesn't
  978. * map to an existing object.
  979. * @throws ClassCastException is thrown if the key maps to an
  980. * object that is not a Boolean.
  981. */
  982. public boolean getBoolean(String key) {
  983. Boolean b = getBoolean(key, null);
  984. if (b != null) {
  985. return b.booleanValue();
  986. } else {
  987. throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
  988. }
  989. }
  990. /**
  991. * Get a boolean associated with the given configuration key.
  992. *
  993. * @param key The configuration key.
  994. * @param defaultValue The default value.
  995. * @return The associated boolean.
  996. * @throws ClassCastException is thrown if the key maps to an
  997. * object that is not a Boolean.
  998. */
  999. public boolean getBoolean(String key, boolean defaultValue) {
  1000. return getBoolean(key, new Boolean(defaultValue)).booleanValue();
  1001. }
  1002. /**
  1003. * Get a boolean associated with the given configuration key.
  1004. *
  1005. * @param key The configuration key.
  1006. * @param defaultValue The default value.
  1007. * @return The associated boolean if key is found and has valid
  1008. * format, default value otherwise.
  1009. * @throws ClassCastException is thrown if the key maps to an
  1010. * object that is not a Boolean.
  1011. */
  1012. public Boolean getBoolean(String key, Boolean defaultValue) {
  1013. Object value = get(key);
  1014. if (value instanceof Boolean) {
  1015. return (Boolean) value;
  1016. } else if (value instanceof String) {
  1017. String s = testBoolean((String) value);
  1018. Boolean b = new Boolean(s);
  1019. put(key, b);
  1020. return b;
  1021. } else if (value == null) {
  1022. if (defaults != null) {
  1023. return defaults.getBoolean(key, defaultValue);
  1024. } else {
  1025. return defaultValue;
  1026. }
  1027. } else {
  1028. throw new ClassCastException('\'' + key + "' doesn't map to a Boolean object");
  1029. }
  1030. }
  1031. /**
  1032. * Test whether the string represent by value maps to a boolean
  1033. * value or not. We will allow <code>true</code>, <code>on</code>,
  1034. * and <code>yes</code> for a <code>true</code> boolean value, and
  1035. * <code>false</code>, <code>off</code>, and <code>no</code> for
  1036. * <code>false</code> boolean values. Case of value to test for
  1037. * boolean status is ignored.
  1038. *
  1039. * @param value the value to test for boolean state
  1040. * @return <code>true</code> or <code>false</code> if the supplied
  1041. * text maps to a boolean value, or <code>null</code> otherwise.
  1042. */
  1043. public String testBoolean(String value) {
  1044. String s = value.toLowerCase();
  1045. if (s.equals("true") || s.equals("on") || s.equals("yes")) {
  1046. return "true";
  1047. } else if (s.equals("false") || s.equals("off") || s.equals("no")) {
  1048. return "false";
  1049. } else {
  1050. return null;
  1051. }
  1052. }
  1053. /**
  1054. * Get a byte associated with the given configuration key.
  1055. *
  1056. * @param key The configuration key.
  1057. * @return The associated byte.
  1058. * @throws NoSuchElementException is thrown if the key doesn't
  1059. * map to an existing object.
  1060. * @throws ClassCastException is thrown if the key maps to an
  1061. * object that is not a Byte.
  1062. * @throws NumberFormatException is thrown if the value mapped
  1063. * by the key has not a valid number format.
  1064. */
  1065. public byte getByte(String key) {
  1066. Byte b = getByte(key, null);
  1067. if (b != null) {
  1068. return b.byteValue();
  1069. } else {
  1070. throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
  1071. }
  1072. }
  1073. /**
  1074. * Get a byte associated with the given configuration key.
  1075. *
  1076. * @param key The configuration key.
  1077. * @param defaultValue The default value.
  1078. * @return The associated byte.
  1079. * @throws ClassCastException is thrown if the key maps to an
  1080. * object that is not a Byte.
  1081. * @throws NumberFormatException is thrown if the value mapped
  1082. * by the key has not a valid number format.
  1083. */
  1084. public byte getByte(String key, byte defaultValue) {
  1085. return getByte(key, new Byte(defaultValue)).byteValue();
  1086. }
  1087. /**
  1088. * Get a byte associated with the given configuration key.
  1089. *
  1090. * @param key The configuration key.
  1091. * @param defaultValue The default value.
  1092. * @return The associated byte if key is found and has valid
  1093. * format, default value otherwise.
  1094. * @throws ClassCastException is thrown if the key maps to an
  1095. * object that is not a Byte.
  1096. * @throws NumberFormatException is thrown if the value mapped
  1097. * by the key has not a valid number format.
  1098. */
  1099. public Byte getByte(String key, Byte defaultValue) {
  1100. Object value = get(key);
  1101. if (value instanceof Byte) {
  1102. return (Byte) value;
  1103. } else if (value instanceof String) {
  1104. Byte b = new Byte((String) value);
  1105. put(key, b);
  1106. return b;
  1107. } else if (value == null) {
  1108. if (defaults != null) {
  1109. return defaults.getByte(key, defaultValue);
  1110. } else {
  1111. return defaultValue;
  1112. }
  1113. } else {
  1114. throw new ClassCastException('\'' + key + "' doesn't map to a Byte object");
  1115. }
  1116. }
  1117. /**
  1118. * Get a short associated with the given configuration key.
  1119. *
  1120. * @param key The configuration key.
  1121. * @return The associated short.
  1122. * @throws NoSuchElementException is thrown if the key doesn't
  1123. * map to an existing object.
  1124. * @throws ClassCastException is thrown if the key maps to an
  1125. * object that is not a Short.
  1126. * @throws NumberFormatException is thrown if the value mapped
  1127. * by the key has not a valid number format.
  1128. */
  1129. public short getShort(String key) {
  1130. Short s = getShort(key, null);
  1131. if (s != null) {
  1132. return s.shortValue();
  1133. } else {
  1134. throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
  1135. }
  1136. }
  1137. /**
  1138. * Get a short associated with the given configuration key.
  1139. *
  1140. * @param key The configuration key.
  1141. * @param defaultValue The default value.
  1142. * @return The associated short.
  1143. * @throws ClassCastException is thrown if the key maps to an
  1144. * object that is not a Short.
  1145. * @throws NumberFormatException is thrown if the value mapped
  1146. * by the key has not a valid number format.
  1147. */
  1148. public short getShort(String key, short defaultValue) {
  1149. return getShort(key, new Short(defaultValue)).shortValue();
  1150. }
  1151. /**
  1152. * Get a short associated with the given configuration key.
  1153. *
  1154. * @param key The configuration key.
  1155. * @param defaultValue The default value.
  1156. * @return The associated short if key is found and has valid
  1157. * format, default value otherwise.
  1158. * @throws ClassCastException is thrown if the key maps to an
  1159. * object that is not a Short.
  1160. * @throws NumberFormatException is thrown if the value mapped
  1161. * by the key has not a valid number format.
  1162. */
  1163. public Short getShort(String key, Short defaultValue) {
  1164. Object value = get(key);
  1165. if (value instanceof Short) {
  1166. return (Short) value;
  1167. } else if (value instanceof String) {
  1168. Short s = new Short((String) value);
  1169. put(key, s);
  1170. return s;
  1171. } else if (value == null) {
  1172. if (defaults != null) {
  1173. return defaults.getShort(key, defaultValue);
  1174. } else {
  1175. return defaultValue;
  1176. }
  1177. } else {
  1178. throw new ClassCastException('\'' + key + "' doesn't map to a Short object");
  1179. }
  1180. }
  1181. /**
  1182. * The purpose of this method is to get the configuration resource
  1183. * with the given name as an integer.
  1184. *
  1185. * @param name The resource name.
  1186. * @return The value of the resource as an integer.
  1187. */
  1188. public int getInt(String name) {
  1189. return getInteger(name);
  1190. }
  1191. /**
  1192. * The purpose of this method is to get the configuration resource
  1193. * with the given name as an integer, or a default value.
  1194. *
  1195. * @param name The resource name
  1196. * @param def The default value of the resource.
  1197. * @return The value of the resource as an integer.
  1198. */
  1199. public int getInt(String name, int def) {
  1200. return getInteger(name, def);
  1201. }
  1202. /**
  1203. * Get a int associated with the given configuration key.
  1204. *
  1205. * @param key The configuration key.
  1206. * @return The associated int.
  1207. * @throws NoSuchElementException is thrown if the key doesn't
  1208. * map to an existing object.
  1209. * @throws ClassCastException is thrown if the key maps to an
  1210. * object that is not a Integer.
  1211. * @throws NumberFormatException is thrown if the value mapped
  1212. * by the key has not a valid number format.
  1213. */
  1214. public int getInteger(String key) {
  1215. Integer i = getInteger(key, null);
  1216. if (i != null) {
  1217. return i.intValue();
  1218. } else {
  1219. throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
  1220. }
  1221. }
  1222. /**
  1223. * Get a int associated with the given configuration key.
  1224. *
  1225. * @param key The configuration key.
  1226. * @param defaultValue The default value.
  1227. * @return The associated int.
  1228. * @throws ClassCastException is thrown if the key maps to an
  1229. * object that is not a Integer.
  1230. * @throws NumberFormatException is thrown if the value mapped
  1231. * by the key has not a valid number format.
  1232. */
  1233. public int getInteger(String key, int defaultValue) {
  1234. Integer i = getInteger(key, null);
  1235. if (i == null) {
  1236. return defaultValue;
  1237. }
  1238. return i.intValue();
  1239. }
  1240. /**
  1241. * Get a int associated with the given configuration key.
  1242. *
  1243. * @param key The configuration key.
  1244. * @param defaultValue The default value.
  1245. * @return The associated int if key is found and has valid
  1246. * format, default value otherwise.
  1247. * @throws ClassCastException is thrown if the key maps to an
  1248. * object that is not a Integer.
  1249. * @throws NumberFormatException is thrown if the value mapped
  1250. * by the key has not a valid number format.
  1251. */
  1252. public Integer getInteger(String key, Integer defaultValue) {
  1253. Object value = get(key);
  1254. if (value instanceof Integer) {
  1255. return (Integer) value;
  1256. } else if (value instanceof String) {
  1257. Integer i = new Integer((String) value);
  1258. put(key, i);
  1259. return i;
  1260. } else if (value == null) {
  1261. if (defaults != null) {
  1262. return defaults.getInteger(key, defaultValue);
  1263. } else {
  1264. return defaultValue;
  1265. }
  1266. } else {
  1267. throw new ClassCastException('\'' + key + "' doesn't map to a Integer object");
  1268. }
  1269. }
  1270. /**
  1271. * Get a long associated with the given configuration key.
  1272. *
  1273. * @param key The configuration key.
  1274. * @return The associated long.
  1275. * @throws NoSuchElementException is thrown if the key doesn't
  1276. * map to an existing object.
  1277. * @throws ClassCastException is thrown if the key maps to an
  1278. * object that is not a Long.
  1279. * @throws NumberFormatException is thrown if the value mapped
  1280. * by the key has not a valid number format.
  1281. */
  1282. public long getLong(String key) {
  1283. Long l = getLong(key, null);
  1284. if (l != null) {
  1285. return l.longValue();
  1286. } else {
  1287. throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
  1288. }
  1289. }
  1290. /**
  1291. * Get a long associated with the given configuration key.
  1292. *
  1293. * @param key The configuration key.
  1294. * @param defaultValue The default value.
  1295. * @return The associated long.
  1296. * @throws ClassCastException is thrown if the key maps to an
  1297. * object that is not a Long.
  1298. * @throws NumberFormatException is thrown if the value mapped
  1299. * by the key has not a valid number format.
  1300. */
  1301. public long getLong(String key, long defaultValue) {
  1302. return getLong(key, new Long(defaultValue)).longValue();
  1303. }
  1304. /**
  1305. * Get a long associated with the given configuration key.
  1306. *
  1307. * @param key The configuration key.
  1308. * @param defaultValue The default value.
  1309. * @return The associated long if key is found and has valid
  1310. * format, default value otherwise.
  1311. * @throws ClassCastException is thrown if the key maps to an
  1312. * object that is not a Long.
  1313. * @throws NumberFormatException is thrown if the value mapped
  1314. * by the key has not a valid number format.
  1315. */
  1316. public Long getLong(String key, Long defaultValue) {
  1317. Object value = get(key);
  1318. if (value instanceof Long) {
  1319. return (Long) value;
  1320. } else if (value instanceof String) {
  1321. Long l = new Long((String) value);
  1322. put(key, l);
  1323. return l;
  1324. } else if (value == null) {
  1325. if (defaults != null) {
  1326. return defaults.getLong(key, defaultValue);
  1327. } else {
  1328. return defaultValue;
  1329. }
  1330. } else {
  1331. throw new ClassCastException('\'' + key + "' doesn't map to a Long object");
  1332. }
  1333. }
  1334. /**
  1335. * Get a float associated with the given configuration key.
  1336. *
  1337. * @param key The configuration key.
  1338. * @return The associated float.
  1339. * @throws NoSuchElementException is thrown if the key doesn't
  1340. * map to an existing object.
  1341. * @throws ClassCastException is thrown if the key maps to an
  1342. * object that is not a Float.
  1343. * @throws NumberFormatException is thrown if the value mapped
  1344. * by the key has not a valid number format.
  1345. */
  1346. public float getFloat(String key) {
  1347. Float f = getFloat(key, null);
  1348. if (f != null) {
  1349. return f.floatValue();
  1350. } else {
  1351. throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
  1352. }
  1353. }
  1354. /**
  1355. * Get a float associated with the given configuration key.
  1356. *
  1357. * @param key The configuration key.
  1358. * @param defaultValue The default value.
  1359. * @return The associated float.
  1360. * @throws ClassCastException is thrown if the key maps to an
  1361. * object that is not a Float.
  1362. * @throws NumberFormatException is thrown if the value mapped
  1363. * by the key has not a valid number format.
  1364. */
  1365. public float getFloat(String key, float defaultValue) {
  1366. return getFloat(key, new Float(defaultValue)).floatValue();
  1367. }
  1368. /**
  1369. * Get a float associated with the given configuration key.
  1370. *
  1371. * @param key The configuration key.
  1372. * @param defaultValue The default value.
  1373. * @return The associated float if key is found and has valid
  1374. * format, default value otherwise.
  1375. * @throws ClassCastException is thrown if the key maps to an
  1376. * object that is not a Float.
  1377. * @throws NumberFormatException is thrown if the value mapped
  1378. * by the key has not a valid number format.
  1379. */
  1380. public Float getFloat(String key, Float defaultValue) {
  1381. Object value = get(key);
  1382. if (value instanceof Float) {
  1383. return (Float) value;
  1384. } else if (value instanceof String) {
  1385. Float f = new Float((String) value);
  1386. put(key, f);
  1387. return f;
  1388. } else if (value == null) {
  1389. if (defaults != null) {
  1390. return defaults.getFloat(key, defaultValue);
  1391. } else {
  1392. return defaultValue;
  1393. }
  1394. } else {
  1395. throw new ClassCastException('\'' + key + "' doesn't map to a Float object");
  1396. }
  1397. }
  1398. /**
  1399. * Get a double associated with the given configuration key.
  1400. *
  1401. * @param key The configuration key.
  1402. * @return The associated double.
  1403. * @throws NoSuchElementException is thrown if the key doesn't
  1404. * map to an existing object.
  1405. * @throws ClassCastException is thrown if the key maps to an
  1406. * object that is not a Double.
  1407. * @throws NumberFormatException is thrown if the value mapped
  1408. * by the key has not a valid number format.
  1409. */
  1410. public double getDouble(String key) {
  1411. Double d = getDouble(key, null);
  1412. if (d != null) {
  1413. return d.doubleValue();
  1414. } else {
  1415. throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
  1416. }
  1417. }
  1418. /**
  1419. * Get a double associated with the given configuration key.
  1420. *
  1421. * @param key The configuration key.
  1422. * @param defaultValue The default value.
  1423. * @return The associated double.
  1424. * @throws ClassCastException is thrown if the key maps to an
  1425. * object that is not a Double.
  1426. * @throws NumberFormatException is thrown if the value mapped
  1427. * by the key has not a valid number format.
  1428. */
  1429. public double getDouble(String key, double defaultValue) {
  1430. return getDouble(key, new Double(defaultValue)).doubleValue();
  1431. }
  1432. /**
  1433. * Get a double associated with the given configuration key.
  1434. *
  1435. * @param key The configuration key.
  1436. * @param defaultValue The default value.
  1437. * @return The associated double if key is found and has valid
  1438. * format, default value otherwise.
  1439. * @throws ClassCastException is thrown if the key maps to an
  1440. * object that is not a Double.
  1441. * @throws NumberFormatException is thrown if the value mapped
  1442. * by the key has not a valid number format.
  1443. */
  1444. public Double getDouble(String key, Double defaultValue) {
  1445. Object value = get(key);
  1446. if (value instanceof Double) {
  1447. return (Double) value;
  1448. } else if (value instanceof String) {
  1449. Double d = new Double((String) value);
  1450. put(key, d);
  1451. return d;
  1452. } else if (value == null) {
  1453. if (defaults != null) {
  1454. return defaults.getDouble(key, defaultValue);
  1455. } else {
  1456. return defaultValue;
  1457. }
  1458. } else {
  1459. throw new ClassCastException('\'' + key + "' doesn't map to a Double object");
  1460. }
  1461. }
  1462. /**
  1463. * Convert a standard properties class into a configuration class.
  1464. *
  1465. * @param props the properties object to convert
  1466. * @return new ExtendedProperties created from props
  1467. */
  1468. public static ExtendedProperties convertProperties(Properties props) {
  1469. ExtendedProperties c = new ExtendedProperties();
  1470. for (Enumeration e = props.keys(); e.hasMoreElements();) {
  1471. String s = (String) e.nextElement();
  1472. c.setProperty(s, props.getProperty(s));
  1473. }
  1474. return c;
  1475. }
  1476. }