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