Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractConfiguration |
|
| 2.7058823529411766;2,706 | ||||
AbstractConfiguration$1 |
|
| 2.7058823529411766;2,706 | ||||
AbstractConfiguration$2 |
|
| 2.7058823529411766;2,706 | ||||
AbstractConfiguration$3 |
|
| 2.7058823529411766;2,706 |
1 | /* | |
2 | * Licensed to the Apache Software Foundation (ASF) under one or more | |
3 | * contributor license agreements. See the NOTICE file distributed with | |
4 | * this work for additional information regarding copyright ownership. | |
5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | |
6 | * (the "License"); you may not use this file except in compliance with | |
7 | * the License. You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | ||
18 | package org.apache.commons.configuration; | |
19 | ||
20 | import java.lang.reflect.Array; | |
21 | import java.math.BigDecimal; | |
22 | import java.math.BigInteger; | |
23 | import java.util.ArrayList; | |
24 | import java.util.Arrays; | |
25 | import java.util.Collection; | |
26 | import java.util.Iterator; | |
27 | import java.util.List; | |
28 | import java.util.NoSuchElementException; | |
29 | import java.util.Properties; | |
30 | ||
31 | import org.apache.commons.collections.Predicate; | |
32 | import org.apache.commons.collections.iterators.FilterIterator; | |
33 | import org.apache.commons.configuration.event.ConfigurationErrorEvent; | |
34 | import org.apache.commons.configuration.event.ConfigurationErrorListener; | |
35 | import org.apache.commons.configuration.event.EventSource; | |
36 | import org.apache.commons.configuration.interpol.ConfigurationInterpolator; | |
37 | import org.apache.commons.lang.BooleanUtils; | |
38 | import org.apache.commons.lang.text.StrLookup; | |
39 | import org.apache.commons.lang.text.StrSubstitutor; | |
40 | import org.apache.commons.logging.Log; | |
41 | import org.apache.commons.logging.impl.NoOpLog; | |
42 | ||
43 | /** | |
44 | * <p>Abstract configuration class. Provides basic functionality but does not | |
45 | * store any data.</p> | |
46 | * <p>If you want to write your own Configuration class then you should | |
47 | * implement only abstract methods from this class. A lot of functionality | |
48 | * needed by typical implementations of the <code>Configuration</code> | |
49 | * interface is already provided by this base class. Following is a list of | |
50 | * features implemented here: | |
51 | * <ul><li>Data conversion support. The various data types required by the | |
52 | * <code>Configuration</code> interface are already handled by this base class. | |
53 | * A concrete sub class only needs to provide a generic <code>getProperty()</code> | |
54 | * method.</li> | |
55 | * <li>Support for variable interpolation. Property values containing special | |
56 | * variable tokens (like <code>${var}</code>) will be replaced by their | |
57 | * corresponding values.</li> | |
58 | * <li>Support for string lists. The values of properties to be added to this | |
59 | * configuration are checked whether they contain a list delimiter character. If | |
60 | * this is the case and if list splitting is enabled, the string is split and | |
61 | * multiple values are added for this property. (With the | |
62 | * <code>setListDelimiter()</code> method the delimiter character can be | |
63 | * specified; per default a comma is used. The | |
64 | * <code>setDelimiterParsingDisabled()</code> method can be used to disable | |
65 | * list splitting completely.)</li> | |
66 | * <li>Allows to specify how missing properties are treated. Per default the | |
67 | * get methods returning an object will return <b>null</b> if the searched | |
68 | * property key is not found (and no default value is provided). With the | |
69 | * <code>setThrowExceptionOnMissing()</code> method this behavior can be | |
70 | * changed to throw an exception when a requested property cannot be found.</li> | |
71 | * <li>Basic event support. Whenever this configuration is modified registered | |
72 | * event listeners are notified. Refer to the various <code>EVENT_XXX</code> | |
73 | * constants to get an impression about which event types are supported.</li> | |
74 | * </ul></p> | |
75 | * | |
76 | * @author <a href="mailto:ksh@scand.com">Konstantin Shaposhnikov </a> | |
77 | * @author Oliver Heger | |
78 | * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen </a> | |
79 | * @version $Id: AbstractConfiguration.java 652316 2008-04-30 10:59:58Z ebourg $ | |
80 | */ | |
81 | public abstract class AbstractConfiguration extends EventSource implements Configuration | |
82 | { | |
83 | /** | |
84 | * Constant for the add property event type. | |
85 | * @since 1.3 | |
86 | */ | |
87 | public static final int EVENT_ADD_PROPERTY = 1; | |
88 | ||
89 | /** | |
90 | * Constant for the clear property event type. | |
91 | * @since 1.3 | |
92 | */ | |
93 | public static final int EVENT_CLEAR_PROPERTY = 2; | |
94 | ||
95 | /** | |
96 | * Constant for the set property event type. | |
97 | * @since 1.3 | |
98 | */ | |
99 | public static final int EVENT_SET_PROPERTY = 3; | |
100 | ||
101 | /** | |
102 | * Constant for the clear configuration event type. | |
103 | * @since 1.3 | |
104 | */ | |
105 | public static final int EVENT_CLEAR = 4; | |
106 | ||
107 | /** | |
108 | * Constant for the get property event type. This event type is used for | |
109 | * error events. | |
110 | * @since 1.4 | |
111 | */ | |
112 | public static final int EVENT_READ_PROPERTY = 5; | |
113 | ||
114 | /** start token */ | |
115 | protected static final String START_TOKEN = "${"; | |
116 | ||
117 | /** end token */ | |
118 | protected static final String END_TOKEN = "}"; | |
119 | ||
120 | /** | |
121 | * Constant for the disabled list delimiter. This character is passed to the | |
122 | * list parsing methods if delimiter parsing is disabled. So this character | |
123 | * should not occur in string property values. | |
124 | */ | |
125 | private static final char DISABLED_DELIMITER = '\0'; | |
126 | ||
127 | /** The default value for listDelimiter */ | |
128 | 66 | private static char defaultListDelimiter = ','; |
129 | ||
130 | /** Delimiter used to convert single values to lists */ | |
131 | 3725 | private char listDelimiter = defaultListDelimiter; |
132 | ||
133 | /** | |
134 | * When set to true the given configuration delimiter will not be used | |
135 | * while parsing for this configuration. | |
136 | */ | |
137 | private boolean delimiterParsingDisabled; | |
138 | ||
139 | /** | |
140 | * Whether the configuration should throw NoSuchElementExceptions or simply | |
141 | * return null when a property does not exist. Defaults to return null. | |
142 | */ | |
143 | private boolean throwExceptionOnMissing; | |
144 | ||
145 | /** Stores a reference to the object that handles variable interpolation.*/ | |
146 | private StrSubstitutor substitutor; | |
147 | ||
148 | /** Stores the logger.*/ | |
149 | private Log log; | |
150 | ||
151 | /** | |
152 | * Creates a new instance of <code>AbstractConfiguration</code>. | |
153 | */ | |
154 | public AbstractConfiguration() | |
155 | 3725 | { |
156 | 3725 | setLogger(null); |
157 | 3725 | } |
158 | ||
159 | /** | |
160 | * For configurations extending AbstractConfiguration, allow them to change | |
161 | * the listDelimiter from the default comma (","). This value will be used | |
162 | * only when creating new configurations. Those already created will not be | |
163 | * affected by this change | |
164 | * | |
165 | * @param delimiter The new listDelimiter | |
166 | */ | |
167 | public static void setDefaultListDelimiter(char delimiter) | |
168 | { | |
169 | 2 | AbstractConfiguration.defaultListDelimiter = delimiter; |
170 | 2 | } |
171 | ||
172 | /** | |
173 | * Sets the default list delimiter. | |
174 | * | |
175 | * @param delimiter the delimiter character | |
176 | * @deprecated Use AbstractConfiguration.setDefaultListDelimiter(char) | |
177 | * instead | |
178 | */ | |
179 | public static void setDelimiter(char delimiter) | |
180 | { | |
181 | 0 | setDefaultListDelimiter(delimiter); |
182 | 0 | } |
183 | ||
184 | /** | |
185 | * Retrieve the current delimiter. By default this is a comma (","). | |
186 | * | |
187 | * @return The delimiter in use | |
188 | */ | |
189 | public static char getDefaultListDelimiter() | |
190 | { | |
191 | 63 | return AbstractConfiguration.defaultListDelimiter; |
192 | } | |
193 | ||
194 | /** | |
195 | * Returns the default list delimiter. | |
196 | * | |
197 | * @return the default list delimiter | |
198 | * @deprecated Use AbstractConfiguration.getDefaultListDelimiter() instead | |
199 | */ | |
200 | public static char getDelimiter() | |
201 | { | |
202 | 0 | return getDefaultListDelimiter(); |
203 | } | |
204 | ||
205 | /** | |
206 | * Change the list delimiter for this configuration. | |
207 | * | |
208 | * Note: this change will only be effective for new parsings. If you | |
209 | * want it to take effect for all loaded properties use the no arg constructor | |
210 | * and call this method before setting the source. | |
211 | * | |
212 | * @param listDelimiter The new listDelimiter | |
213 | */ | |
214 | public void setListDelimiter(char listDelimiter) | |
215 | { | |
216 | 498 | this.listDelimiter = listDelimiter; |
217 | 498 | } |
218 | ||
219 | /** | |
220 | * Retrieve the delimiter for this configuration. The default | |
221 | * is the value of defaultListDelimiter. | |
222 | * | |
223 | * @return The listDelimiter in use | |
224 | */ | |
225 | public char getListDelimiter() | |
226 | { | |
227 | 167698 | return listDelimiter; |
228 | } | |
229 | ||
230 | /** | |
231 | * Determine if this configuration is using delimiters when parsing | |
232 | * property values to convert them to lists of values. Defaults to false | |
233 | * @return true if delimiters are not being used | |
234 | */ | |
235 | public boolean isDelimiterParsingDisabled() | |
236 | { | |
237 | 159850 | return delimiterParsingDisabled; |
238 | } | |
239 | ||
240 | /** | |
241 | * Set whether this configuration should use delimiters when parsing | |
242 | * property values to convert them to lists of values. By default delimiter | |
243 | * parsing is enabled | |
244 | * | |
245 | * Note: this change will only be effective for new parsings. If you | |
246 | * want it to take effect for all loaded properties use the no arg constructor | |
247 | * and call this method before setting source. | |
248 | * @param delimiterParsingDisabled a flag whether delimiter parsing should | |
249 | * be disabled | |
250 | */ | |
251 | public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled) | |
252 | { | |
253 | 668 | this.delimiterParsingDisabled = delimiterParsingDisabled; |
254 | 668 | } |
255 | ||
256 | /** | |
257 | * Allows to set the <code>throwExceptionOnMissing</code> flag. This | |
258 | * flag controls the behavior of property getter methods that return | |
259 | * objects if the requested property is missing. If the flag is set to | |
260 | * <b>false</b> (which is the default value), these methods will return | |
261 | * <b>null</b>. If set to <b>true</b>, they will throw a | |
262 | * <code>NoSuchElementException</code> exception. Note that getter methods | |
263 | * for primitive data types are not affected by this flag. | |
264 | * | |
265 | * @param throwExceptionOnMissing The new value for the property | |
266 | */ | |
267 | public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing) | |
268 | { | |
269 | 968 | this.throwExceptionOnMissing = throwExceptionOnMissing; |
270 | 968 | } |
271 | ||
272 | /** | |
273 | * Returns true if missing values throw Exceptions. | |
274 | * | |
275 | * @return true if missing values throw Exceptions | |
276 | */ | |
277 | public boolean isThrowExceptionOnMissing() | |
278 | { | |
279 | 995 | return throwExceptionOnMissing; |
280 | } | |
281 | ||
282 | /** | |
283 | * Returns the object that is responsible for variable interpolation. | |
284 | * | |
285 | * @return the object responsible for variable interpolation | |
286 | * @since 1.4 | |
287 | */ | |
288 | public synchronized StrSubstitutor getSubstitutor() | |
289 | { | |
290 | 8653 | if (substitutor == null) |
291 | { | |
292 | 721 | substitutor = new StrSubstitutor(createInterpolator()); |
293 | } | |
294 | 8653 | return substitutor; |
295 | } | |
296 | ||
297 | /** | |
298 | * Returns the <code>ConfigurationInterpolator</code> object that manages | |
299 | * the lookup objects for resolving variables. <em>Note:</em> If this | |
300 | * object is manipulated (e.g. new lookup objects added), synchronisation | |
301 | * has to be manually ensured. Because | |
302 | * <code>ConfigurationInterpolator</code> is not thread-safe concurrent | |
303 | * access to properties of this configuration instance (which causes the | |
304 | * interpolator to be invoked) may cause race conditions. | |
305 | * | |
306 | * @return the <code>ConfigurationInterpolator</code> associated with this | |
307 | * configuration | |
308 | * @since 1.4 | |
309 | */ | |
310 | public ConfigurationInterpolator getInterpolator() | |
311 | { | |
312 | 3 | return (ConfigurationInterpolator) getSubstitutor() |
313 | .getVariableResolver(); | |
314 | } | |
315 | ||
316 | /** | |
317 | * Creates the interpolator object that is responsible for variable | |
318 | * interpolation. This method is invoked on first access of the | |
319 | * interpolation features. It creates a new instance of | |
320 | * <code>ConfigurationInterpolator</code> and sets the default lookup | |
321 | * object to an implementation that queries this configuration. | |
322 | * | |
323 | * @return the newly created interpolator object | |
324 | * @since 1.4 | |
325 | */ | |
326 | protected ConfigurationInterpolator createInterpolator() | |
327 | { | |
328 | 721 | ConfigurationInterpolator interpol = new ConfigurationInterpolator(); |
329 | 721 | interpol.setDefaultLookup(new StrLookup() |
330 | { | |
331 | 721 | public String lookup(String var) |
332 | { | |
333 | 2377 | Object prop = resolveContainerStore(var); |
334 | 2377 | return (prop != null) ? prop.toString() : null; |
335 | } | |
336 | }); | |
337 | 721 | return interpol; |
338 | } | |
339 | ||
340 | /** | |
341 | * Returns the logger used by this configuration object. | |
342 | * | |
343 | * @return the logger | |
344 | * @since 1.4 | |
345 | */ | |
346 | public Log getLogger() | |
347 | { | |
348 | 2127 | return log; |
349 | } | |
350 | ||
351 | /** | |
352 | * Allows to set the logger to be used by this configuration object. This | |
353 | * method makes it possible for clients to exactly control logging behavior. | |
354 | * Per default a logger is set that will ignore all log messages. Derived | |
355 | * classes that want to enable logging should call this method during their | |
356 | * initialization with the logger to be used. | |
357 | * | |
358 | * @param log the new logger | |
359 | * @since 1.4 | |
360 | */ | |
361 | public void setLogger(Log log) | |
362 | { | |
363 | 5218 | this.log = (log != null) ? log : new NoOpLog(); |
364 | 5218 | } |
365 | ||
366 | /** | |
367 | * Adds a special | |
368 | * <code>{@link org.apache.commons.configuration.event.ConfigurationErrorListener}</code> | |
369 | * object to this configuration that will log all internal errors. This | |
370 | * method is intended to be used by certain derived classes, for which it is | |
371 | * known that they can fail on property access (e.g. | |
372 | * <code>DatabaseConfiguration</code>). | |
373 | * | |
374 | * @since 1.4 | |
375 | */ | |
376 | public void addErrorLogListener() | |
377 | { | |
378 | 1487 | addErrorListener(new ConfigurationErrorListener() |
379 | { | |
380 | 1487 | public void configurationError(ConfigurationErrorEvent event) |
381 | { | |
382 | 0 | getLogger().warn("Internal error", event.getCause()); |
383 | 0 | } |
384 | }); | |
385 | 1487 | } |
386 | ||
387 | public void addProperty(String key, Object value) | |
388 | { | |
389 | 131929 | fireEvent(EVENT_ADD_PROPERTY, key, value, true); |
390 | 131929 | addPropertyValues(key, value, |
391 | isDelimiterParsingDisabled() ? DISABLED_DELIMITER | |
392 | : getListDelimiter()); | |
393 | 131926 | fireEvent(EVENT_ADD_PROPERTY, key, value, false); |
394 | 131926 | } |
395 | ||
396 | /** | |
397 | * Adds a key/value pair to the Configuration. Override this method to | |
398 | * provide write access to underlying Configuration store. | |
399 | * | |
400 | * @param key key to use for mapping | |
401 | * @param value object to store | |
402 | */ | |
403 | protected abstract void addPropertyDirect(String key, Object value); | |
404 | ||
405 | /** | |
406 | * Adds the specified value for the given property. This method supports | |
407 | * single values and containers (e.g. collections or arrays) as well. In the | |
408 | * latter case, <code>addPropertyDirect()</code> will be called for each | |
409 | * element. | |
410 | * | |
411 | * @param key the property key | |
412 | * @param value the value object | |
413 | * @param delimiter the list delimiter character | |
414 | */ | |
415 | private void addPropertyValues(String key, Object value, char delimiter) | |
416 | { | |
417 | 134445 | Iterator it = PropertyConverter.toIterator(value, delimiter); |
418 | 278427 | while (it.hasNext()) |
419 | { | |
420 | 143985 | addPropertyDirect(key, it.next()); |
421 | } | |
422 | 134442 | } |
423 | ||
424 | /** | |
425 | * interpolate key names to handle ${key} stuff | |
426 | * | |
427 | * @param base string to interpolate | |
428 | * | |
429 | * @return returns the key name with the ${key} substituted | |
430 | */ | |
431 | protected String interpolate(String base) | |
432 | { | |
433 | 5550 | Object result = interpolate((Object) base); |
434 | 5547 | return (result == null) ? null : result.toString(); |
435 | } | |
436 | ||
437 | /** | |
438 | * Returns the interpolated value. Non String values are returned without change. | |
439 | * | |
440 | * @param value the value to interpolate | |
441 | * | |
442 | * @return returns the value with variables substituted | |
443 | */ | |
444 | protected Object interpolate(Object value) | |
445 | { | |
446 | 9514 | return PropertyConverter.interpolate(value, this); |
447 | } | |
448 | ||
449 | /** | |
450 | * Recursive handler for multple levels of interpolation. | |
451 | * | |
452 | * When called the first time, priorVariables should be null. | |
453 | * | |
454 | * @param base string with the ${key} variables | |
455 | * @param priorVariables serves two purposes: to allow checking for loops, | |
456 | * and creating a meaningful exception message should a loop occur. It's | |
457 | * 0'th element will be set to the value of base from the first call. All | |
458 | * subsequent interpolated variables are added afterward. | |
459 | * | |
460 | * @return the string with the interpolation taken care of | |
461 | * @deprecated Interpolation is now handled by | |
462 | * <code>{@link PropertyConverter}</code>; this method will no longer be | |
463 | * called | |
464 | */ | |
465 | protected String interpolateHelper(String base, List priorVariables) | |
466 | { | |
467 | 0 | return base; // just a dummy implementation |
468 | } | |
469 | ||
470 | public Configuration subset(String prefix) | |
471 | { | |
472 | 45 | return new SubsetConfiguration(this, prefix, "."); |
473 | } | |
474 | ||
475 | public void setProperty(String key, Object value) | |
476 | { | |
477 | 837 | fireEvent(EVENT_SET_PROPERTY, key, value, true); |
478 | 837 | setDetailEvents(false); |
479 | try | |
480 | { | |
481 | 837 | clearProperty(key); |
482 | 836 | addProperty(key, value); |
483 | } | |
484 | finally | |
485 | { | |
486 | 837 | setDetailEvents(true); |
487 | 836 | } |
488 | 836 | fireEvent(EVENT_SET_PROPERTY, key, value, false); |
489 | 836 | } |
490 | ||
491 | /** | |
492 | * Removes the specified property from this configuration. This | |
493 | * implementation performs some preparations and then delegates to | |
494 | * <code>clearPropertyDirect()</code>, which will do the real work. | |
495 | * | |
496 | * @param key the key to be removed | |
497 | */ | |
498 | public void clearProperty(String key) | |
499 | { | |
500 | 958 | fireEvent(EVENT_CLEAR_PROPERTY, key, null, true); |
501 | 958 | clearPropertyDirect(key); |
502 | 958 | fireEvent(EVENT_CLEAR_PROPERTY, key, null, false); |
503 | 958 | } |
504 | ||
505 | /** | |
506 | * Removes the specified property from this configuration. This method is | |
507 | * called by <code>clearProperty()</code> after it has done some | |
508 | * preparations. It should be overriden in sub classes. This base | |
509 | * implementation is just left empty. | |
510 | * | |
511 | * @param key the key to be removed | |
512 | */ | |
513 | protected void clearPropertyDirect(String key) | |
514 | { | |
515 | // override in sub classes | |
516 | 0 | } |
517 | ||
518 | public void clear() | |
519 | { | |
520 | 140 | fireEvent(EVENT_CLEAR, null, null, true); |
521 | 140 | setDetailEvents(false); |
522 | 140 | boolean useIterator = true; |
523 | try | |
524 | { | |
525 | 140 | Iterator it = getKeys(); |
526 | 4493 | while (it.hasNext()) |
527 | { | |
528 | 4353 | String key = (String) it.next(); |
529 | 4353 | if (useIterator) |
530 | { | |
531 | try | |
532 | { | |
533 | 140 | it.remove(); |
534 | } | |
535 | 1 | catch (UnsupportedOperationException usoex) |
536 | { | |
537 | 1 | useIterator = false; |
538 | 139 | } |
539 | } | |
540 | ||
541 | 4353 | if (useIterator && containsKey(key)) |
542 | { | |
543 | 136 | useIterator = false; |
544 | } | |
545 | ||
546 | 4353 | if (!useIterator) |
547 | { | |
548 | // workaround for Iterators that do not remove the property | |
549 | // on calling remove() or do not support remove() at all | |
550 | 4350 | clearProperty(key); |
551 | } | |
552 | } | |
553 | } | |
554 | finally | |
555 | { | |
556 | 140 | setDetailEvents(true); |
557 | 140 | } |
558 | 140 | fireEvent(EVENT_CLEAR, null, null, false); |
559 | 140 | } |
560 | ||
561 | public Iterator getKeys(final String prefix) | |
562 | { | |
563 | 41 | return new FilterIterator(getKeys(), new Predicate() |
564 | { | |
565 | 41 | public boolean evaluate(Object obj) |
566 | { | |
567 | 466 | String key = (String) obj; |
568 | 466 | return key.startsWith(prefix + ".") || key.equals(prefix); |
569 | } | |
570 | }); | |
571 | } | |
572 | ||
573 | public Properties getProperties(String key) | |
574 | { | |
575 | 4 | return getProperties(key, null); |
576 | } | |
577 | ||
578 | /** | |
579 | * Get a list of properties associated with the given configuration key. | |
580 | * | |
581 | * @param key The configuration key. | |
582 | * @param defaults Any default values for the returned | |
583 | * <code>Properties</code> object. Ignored if <code>null</code>. | |
584 | * | |
585 | * @return The associated properties if key is found. | |
586 | * | |
587 | * @throws ConversionException is thrown if the key maps to an object that | |
588 | * is not a String/List of Strings. | |
589 | * | |
590 | * @throws IllegalArgumentException if one of the tokens is malformed (does | |
591 | * not contain an equals sign). | |
592 | */ | |
593 | public Properties getProperties(String key, Properties defaults) | |
594 | { | |
595 | /* | |
596 | * Grab an array of the tokens for this key. | |
597 | */ | |
598 | 4 | String[] tokens = getStringArray(key); |
599 | ||
600 | /* | |
601 | * Each token is of the form 'key=value'. | |
602 | */ | |
603 | 4 | Properties props = defaults == null ? new Properties() : new Properties(defaults); |
604 | 10 | for (int i = 0; i < tokens.length; i++) |
605 | { | |
606 | 8 | String token = tokens[i]; |
607 | 8 | int equalSign = token.indexOf('='); |
608 | 8 | if (equalSign > 0) |
609 | { | |
610 | 6 | String pkey = token.substring(0, equalSign).trim(); |
611 | 6 | String pvalue = token.substring(equalSign + 1).trim(); |
612 | 6 | props.put(pkey, pvalue); |
613 | } | |
614 | 2 | else if (tokens.length == 1 && "".equals(token)) |
615 | { | |
616 | // Semantically equivalent to an empty Properties | |
617 | // object. | |
618 | 2 | break; |
619 | } | |
620 | else | |
621 | { | |
622 | 0 | throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign"); |
623 | } | |
624 | } | |
625 | 4 | return props; |
626 | } | |
627 | ||
628 | /** | |
629 | * {@inheritDoc} | |
630 | * @see PropertyConverter#toBoolean(Object) | |
631 | */ | |
632 | public boolean getBoolean(String key) | |
633 | { | |
634 | 1071 | Boolean b = getBoolean(key, null); |
635 | 1069 | if (b != null) |
636 | { | |
637 | 1067 | return b.booleanValue(); |
638 | } | |
639 | else | |
640 | { | |
641 | 2 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
642 | } | |
643 | } | |
644 | ||
645 | /** | |
646 | * {@inheritDoc} | |
647 | * @see PropertyConverter#toBoolean(Object) | |
648 | */ | |
649 | public boolean getBoolean(String key, boolean defaultValue) | |
650 | { | |
651 | 24 | return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue)).booleanValue(); |
652 | } | |
653 | ||
654 | /** | |
655 | * Obtains the value of the specified key and tries to convert it into a | |
656 | * <code>Boolean</code> object. If the property has no value, the passed | |
657 | * in default value will be used. | |
658 | * | |
659 | * @param key the key of the property | |
660 | * @param defaultValue the default value | |
661 | * @return the value of this key converted to a <code>Boolean</code> | |
662 | * @throws ConversionException if the value cannot be converted to a | |
663 | * <code>Boolean</code> | |
664 | * @see PropertyConverter#toBoolean(Object) | |
665 | */ | |
666 | public Boolean getBoolean(String key, Boolean defaultValue) | |
667 | { | |
668 | 1131 | Object value = resolveContainerStore(key); |
669 | ||
670 | 1131 | if (value == null) |
671 | { | |
672 | 37 | return defaultValue; |
673 | } | |
674 | else | |
675 | { | |
676 | try | |
677 | { | |
678 | 1094 | return PropertyConverter.toBoolean(interpolate(value)); |
679 | } | |
680 | 3 | catch (ConversionException e) |
681 | { | |
682 | 3 | throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e); |
683 | } | |
684 | } | |
685 | } | |
686 | ||
687 | public byte getByte(String key) | |
688 | { | |
689 | 15 | Byte b = getByte(key, null); |
690 | 13 | if (b != null) |
691 | { | |
692 | 11 | return b.byteValue(); |
693 | } | |
694 | else | |
695 | { | |
696 | 2 | throw new NoSuchElementException('\'' + key + " doesn't map to an existing object"); |
697 | } | |
698 | } | |
699 | ||
700 | public byte getByte(String key, byte defaultValue) | |
701 | { | |
702 | 4 | return getByte(key, new Byte(defaultValue)).byteValue(); |
703 | } | |
704 | ||
705 | public Byte getByte(String key, Byte defaultValue) | |
706 | { | |
707 | 22 | Object value = resolveContainerStore(key); |
708 | ||
709 | 22 | if (value == null) |
710 | { | |
711 | 4 | return defaultValue; |
712 | } | |
713 | else | |
714 | { | |
715 | try | |
716 | { | |
717 | 18 | return PropertyConverter.toByte(interpolate(value)); |
718 | } | |
719 | 2 | catch (ConversionException e) |
720 | { | |
721 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e); |
722 | } | |
723 | } | |
724 | } | |
725 | ||
726 | public double getDouble(String key) | |
727 | { | |
728 | 22 | Double d = getDouble(key, null); |
729 | 20 | if (d != null) |
730 | { | |
731 | 18 | return d.doubleValue(); |
732 | } | |
733 | else | |
734 | { | |
735 | 2 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
736 | } | |
737 | } | |
738 | ||
739 | public double getDouble(String key, double defaultValue) | |
740 | { | |
741 | 11 | return getDouble(key, new Double(defaultValue)).doubleValue(); |
742 | } | |
743 | ||
744 | public Double getDouble(String key, Double defaultValue) | |
745 | { | |
746 | 36 | Object value = resolveContainerStore(key); |
747 | ||
748 | 36 | if (value == null) |
749 | { | |
750 | 11 | return defaultValue; |
751 | } | |
752 | else | |
753 | { | |
754 | try | |
755 | { | |
756 | 25 | return PropertyConverter.toDouble(interpolate(value)); |
757 | } | |
758 | 2 | catch (ConversionException e) |
759 | { | |
760 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a Double object", e); |
761 | } | |
762 | } | |
763 | } | |
764 | ||
765 | public float getFloat(String key) | |
766 | { | |
767 | 14 | Float f = getFloat(key, null); |
768 | 12 | if (f != null) |
769 | { | |
770 | 10 | return f.floatValue(); |
771 | } | |
772 | else | |
773 | { | |
774 | 2 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
775 | } | |
776 | } | |
777 | ||
778 | public float getFloat(String key, float defaultValue) | |
779 | { | |
780 | 7 | return getFloat(key, new Float(defaultValue)).floatValue(); |
781 | } | |
782 | ||
783 | public Float getFloat(String key, Float defaultValue) | |
784 | { | |
785 | 24 | Object value = resolveContainerStore(key); |
786 | ||
787 | 24 | if (value == null) |
788 | { | |
789 | 7 | return defaultValue; |
790 | } | |
791 | else | |
792 | { | |
793 | try | |
794 | { | |
795 | 17 | return PropertyConverter.toFloat(interpolate(value)); |
796 | } | |
797 | 2 | catch (ConversionException e) |
798 | { | |
799 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a Float object", e); |
800 | } | |
801 | } | |
802 | } | |
803 | ||
804 | public int getInt(String key) | |
805 | { | |
806 | 1076 | Integer i = getInteger(key, null); |
807 | 1076 | if (i != null) |
808 | { | |
809 | 1076 | return i.intValue(); |
810 | } | |
811 | else | |
812 | { | |
813 | 0 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
814 | } | |
815 | } | |
816 | ||
817 | public int getInt(String key, int defaultValue) | |
818 | { | |
819 | 3 | Integer i = getInteger(key, null); |
820 | ||
821 | 3 | if (i == null) |
822 | { | |
823 | 3 | return defaultValue; |
824 | } | |
825 | ||
826 | 0 | return i.intValue(); |
827 | } | |
828 | ||
829 | public Integer getInteger(String key, Integer defaultValue) | |
830 | { | |
831 | 1080 | Object value = resolveContainerStore(key); |
832 | ||
833 | 1080 | if (value == null) |
834 | { | |
835 | 3 | return defaultValue; |
836 | } | |
837 | else | |
838 | { | |
839 | try | |
840 | { | |
841 | 1077 | return PropertyConverter.toInteger(interpolate(value)); |
842 | } | |
843 | 0 | catch (ConversionException e) |
844 | { | |
845 | 0 | throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e); |
846 | } | |
847 | } | |
848 | } | |
849 | ||
850 | public long getLong(String key) | |
851 | { | |
852 | 16 | Long l = getLong(key, null); |
853 | 14 | if (l != null) |
854 | { | |
855 | 12 | return l.longValue(); |
856 | } | |
857 | else | |
858 | { | |
859 | 2 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
860 | } | |
861 | } | |
862 | ||
863 | public long getLong(String key, long defaultValue) | |
864 | { | |
865 | 7 | return getLong(key, new Long(defaultValue)).longValue(); |
866 | } | |
867 | ||
868 | public Long getLong(String key, Long defaultValue) | |
869 | { | |
870 | 27 | Object value = resolveContainerStore(key); |
871 | ||
872 | 27 | if (value == null) |
873 | { | |
874 | 7 | return defaultValue; |
875 | } | |
876 | else | |
877 | { | |
878 | try | |
879 | { | |
880 | 20 | return PropertyConverter.toLong(interpolate(value)); |
881 | } | |
882 | 2 | catch (ConversionException e) |
883 | { | |
884 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a Long object", e); |
885 | } | |
886 | } | |
887 | } | |
888 | ||
889 | public short getShort(String key) | |
890 | { | |
891 | 17 | Short s = getShort(key, null); |
892 | 15 | if (s != null) |
893 | { | |
894 | 13 | return s.shortValue(); |
895 | } | |
896 | else | |
897 | { | |
898 | 2 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
899 | } | |
900 | } | |
901 | ||
902 | public short getShort(String key, short defaultValue) | |
903 | { | |
904 | 7 | return getShort(key, new Short(defaultValue)).shortValue(); |
905 | } | |
906 | ||
907 | public Short getShort(String key, Short defaultValue) | |
908 | { | |
909 | 31 | Object value = resolveContainerStore(key); |
910 | ||
911 | 31 | if (value == null) |
912 | { | |
913 | 9 | return defaultValue; |
914 | } | |
915 | else | |
916 | { | |
917 | try | |
918 | { | |
919 | 22 | return PropertyConverter.toShort(interpolate(value)); |
920 | } | |
921 | 2 | catch (ConversionException e) |
922 | { | |
923 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a Short object", e); |
924 | } | |
925 | } | |
926 | } | |
927 | ||
928 | /** | |
929 | * {@inheritDoc} | |
930 | * @see #setThrowExceptionOnMissing(boolean) | |
931 | */ | |
932 | public BigDecimal getBigDecimal(String key) | |
933 | { | |
934 | 7 | BigDecimal number = getBigDecimal(key, null); |
935 | 5 | if (number != null) |
936 | { | |
937 | 3 | return number; |
938 | } | |
939 | 2 | else if (isThrowExceptionOnMissing()) |
940 | { | |
941 | 1 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
942 | } | |
943 | else | |
944 | { | |
945 | 1 | return null; |
946 | } | |
947 | } | |
948 | ||
949 | public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) | |
950 | { | |
951 | 12 | Object value = resolveContainerStore(key); |
952 | ||
953 | 12 | if (value == null) |
954 | { | |
955 | 4 | return defaultValue; |
956 | } | |
957 | else | |
958 | { | |
959 | try | |
960 | { | |
961 | 8 | return PropertyConverter.toBigDecimal(interpolate(value)); |
962 | } | |
963 | 2 | catch (ConversionException e) |
964 | { | |
965 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e); |
966 | } | |
967 | } | |
968 | } | |
969 | ||
970 | /** | |
971 | * {@inheritDoc} | |
972 | * @see #setThrowExceptionOnMissing(boolean) | |
973 | */ | |
974 | public BigInteger getBigInteger(String key) | |
975 | { | |
976 | 8 | BigInteger number = getBigInteger(key, null); |
977 | 6 | if (number != null) |
978 | { | |
979 | 4 | return number; |
980 | } | |
981 | 2 | else if (isThrowExceptionOnMissing()) |
982 | { | |
983 | 1 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
984 | } | |
985 | else | |
986 | { | |
987 | 1 | return null; |
988 | } | |
989 | } | |
990 | ||
991 | public BigInteger getBigInteger(String key, BigInteger defaultValue) | |
992 | { | |
993 | 13 | Object value = resolveContainerStore(key); |
994 | ||
995 | 13 | if (value == null) |
996 | { | |
997 | 4 | return defaultValue; |
998 | } | |
999 | else | |
1000 | { | |
1001 | try | |
1002 | { | |
1003 | 9 | return PropertyConverter.toBigInteger(interpolate(value)); |
1004 | } | |
1005 | 2 | catch (ConversionException e) |
1006 | { | |
1007 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e); |
1008 | } | |
1009 | } | |
1010 | } | |
1011 | ||
1012 | /** | |
1013 | * {@inheritDoc} | |
1014 | * @see #setThrowExceptionOnMissing(boolean) | |
1015 | */ | |
1016 | public String getString(String key) | |
1017 | { | |
1018 | 1007 | String s = getString(key, null); |
1019 | 1004 | if (s != null) |
1020 | { | |
1021 | 667 | return s; |
1022 | } | |
1023 | 337 | else if (isThrowExceptionOnMissing()) |
1024 | { | |
1025 | 7 | throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object"); |
1026 | } | |
1027 | else | |
1028 | { | |
1029 | 330 | return null; |
1030 | } | |
1031 | } | |
1032 | ||
1033 | public String getString(String key, String defaultValue) | |
1034 | { | |
1035 | 1087 | Object value = resolveContainerStore(key); |
1036 | ||
1037 | 1087 | if (value instanceof String) |
1038 | { | |
1039 | 742 | return interpolate((String) value); |
1040 | } | |
1041 | 345 | else if (value == null) |
1042 | { | |
1043 | 345 | return interpolate(defaultValue); |
1044 | } | |
1045 | else | |
1046 | { | |
1047 | 0 | throw new ConversionException('\'' + key + "' doesn't map to a String object"); |
1048 | } | |
1049 | } | |
1050 | ||
1051 | /** | |
1052 | * Get an array of strings associated with the given configuration key. | |
1053 | * If the key doesn't map to an existing object, an empty array is returned. | |
1054 | * If a property is added to a configuration, it is checked whether it | |
1055 | * contains multiple values. This is obvious if the added object is a list | |
1056 | * or an array. For strings it is checked whether the string contains the | |
1057 | * list delimiter character that can be specified using the | |
1058 | * <code>setListDelimiter()</code> method. If this is the case, the string | |
1059 | * is splitted at these positions resulting in a property with multiple | |
1060 | * values. | |
1061 | * | |
1062 | * @param key The configuration key. | |
1063 | * @return The associated string array if key is found. | |
1064 | * | |
1065 | * @throws ConversionException is thrown if the key maps to an | |
1066 | * object that is not a String/List of Strings. | |
1067 | * @see #setListDelimiter(char) | |
1068 | * @see #setDelimiterParsingDisabled(boolean) | |
1069 | */ | |
1070 | public String[] getStringArray(String key) | |
1071 | { | |
1072 | 22 | Object value = getProperty(key); |
1073 | ||
1074 | String[] array; | |
1075 | ||
1076 | 22 | if (value instanceof String) |
1077 | { | |
1078 | 12 | array = new String[1]; |
1079 | ||
1080 | 12 | array[0] = interpolate((String) value); |
1081 | } | |
1082 | 10 | else if (value instanceof List) |
1083 | { | |
1084 | 8 | List list = (List) value; |
1085 | 8 | array = new String[list.size()]; |
1086 | ||
1087 | 30 | for (int i = 0; i < array.length; i++) |
1088 | { | |
1089 | 22 | array[i] = interpolate((String) list.get(i)); |
1090 | } | |
1091 | } | |
1092 | 2 | else if (value == null) |
1093 | { | |
1094 | 2 | array = new String[0]; |
1095 | } | |
1096 | else | |
1097 | { | |
1098 | 0 | throw new ConversionException('\'' + key + "' doesn't map to a String/List object"); |
1099 | } | |
1100 | 22 | return array; |
1101 | } | |
1102 | ||
1103 | /** | |
1104 | * {@inheritDoc} | |
1105 | * @see #getStringArray(String) | |
1106 | */ | |
1107 | public List getList(String key) | |
1108 | { | |
1109 | 260 | return getList(key, new ArrayList()); |
1110 | } | |
1111 | ||
1112 | public List getList(String key, List defaultValue) | |
1113 | { | |
1114 | 215 | Object value = getProperty(key); |
1115 | List list; | |
1116 | ||
1117 | 215 | if (value instanceof String) |
1118 | { | |
1119 | 30 | list = new ArrayList(1); |
1120 | 30 | list.add(interpolate((String) value)); |
1121 | } | |
1122 | 185 | else if (value instanceof List) |
1123 | { | |
1124 | 149 | list = new ArrayList(); |
1125 | 149 | List l = (List) value; |
1126 | ||
1127 | // add the interpolated elements in the new list | |
1128 | 149 | Iterator it = l.iterator(); |
1129 | 745 | while (it.hasNext()) |
1130 | { | |
1131 | 596 | list.add(interpolate(it.next())); |
1132 | } | |
1133 | } | |
1134 | 36 | else if (value == null) |
1135 | { | |
1136 | 32 | list = defaultValue; |
1137 | } | |
1138 | 4 | else if (value.getClass().isArray()) |
1139 | { | |
1140 | 2 | return Arrays.asList((Object[]) value); |
1141 | } | |
1142 | else | |
1143 | { | |
1144 | 2 | throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a " |
1145 | + value.getClass().getName()); | |
1146 | } | |
1147 | 211 | return list; |
1148 | } | |
1149 | ||
1150 | /** | |
1151 | * Returns an object from the store described by the key. If the value is a | |
1152 | * Collection object, replace it with the first object in the collection. | |
1153 | * | |
1154 | * @param key The property key. | |
1155 | * | |
1156 | * @return value Value, transparently resolving a possible collection dependency. | |
1157 | */ | |
1158 | protected Object resolveContainerStore(String key) | |
1159 | { | |
1160 | 5894 | Object value = getProperty(key); |
1161 | 5894 | if (value != null) |
1162 | { | |
1163 | 5430 | if (value instanceof Collection) |
1164 | { | |
1165 | 16 | Collection collection = (Collection) value; |
1166 | 16 | value = collection.isEmpty() ? null : collection.iterator().next(); |
1167 | } | |
1168 | 5414 | else if (value.getClass().isArray() && Array.getLength(value) > 0) |
1169 | { | |
1170 | 8 | value = Array.get(value, 0); |
1171 | } | |
1172 | } | |
1173 | ||
1174 | 5894 | return value; |
1175 | } | |
1176 | ||
1177 | /** | |
1178 | * Copies the content of the specified configuration into this | |
1179 | * configuration. If the specified configuration contains a key that is also | |
1180 | * present in this configuration, the value of this key will be replaced by | |
1181 | * the new value. <em>Note:</em> This method won't work well when copying | |
1182 | * hierarchical configurations because it is not able to copy information | |
1183 | * about the properties' structure (i.e. the parent-child-relationships will | |
1184 | * get lost). So when dealing with hierarchical configuration objects their | |
1185 | * <code>{@link HierarchicalConfiguration#clone() clone()}</code> methods | |
1186 | * should be used. | |
1187 | * | |
1188 | * @param c the configuration to copy (can be <b>null</b>, then this | |
1189 | * operation will have no effect) | |
1190 | * @since 1.5 | |
1191 | */ | |
1192 | public void copy(Configuration c) | |
1193 | { | |
1194 | 5 | if (c != null) |
1195 | { | |
1196 | 4 | for (Iterator it = c.getKeys(); it.hasNext();) |
1197 | { | |
1198 | 49 | String key = (String) it.next(); |
1199 | 49 | Object value = c.getProperty(key); |
1200 | 49 | fireEvent(EVENT_SET_PROPERTY, key, value, true); |
1201 | 49 | setDetailEvents(false); |
1202 | try | |
1203 | { | |
1204 | 49 | clearProperty(key); |
1205 | 49 | addPropertyValues(key, value, DISABLED_DELIMITER); |
1206 | } | |
1207 | finally | |
1208 | { | |
1209 | 49 | setDetailEvents(true); |
1210 | 49 | } |
1211 | 49 | fireEvent(EVENT_SET_PROPERTY, key, value, false); |
1212 | } | |
1213 | } | |
1214 | 5 | } |
1215 | ||
1216 | /** | |
1217 | * Appends the content of the specified configuration to this configuration. | |
1218 | * The values of all properties contained in the specified configuration | |
1219 | * will be appended to this configuration. So if a property is already | |
1220 | * present in this configuration, its new value will be a union of the | |
1221 | * values in both configurations. <em>Note:</em> This method won't work | |
1222 | * well when appending hierarchical configurations because it is not able to | |
1223 | * copy information about the properties' structure (i.e. the | |
1224 | * parent-child-relationships will get lost). So when dealing with | |
1225 | * hierarchical configuration objects their | |
1226 | * <code>{@link HierarchicalConfiguration#clone() clone()}</code> methods | |
1227 | * should be used. | |
1228 | * | |
1229 | * @param c the configuration to be appended (can be <b>null</b>, then this | |
1230 | * operation will have no effect) | |
1231 | * @since 1.5 | |
1232 | */ | |
1233 | public void append(Configuration c) | |
1234 | { | |
1235 | 84 | if (c != null) |
1236 | { | |
1237 | 83 | for (Iterator it = c.getKeys(); it.hasNext();) |
1238 | { | |
1239 | 2467 | String key = (String) it.next(); |
1240 | 2467 | Object value = c.getProperty(key); |
1241 | 2467 | fireEvent(EVENT_ADD_PROPERTY, key, value, true); |
1242 | 2467 | addPropertyValues(key, value, DISABLED_DELIMITER); |
1243 | 2467 | fireEvent(EVENT_ADD_PROPERTY, key, value, false); |
1244 | } | |
1245 | } | |
1246 | 84 | } |
1247 | ||
1248 | /** | |
1249 | * Returns a configuration with the same content as this configuration, but | |
1250 | * with all variables replaced by their actual values. This method tries to | |
1251 | * clone the configuration and then perform interpolation on all properties. | |
1252 | * So property values of the form <code>${var}</code> will be resolved as | |
1253 | * far as possible (if a variable cannot be resolved, it remains unchanged). | |
1254 | * This operation is useful if the content of a configuration is to be | |
1255 | * exported or processed by an external component that does not support | |
1256 | * variable interpolation. | |
1257 | * | |
1258 | * @return a configuration with all variables interpolated | |
1259 | * @throws ConfigurationRuntimeException if this configuration cannot be | |
1260 | * cloned | |
1261 | * @since 1.5 | |
1262 | */ | |
1263 | public Configuration interpolatedConfiguration() | |
1264 | { | |
1265 | // first clone this configuration | |
1266 | 1 | AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils |
1267 | .cloneConfiguration(this); | |
1268 | ||
1269 | // now perform interpolation | |
1270 | 1 | c.setDelimiterParsingDisabled(true); |
1271 | 1 | for (Iterator it = getKeys(); it.hasNext();) |
1272 | { | |
1273 | 8 | String key = (String) it.next(); |
1274 | 8 | c.setProperty(key, getList(key)); |
1275 | } | |
1276 | ||
1277 | 1 | c.setDelimiterParsingDisabled(isDelimiterParsingDisabled()); |
1278 | 1 | return c; |
1279 | } | |
1280 | } |