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.configuration2; 19 20 import java.math.BigDecimal; 21 import java.math.BigInteger; 22 import java.time.Duration; 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.Collection; 26 import java.util.Collections; 27 import java.util.Iterator; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.NoSuchElementException; 31 import java.util.Objects; 32 import java.util.Properties; 33 import java.util.concurrent.atomic.AtomicReference; 34 import java.util.stream.Collectors; 35 36 import org.apache.commons.configuration2.convert.ConversionHandler; 37 import org.apache.commons.configuration2.convert.DefaultConversionHandler; 38 import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler; 39 import org.apache.commons.configuration2.convert.ListDelimiterHandler; 40 import org.apache.commons.configuration2.event.BaseEventSource; 41 import org.apache.commons.configuration2.event.ConfigurationErrorEvent; 42 import org.apache.commons.configuration2.event.ConfigurationEvent; 43 import org.apache.commons.configuration2.event.EventListener; 44 import org.apache.commons.configuration2.ex.ConversionException; 45 import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; 46 import org.apache.commons.configuration2.interpol.InterpolatorSpecification; 47 import org.apache.commons.configuration2.interpol.Lookup; 48 import org.apache.commons.configuration2.io.ConfigurationLogger; 49 import org.apache.commons.configuration2.sync.LockMode; 50 import org.apache.commons.configuration2.sync.NoOpSynchronizer; 51 import org.apache.commons.configuration2.sync.Synchronizer; 52 import org.apache.commons.lang3.ArrayUtils; 53 import org.apache.commons.lang3.ClassUtils; 54 import org.apache.commons.lang3.ObjectUtils; 55 import org.apache.commons.lang3.StringUtils; 56 57 /** 58 * <p> 59 * Abstract configuration class. Provides basic functionality but does not store any data. 60 * </p> 61 * <p> 62 * If you want to write your own Configuration class then you should implement only abstract methods from this class. A 63 * lot of functionality needed by typical implementations of the {@code Configuration} interface is already provided by 64 * this base class. Following is a list of features implemented here: 65 * </p> 66 * <ul> 67 * <li>Data conversion support. The various data types required by the {@code Configuration} interface are already 68 * handled by this base class. A concrete sub class only needs to provide a generic {@code getProperty()} method.</li> 69 * <li>Support for variable interpolation. Property values containing special variable tokens (like {@code ${var}}) will 70 * be replaced by their corresponding values.</li> 71 * <li>Optional support for string lists. The values of properties to be added to this configuration are checked whether 72 * they contain a list delimiter character. If this is the case and if list splitting is enabled, the string is split 73 * and multiple values are added for this property. List splitting is controlled by a {@link ListDelimiterHandler} 74 * object which can be set using the {@link #setListDelimiterHandler(ListDelimiterHandler)} method. It is disabled per 75 * default. To enable this feature, set a suitable {@code ListDelimiterHandler}, e.g. an instance of 76 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} configured 77 * with the desired list delimiter character.</li> 78 * <li>Allows specifying how missing properties are treated. Per default the get methods returning an object will return 79 * <b>null</b> if the searched property key is not found (and no default value is provided). With the 80 * {@code setThrowExceptionOnMissing()} method this behavior can be changed to throw an exception when a requested 81 * property cannot be found.</li> 82 * <li>Basic event support. Whenever this configuration is modified registered event listeners are notified. Refer to 83 * the various {@code EVENT_XXX} constants to get an impression about which event types are supported.</li> 84 * <li>Support for proper synchronization based on the {@link Synchronizer} interface.</li> 85 * </ul> 86 * <p> 87 * Most methods defined by the {@code Configuration} interface are already implemented in this class. Many method 88 * implementations perform basic book-keeping tasks (e.g. firing events, handling synchronization), and then delegate to 89 * other (protected) methods executing the actual work. Subclasses override these protected methods to define or adapt 90 * behavior. The public entry point methods are final to prevent subclasses from breaking basic functionality. 91 * </p> 92 */ 93 public abstract class AbstractConfiguration extends BaseEventSource implements Configuration { 94 95 /** The list delimiter handler. */ 96 private ListDelimiterHandler listDelimiterHandler; 97 98 /** The conversion handler. */ 99 private ConversionHandler conversionHandler; 100 101 /** 102 * Whether the configuration should throw NoSuchElementExceptions or simply return null when a property does not exist. 103 * Defaults to return null. 104 */ 105 private boolean throwExceptionOnMissing; 106 107 /** Stores a reference to the object that handles variable interpolation. */ 108 private AtomicReference<ConfigurationInterpolator> interpolator; 109 110 /** The object responsible for synchronization. */ 111 private volatile Synchronizer synchronizer; 112 113 /** The object used for dealing with encoded property values. */ 114 private ConfigurationDecoder configurationDecoder; 115 116 /** Stores the logger. */ 117 private ConfigurationLogger log; 118 119 /** 120 * Creates a new instance of {@code AbstractConfiguration}. 121 */ 122 public AbstractConfiguration() { 123 interpolator = new AtomicReference<>(); 124 initLogger(null); 125 installDefaultInterpolator(); 126 listDelimiterHandler = DisabledListDelimiterHandler.INSTANCE; 127 conversionHandler = DefaultConversionHandler.INSTANCE; 128 } 129 130 /** 131 * Gets the {@code ListDelimiterHandler} used by this instance. 132 * 133 * @return the {@code ListDelimiterHandler} 134 * @since 2.0 135 */ 136 public ListDelimiterHandler getListDelimiterHandler() { 137 return listDelimiterHandler; 138 } 139 140 /** 141 * <p> 142 * Sets the {@code ListDelimiterHandler} to be used by this instance. This object is invoked every time when dealing 143 * with string properties that may contain a list delimiter and thus have to be split to multiple values. Per default, a 144 * {@code ListDelimiterHandler} implementation is set which does not support list splitting. This can be changed for 145 * instance by setting a {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler 146 * DefaultListDelimiterHandler} object. 147 * </p> 148 * <p> 149 * <strong>Warning:</strong> Be careful when changing the list delimiter handler when the configuration has already been 150 * loaded/populated. List handling is typically applied already when properties are added to the configuration. If later 151 * another handler is set which processes lists differently, results may be unexpected; some operations may even cause 152 * exceptions. 153 * </p> 154 * 155 * @param listDelimiterHandler the {@code ListDelimiterHandler} to be used (must not be <b>null</b>) 156 * @throws IllegalArgumentException if the {@code ListDelimiterHandler} is <b>null</b> 157 * @since 2.0 158 */ 159 public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) { 160 if (listDelimiterHandler == null) { 161 throw new IllegalArgumentException("List delimiter handler must not be null!"); 162 } 163 this.listDelimiterHandler = listDelimiterHandler; 164 } 165 166 /** 167 * Gets the {@code ConversionHandler} used by this instance. 168 * 169 * @return the {@code ConversionHandler} 170 * @since 2.0 171 */ 172 public ConversionHandler getConversionHandler() { 173 return conversionHandler; 174 } 175 176 /** 177 * Sets the {@code ConversionHandler} to be used by this instance. The {@code ConversionHandler} is responsible for 178 * every kind of data type conversion. It is consulted by all get methods returning results in specific data types. A 179 * newly created configuration uses a default {@code ConversionHandler} implementation. This can be changed while 180 * initializing the configuration (e.g. via a builder). Note that access to this property is not synchronized. 181 * 182 * @param conversionHandler the {@code ConversionHandler} to be used (must not be <b>null</b>) 183 * @throws IllegalArgumentException if the {@code ConversionHandler} is <b>null</b> 184 * @since 2.0 185 */ 186 public void setConversionHandler(final ConversionHandler conversionHandler) { 187 if (conversionHandler == null) { 188 throw new IllegalArgumentException("ConversionHandler must not be null!"); 189 } 190 this.conversionHandler = conversionHandler; 191 } 192 193 /** 194 * Allows to set the {@code throwExceptionOnMissing} flag. This flag controls the behavior of property getter methods 195 * that return objects if the requested property is missing. If the flag is set to <b>false</b> (which is the default 196 * value), these methods will return <b>null</b>. If set to <b>true</b>, they will throw a 197 * {@code NoSuchElementException} exception. Note that getter methods for primitive data types are not affected by this 198 * flag. 199 * 200 * @param throwExceptionOnMissing The new value for the property 201 */ 202 public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) { 203 this.throwExceptionOnMissing = throwExceptionOnMissing; 204 } 205 206 /** 207 * Returns true if missing values throw Exceptions. 208 * 209 * @return true if missing values throw Exceptions 210 */ 211 public boolean isThrowExceptionOnMissing() { 212 return throwExceptionOnMissing; 213 } 214 215 /** 216 * Gets the {@code ConfigurationInterpolator} object that manages the lookup objects for resolving variables. 217 * Unless a custom interpolator has been set or the instance has been modified, the returned interpolator will 218 * resolve values from this configuration instance and support the 219 * {@link ConfigurationInterpolator#getDefaultPrefixLookups() default prefix lookups}. 220 * 221 * @return the {@code ConfigurationInterpolator} associated with this configuration 222 * @since 1.4 223 * @see ConfigurationInterpolator#getDefaultPrefixLookups() 224 */ 225 @Override 226 public ConfigurationInterpolator getInterpolator() { 227 return interpolator.get(); 228 } 229 230 /** 231 * {@inheritDoc} This implementation sets the passed in object without further modifications. A <b>null</b> argument is 232 * allowed; this disables interpolation. 233 * 234 * @since 2.0 235 */ 236 @Override 237 public final void setInterpolator(final ConfigurationInterpolator ci) { 238 interpolator.set(ci); 239 } 240 241 /** 242 * {@inheritDoc} This implementation creates a new {@code ConfigurationInterpolator} instance and initializes it with 243 * the given {@code Lookup} objects. In addition, it adds a specialized default {@code Lookup} object which queries this 244 * {@code Configuration}. 245 * 246 * @since 2.0 247 */ 248 @Override 249 public final void installInterpolator(final Map<String, ? extends Lookup> prefixLookups, final Collection<? extends Lookup> defLookups) { 250 final InterpolatorSpecification spec = new InterpolatorSpecification.Builder().withPrefixLookups(prefixLookups).withDefaultLookups(defLookups) 251 .withDefaultLookup(new ConfigurationLookup(this)).create(); 252 setInterpolator(ConfigurationInterpolator.fromSpecification(spec)); 253 } 254 255 /** 256 * Registers all {@code Lookup} objects in the given map at the current {@code ConfigurationInterpolator} of this 257 * configuration. The set of default lookup objects (for variables without a prefix) is not modified by this method. If 258 * this configuration does not have a {@code ConfigurationInterpolator}, a new instance is created. Note: This method is 259 * mainly intended to be used for initializing a configuration when it is created by a builder. Normal client code 260 * should better call {@link #installInterpolator(Map, Collection)} to define the {@code ConfigurationInterpolator} in a 261 * single step. 262 * 263 * @param lookups a map with new {@code Lookup} objects and their prefixes (may be <b>null</b>) 264 * @since 2.0 265 */ 266 public void setPrefixLookups(final Map<String, ? extends Lookup> lookups) { 267 boolean success; 268 do { 269 // do this in a loop because the ConfigurationInterpolator 270 // instance may be changed by another thread 271 final ConfigurationInterpolator ciOld = getInterpolator(); 272 final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator(); 273 ciNew.registerLookups(lookups); 274 success = interpolator.compareAndSet(ciOld, ciNew); 275 } while (!success); 276 } 277 278 /** 279 * Adds all {@code Lookup} objects in the given collection as default lookups (i.e. lookups without a variable prefix) 280 * to the {@code ConfigurationInterpolator} object of this configuration. In addition, it adds a specialized default 281 * {@code Lookup} object which queries this {@code Configuration}. The set of {@code Lookup} objects with prefixes is 282 * not modified by this method. If this configuration does not have a {@code ConfigurationInterpolator}, a new instance 283 * is created. Note: This method is mainly intended to be used for initializing a configuration when it is created by a 284 * builder. Normal client code should better call {@link #installInterpolator(Map, Collection)} to define the 285 * {@code ConfigurationInterpolator} in a single step. 286 * 287 * @param lookups the collection with default {@code Lookup} objects to be added 288 * @since 2.0 289 */ 290 public void setDefaultLookups(final Collection<? extends Lookup> lookups) { 291 boolean success; 292 do { 293 final ConfigurationInterpolator ciOld = getInterpolator(); 294 final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator(); 295 Lookup confLookup = findConfigurationLookup(ciNew); 296 if (confLookup == null) { 297 confLookup = new ConfigurationLookup(this); 298 } else { 299 ciNew.removeDefaultLookup(confLookup); 300 } 301 ciNew.addDefaultLookups(lookups); 302 ciNew.addDefaultLookup(confLookup); 303 success = interpolator.compareAndSet(ciOld, ciNew); 304 } while (!success); 305 } 306 307 /** 308 * Sets the specified {@code ConfigurationInterpolator} as the parent of this configuration's 309 * {@code ConfigurationInterpolator}. If this configuration does not have a {@code ConfigurationInterpolator}, a new 310 * instance is created. Note: This method is mainly intended to be used for initializing a configuration when it is 311 * created by a builder. Normal client code can directly update the {@code ConfigurationInterpolator}. 312 * 313 * @param parent the parent {@code ConfigurationInterpolator} to be set 314 * @since 2.0 315 */ 316 public void setParentInterpolator(final ConfigurationInterpolator parent) { 317 boolean success; 318 do { 319 final ConfigurationInterpolator ciOld = getInterpolator(); 320 final ConfigurationInterpolator ciNew = ciOld != null ? ciOld : new ConfigurationInterpolator(); 321 ciNew.setParentInterpolator(parent); 322 success = interpolator.compareAndSet(ciOld, ciNew); 323 } while (!success); 324 } 325 326 /** 327 * Sets the {@code ConfigurationDecoder} for this configuration. This object is used by 328 * {@link #getEncodedString(String)}. 329 * 330 * @param configurationDecoder the {@code ConfigurationDecoder} 331 * @since 2.0 332 */ 333 public void setConfigurationDecoder(final ConfigurationDecoder configurationDecoder) { 334 this.configurationDecoder = configurationDecoder; 335 } 336 337 /** 338 * Gets the {@code ConfigurationDecoder} used by this instance. 339 * 340 * @return the {@code ConfigurationDecoder} 341 * @since 2.0 342 */ 343 public ConfigurationDecoder getConfigurationDecoder() { 344 return configurationDecoder; 345 } 346 347 /** 348 * Creates a clone of the {@code ConfigurationInterpolator} used by this instance. This method can be called by 349 * {@code clone()} implementations of derived classes. Normally, the {@code ConfigurationInterpolator} of a 350 * configuration instance must not be shared with other instances because it contains a specific {@code Lookup} object 351 * pointing to the owning configuration. This has to be taken into account when cloning a configuration. This method 352 * creates a new {@code ConfigurationInterpolator} for this configuration instance which contains all lookup objects 353 * from the original {@code ConfigurationInterpolator} except for the configuration specific lookup pointing to the 354 * passed in original configuration. This one is replaced by a corresponding {@code Lookup} referring to this 355 * configuration. 356 * 357 * @param orgConfig the original configuration from which this one was cloned 358 * @since 2.0 359 */ 360 protected void cloneInterpolator(final AbstractConfiguration orgConfig) { 361 interpolator = new AtomicReference<>(); 362 final ConfigurationInterpolator orgInterpolator = orgConfig.getInterpolator(); 363 final List<Lookup> defaultLookups = orgInterpolator.getDefaultLookups(); 364 final Lookup lookup = findConfigurationLookup(orgInterpolator, orgConfig); 365 if (lookup != null) { 366 defaultLookups.remove(lookup); 367 } 368 369 installInterpolator(orgInterpolator.getLookups(), defaultLookups); 370 } 371 372 /** 373 * Creates a default {@code ConfigurationInterpolator} which is initialized with all default {@code Lookup} objects. 374 * This method is called by the constructor. It ensures that default interpolation works for every new configuration 375 * instance. 376 */ 377 private void installDefaultInterpolator() { 378 installInterpolator(ConfigurationInterpolator.getDefaultPrefixLookups(), null); 379 } 380 381 /** 382 * Finds a {@code ConfigurationLookup} pointing to this configuration in the default lookups of the specified 383 * {@code ConfigurationInterpolator}. This method is called to ensure that there is exactly one default lookup querying 384 * this configuration. 385 * 386 * @param ci the {@code ConfigurationInterpolator} in question 387 * @return the found {@code Lookup} object or <b>null</b> 388 */ 389 private Lookup findConfigurationLookup(final ConfigurationInterpolator ci) { 390 return findConfigurationLookup(ci, this); 391 } 392 393 /** 394 * Finds a {@code ConfigurationLookup} pointing to the specified configuration in the default lookups for the specified 395 * {@code ConfigurationInterpolator}. 396 * 397 * @param ci the {@code ConfigurationInterpolator} in question 398 * @param targetConf the target configuration of the searched lookup 399 * @return the found {@code Lookup} object or <b>null</b> 400 */ 401 private static Lookup findConfigurationLookup(final ConfigurationInterpolator ci, final ImmutableConfiguration targetConf) { 402 for (final Lookup l : ci.getDefaultLookups()) { 403 if (l instanceof ConfigurationLookup && targetConf == ((ConfigurationLookup) l).getConfiguration()) { 404 return l; 405 } 406 } 407 return null; 408 } 409 410 /** 411 * Gets the logger used by this configuration object. 412 * 413 * @return the logger 414 * @since 2.0 415 */ 416 public ConfigurationLogger getLogger() { 417 return log; 418 } 419 420 /** 421 * Allows setting the logger to be used by this configuration object. This method makes it possible for clients to 422 * exactly control logging behavior. Per default a logger is set that will ignore all log messages. Derived classes that 423 * want to enable logging should call this method during their initialization with the logger to be used. It is legal to 424 * pass a <b>null</b> logger; in this case, logging will be disabled. 425 * 426 * @param log the new logger 427 * @since 2.0 428 */ 429 public void setLogger(final ConfigurationLogger log) { 430 initLogger(log); 431 } 432 433 /** 434 * Adds a special {@link EventListener} object to this configuration that will log all internal errors. This method is 435 * intended to be used by certain derived classes, for which it is known that they can fail on property access (e.g. 436 * {@code DatabaseConfiguration}). 437 * 438 * @since 1.4 439 */ 440 public final void addErrorLogListener() { 441 addEventListener(ConfigurationErrorEvent.ANY, event -> getLogger().warn("Internal error", event.getCause())); 442 } 443 444 /** 445 * Gets the object responsible for synchronizing this configuration. All access to this configuration - both read and 446 * write access - is controlled by this object. This implementation never returns <b>null</b>. If no 447 * {@code Synchronizer} has been set, a {@link NoOpSynchronizer} is returned. So, per default, instances of 448 * {@code AbstractConfiguration} are not thread-safe unless a suitable {@code Synchronizer} is set! 449 * 450 * @return the {@code Synchronizer} used by this instance 451 * @since 2.0 452 */ 453 @Override 454 public final Synchronizer getSynchronizer() { 455 final Synchronizer sync = synchronizer; 456 return sync != null ? sync : NoOpSynchronizer.INSTANCE; 457 } 458 459 /** 460 * Sets the object responsible for synchronizing this configuration. This method has to be called with a suitable 461 * {@code Synchronizer} object when initializing this configuration instance in order to make it thread-safe. 462 * 463 * @param synchronizer the new {@code Synchronizer}; can be <b>null</b>, then this instance uses a 464 * {@link NoOpSynchronizer} 465 * @since 2.0 466 */ 467 @Override 468 public final void setSynchronizer(final Synchronizer synchronizer) { 469 this.synchronizer = synchronizer; 470 } 471 472 /** 473 * {@inheritDoc} This implementation delegates to {@code beginRead()} or {@code beginWrite()}, depending on the 474 * {@code LockMode} argument. Subclasses can override these protected methods to perform additional steps when a 475 * configuration is locked. 476 * 477 * @since 2.0 478 * @throws NullPointerException if the argument is <b>null</b> 479 */ 480 @Override 481 public final void lock(final LockMode mode) { 482 switch (mode) { 483 case READ: 484 beginRead(false); 485 break; 486 case WRITE: 487 beginWrite(false); 488 break; 489 default: 490 throw new IllegalArgumentException("Unsupported LockMode: " + mode); 491 } 492 } 493 494 /** 495 * {@inheritDoc} This implementation delegates to {@code endRead()} or {@code endWrite()}, depending on the 496 * {@code LockMode} argument. Subclasses can override these protected methods to perform additional steps when a 497 * configuration's lock is released. 498 * 499 * @throws NullPointerException if the argument is <b>null</b> 500 */ 501 @Override 502 public final void unlock(final LockMode mode) { 503 switch (mode) { 504 case READ: 505 endRead(); 506 break; 507 case WRITE: 508 endWrite(); 509 break; 510 default: 511 throw new IllegalArgumentException("Unsupported LockMode: " + mode); 512 } 513 } 514 515 /** 516 * Notifies this configuration's {@link Synchronizer} that a read operation is about to start. This method is called by 517 * all methods which access this configuration in a read-only mode. Subclasses may override it to perform additional 518 * actions before this read operation. The boolean <em>optimize</em> argument can be evaluated by overridden methods in 519 * derived classes. Some operations which require a lock do not need a fully initialized configuration object. By 520 * setting this flag to <strong>true</strong>, such operations can give a corresponding hint. An overridden 521 * implementation of {@code beginRead()} can then decide to skip some initialization steps. All basic operations in this 522 * class (and most of the basic {@code Configuration} implementations) call this method with a parameter value of 523 * <strong>false</strong>. <strong>In any case the inherited method must be called! Otherwise, proper synchronization is 524 * not guaranteed.</strong> 525 * 526 * @param optimize a flag whether optimization can be performed 527 * @since 2.0 528 */ 529 protected void beginRead(final boolean optimize) { 530 getSynchronizer().beginRead(); 531 } 532 533 /** 534 * Notifies this configuration's {@link Synchronizer} that a read operation has finished. This method is called by all 535 * methods which access this configuration in a read-only manner at the end of their execution. Subclasses may override 536 * it to perform additional actions after this read operation. <strong>In any case the inherited method must be called! 537 * Otherwise, the read lock will not be released.</strong> 538 * 539 * @since 2.0 540 */ 541 protected void endRead() { 542 getSynchronizer().endRead(); 543 } 544 545 /** 546 * Notifies this configuration's {@link Synchronizer} that an update operation is about to start. This method is called 547 * by all methods which modify this configuration. Subclasses may override it to perform additional operations before an 548 * update. For a description of the boolean <em>optimize</em> argument refer to the documentation of 549 * {@code beginRead()}. <strong>In any case the inherited method must be called! Otherwise, proper synchronization is 550 * not guaranteed.</strong> 551 * 552 * @param optimize a flag whether optimization can be performed 553 * @see #beginRead(boolean) 554 * @since 2.0 555 */ 556 protected void beginWrite(final boolean optimize) { 557 getSynchronizer().beginWrite(); 558 } 559 560 /** 561 * Notifies this configuration's {@link Synchronizer} that an update operation has finished. This method is called by 562 * all methods which modify this configuration at the end of their execution. Subclasses may override it to perform 563 * additional operations after an update. <strong>In any case the inherited method must be called! Otherwise, the write 564 * lock will not be released.</strong> 565 * 566 * @since 2.0 567 */ 568 protected void endWrite() { 569 getSynchronizer().endWrite(); 570 } 571 572 @Override 573 public final void addProperty(final String key, final Object value) { 574 beginWrite(false); 575 try { 576 fireEvent(ConfigurationEvent.ADD_PROPERTY, key, value, true); 577 addPropertyInternal(key, value); 578 fireEvent(ConfigurationEvent.ADD_PROPERTY, key, value, false); 579 } finally { 580 endWrite(); 581 } 582 } 583 584 /** 585 * Actually adds a property to this configuration. This method is called by {@code addProperty()}. It performs list 586 * splitting if necessary and delegates to {@link #addPropertyDirect(String, Object)} for every single property value. 587 * 588 * @param key the key of the property to be added 589 * @param value the new property value 590 * @since 2.0 591 */ 592 protected void addPropertyInternal(final String key, final Object value) { 593 getListDelimiterHandler().parse(value).forEach(obj -> addPropertyDirect(key, obj)); 594 } 595 596 /** 597 * Adds a key/value pair to the Configuration. Override this method to provide write access to underlying Configuration 598 * store. 599 * 600 * @param key key to use for mapping 601 * @param value object to store 602 */ 603 protected abstract void addPropertyDirect(String key, Object value); 604 605 /** 606 * interpolate key names to handle ${key} stuff 607 * 608 * @param base string to interpolate 609 * 610 * @return returns the key name with the ${key} substituted 611 */ 612 protected String interpolate(final String base) { 613 return Objects.toString(interpolate((Object) base), null); 614 } 615 616 /** 617 * Returns the interpolated value. This implementation delegates to the current {@code ConfigurationInterpolator}. If no 618 * {@code ConfigurationInterpolator} is set, the passed in value is returned without changes. 619 * 620 * @param value the value to interpolate 621 * @return the value with variables substituted 622 */ 623 protected Object interpolate(final Object value) { 624 final ConfigurationInterpolator ci = getInterpolator(); 625 return ci != null ? ci.interpolate(value) : value; 626 } 627 628 @Override 629 public Configuration subset(final String prefix) { 630 return new SubsetConfiguration(this, prefix, "."); 631 } 632 633 @Override 634 public ImmutableConfiguration immutableSubset(final String prefix) { 635 return ConfigurationUtils.unmodifiableConfiguration(subset(prefix)); 636 } 637 638 @Override 639 public final void setProperty(final String key, final Object value) { 640 beginWrite(false); 641 try { 642 fireEvent(ConfigurationEvent.SET_PROPERTY, key, value, true); 643 setPropertyInternal(key, value); 644 fireEvent(ConfigurationEvent.SET_PROPERTY, key, value, false); 645 } finally { 646 endWrite(); 647 } 648 } 649 650 /** 651 * Actually sets the value of a property. This method is called by {@code setProperty()}. It provides a default 652 * implementation of this functionality by clearing the specified key and delegating to {@code addProperty()}. 653 * Subclasses should override this method if they can provide a more efficient algorithm for setting a property value. 654 * 655 * @param key the property key 656 * @param value the new property value 657 * @since 2.0 658 */ 659 protected void setPropertyInternal(final String key, final Object value) { 660 setDetailEvents(false); 661 try { 662 clearProperty(key); 663 addProperty(key, value); 664 } finally { 665 setDetailEvents(true); 666 } 667 } 668 669 /** 670 * Removes the specified property from this configuration. This implementation performs some preparations and then 671 * delegates to {@code clearPropertyDirect()}, which will do the real work. 672 * 673 * @param key the key to be removed 674 */ 675 @Override 676 public final void clearProperty(final String key) { 677 beginWrite(false); 678 try { 679 fireEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, true); 680 clearPropertyDirect(key); 681 fireEvent(ConfigurationEvent.CLEAR_PROPERTY, key, null, false); 682 } finally { 683 endWrite(); 684 } 685 } 686 687 /** 688 * Removes the specified property from this configuration. This method is called by {@code clearProperty()} after it has 689 * done some preparations. It must be overridden in sub classes. 690 * 691 * @param key the key to be removed 692 */ 693 protected abstract void clearPropertyDirect(String key); 694 695 @Override 696 public final void clear() { 697 beginWrite(false); 698 try { 699 fireEvent(ConfigurationEvent.CLEAR, null, null, true); 700 clearInternal(); 701 fireEvent(ConfigurationEvent.CLEAR, null, null, false); 702 } finally { 703 endWrite(); 704 } 705 } 706 707 /** 708 * Clears the whole configuration. This method is called by {@code clear()} after some preparations have been made. This 709 * base implementation uses the iterator provided by {@code getKeys()} to remove every single property. Subclasses 710 * should override this method if there is a more efficient way of clearing the configuration. 711 */ 712 protected void clearInternal() { 713 setDetailEvents(false); 714 boolean useIterator = true; 715 try { 716 final Iterator<String> it = getKeys(); 717 while (it.hasNext()) { 718 final String key = it.next(); 719 if (useIterator) { 720 try { 721 it.remove(); 722 } catch (final UnsupportedOperationException usoex) { 723 useIterator = false; 724 } 725 } 726 727 if (useIterator && containsKey(key)) { 728 useIterator = false; 729 } 730 731 if (!useIterator) { 732 // workaround for Iterators that do not remove the 733 // property 734 // on calling remove() or do not support remove() at all 735 clearProperty(key); 736 } 737 } 738 } finally { 739 setDetailEvents(true); 740 } 741 } 742 743 /** 744 * {@inheritDoc} This implementation takes care of synchronization and then delegates to {@code getKeysInternal()} for 745 * obtaining the actual iterator. Note that depending on a concrete implementation, an iteration may fail if the 746 * configuration is updated concurrently. 747 */ 748 @Override 749 public final Iterator<String> getKeys() { 750 beginRead(false); 751 try { 752 return getKeysInternal(); 753 } finally { 754 endRead(); 755 } 756 } 757 758 /** 759 * {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by a 760 * dot ('.'). So the call {@code getKeys("db");} will find the keys {@code db}, {@code db.user}, or {@code db.password}, 761 * but not the key {@code dbdriver}. 762 */ 763 @Override 764 public final Iterator<String> getKeys(final String prefix) { 765 beginRead(false); 766 try { 767 return getKeysInternal(prefix); 768 } finally { 769 endRead(); 770 } 771 } 772 773 /** 774 * {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by the delimiter. 775 * So the call {@code getKeys("db");} will find the keys {@code db}, {@code db@user}, or {@code db@password}, 776 * but not the key {@code dbdriver}. 777 */ 778 @Override 779 public final Iterator<String> getKeys(final String prefix, final String delimiter) { 780 beginRead(false); 781 try { 782 return getKeysInternal(prefix, delimiter); 783 } finally { 784 endRead(); 785 } 786 } 787 788 /** 789 * Actually creates an iterator for iterating over the keys in this configuration. This method is called by 790 * {@code getKeys()}, it has to be defined by concrete subclasses. 791 * 792 * @return an {@code Iterator} with all property keys in this configuration 793 * @since 2.0 794 */ 795 protected abstract Iterator<String> getKeysInternal(); 796 797 /** 798 * Gets an {@code Iterator} with all property keys starting with the specified prefix. This method is called by 799 * {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special 800 * iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way 801 * to iterate over specific keys only. 802 * 803 * @param prefix the prefix for the keys to be taken into account 804 * @return an {@code Iterator} returning the filtered keys 805 * @since 2.0 806 */ 807 protected Iterator<String> getKeysInternal(final String prefix) { 808 return new PrefixedKeysIterator(getKeysInternal(), prefix); 809 } 810 811 /** 812 * Gets an {@code Iterator} with all property keys starting with the specified prefix and specified delimiter. This method is called by 813 * {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special 814 * iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way 815 * to iterate over specific keys only. 816 * 817 * @param prefix the prefix for the keys to be taken into account 818 * @param delimiter the prefix delimiter 819 * @return an {@code Iterator} returning the filtered keys 820 * @since 2.10.0 821 */ 822 protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) { 823 return new PrefixedKeysIterator(getKeysInternal(), prefix, delimiter); 824 } 825 826 /** 827 * {@inheritDoc} This implementation ensures proper synchronization. Subclasses have to define the abstract 828 * {@code getPropertyInternal()} method which is called from here. 829 */ 830 @Override 831 public final Object getProperty(final String key) { 832 beginRead(false); 833 try { 834 return getPropertyInternal(key); 835 } finally { 836 endRead(); 837 } 838 } 839 840 /** 841 * Actually obtains the value of the specified property. This method is called by {@code getProperty()}. Concrete 842 * subclasses must define it to fetch the value of the desired property. 843 * 844 * @param key the key of the property in question 845 * @return the (raw) value of this property 846 * @since 2.0 847 */ 848 protected abstract Object getPropertyInternal(String key); 849 850 /** 851 * {@inheritDoc} This implementation handles synchronization and delegates to {@code isEmptyInternal()}. 852 */ 853 @Override 854 public final boolean isEmpty() { 855 beginRead(false); 856 try { 857 return isEmptyInternal(); 858 } finally { 859 endRead(); 860 } 861 } 862 863 /** 864 * Actually checks whether this configuration contains data. This method is called by {@code isEmpty()}. It has to be 865 * defined by concrete subclasses. 866 * 867 * @return <b>true</b> if this configuration contains no data, <b>false</b> otherwise 868 * @since 2.0 869 */ 870 protected abstract boolean isEmptyInternal(); 871 872 /** 873 * {@inheritDoc} This implementation handles synchronization and delegates to {@code sizeInternal()}. 874 */ 875 @Override 876 public final int size() { 877 beginRead(false); 878 try { 879 return sizeInternal(); 880 } finally { 881 endRead(); 882 } 883 } 884 885 /** 886 * Actually calculates the size of this configuration. This method is called by {@code size()} with a read lock held. 887 * The base implementation provided here calculates the size based on the iterator returned by {@code getKeys()}. Sub 888 * classes which can determine the size in a more efficient way should override this method. 889 * 890 * @return the size of this configuration (i.e. the number of keys) 891 */ 892 protected int sizeInternal() { 893 int size = 0; 894 for (final Iterator<String> keyIt = getKeysInternal(); keyIt.hasNext(); size++) { 895 keyIt.next(); 896 } 897 return size; 898 } 899 900 /** 901 * {@inheritDoc} This implementation handles synchronization and delegates to {@code containsKeyInternal()}. 902 */ 903 @Override 904 public final boolean containsKey(final String key) { 905 beginRead(false); 906 try { 907 return containsKeyInternal(key); 908 } finally { 909 endRead(); 910 } 911 } 912 913 /** 914 * Actually checks whether the specified key is contained in this configuration. This method is called by 915 * {@code containsKey()}. It has to be defined by concrete subclasses. 916 * 917 * @param key the key in question 918 * @return <b>true</b> if this key is contained in this configuration, <b>false</b> otherwise 919 * @since 2.0 920 */ 921 protected abstract boolean containsKeyInternal(String key); 922 923 @Override 924 public Properties getProperties(final String key) { 925 return getProperties(key, null); 926 } 927 928 /** 929 * Gets a list of properties associated with the given configuration key. 930 * 931 * @param key The configuration key. 932 * @param defaults Any default values for the returned {@code Properties} object. Ignored if {@code null}. 933 * 934 * @return The associated properties if key is found. 935 * 936 * @throws ConversionException is thrown if the key maps to an object that is not a String/List of Strings. 937 * 938 * @throws IllegalArgumentException if one of the tokens is malformed (does not contain an equals sign). 939 */ 940 public Properties getProperties(final String key, final Properties defaults) { 941 /* 942 * Grab an array of the tokens for this key. 943 */ 944 final String[] tokens = getStringArray(key); 945 946 /* 947 * Each token is of the form 'key=value'. 948 */ 949 final Properties props = defaults == null ? new Properties() : new Properties(defaults); 950 for (final String token : tokens) { 951 final int equalSign = token.indexOf('='); 952 if (equalSign > 0) { 953 final String pkey = token.substring(0, equalSign).trim(); 954 final String pvalue = token.substring(equalSign + 1).trim(); 955 props.put(pkey, pvalue); 956 } else if (tokens.length == 1 && StringUtils.isEmpty(key)) { 957 // Semantically equivalent to an empty Properties 958 // object. 959 break; 960 } else { 961 throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign"); 962 } 963 } 964 return props; 965 } 966 967 @Override 968 public boolean getBoolean(final String key) { 969 final Boolean b = convert(Boolean.class, key, null, true); 970 return checkNonNullValue(key, b).booleanValue(); 971 } 972 973 @Override 974 public boolean getBoolean(final String key, final boolean defaultValue) { 975 return getBoolean(key, Boolean.valueOf(defaultValue)).booleanValue(); 976 } 977 978 /** 979 * Obtains the value of the specified key and tries to convert it into a {@code Boolean} object. If the property has no 980 * value, the passed in default value will be used. 981 * 982 * @param key the key of the property 983 * @param defaultValue the default value 984 * @return the value of this key converted to a {@code Boolean} 985 * @throws ConversionException if the value cannot be converted to a {@code Boolean} 986 */ 987 @Override 988 public Boolean getBoolean(final String key, final Boolean defaultValue) { 989 return convert(Boolean.class, key, defaultValue, false); 990 } 991 992 @Override 993 public byte getByte(final String key) { 994 final Byte b = convert(Byte.class, key, null, true); 995 return checkNonNullValue(key, b).byteValue(); 996 } 997 998 @Override 999 public byte getByte(final String key, final byte defaultValue) { 1000 return getByte(key, Byte.valueOf(defaultValue)).byteValue(); 1001 } 1002 1003 @Override 1004 public Byte getByte(final String key, final Byte defaultValue) { 1005 return convert(Byte.class, key, defaultValue, false); 1006 } 1007 1008 @Override 1009 public double getDouble(final String key) { 1010 final Double d = convert(Double.class, key, null, true); 1011 return checkNonNullValue(key, d).doubleValue(); 1012 } 1013 1014 @Override 1015 public double getDouble(final String key, final double defaultValue) { 1016 return getDouble(key, Double.valueOf(defaultValue)).doubleValue(); 1017 } 1018 1019 @Override 1020 public Double getDouble(final String key, final Double defaultValue) { 1021 return convert(Double.class, key, defaultValue, false); 1022 } 1023 1024 @Override 1025 public Duration getDuration(final String key) { 1026 return checkNonNullValue(key, convert(Duration.class, key, null, true)); 1027 } 1028 1029 @Override 1030 public Duration getDuration(final String key, final Duration defaultValue) { 1031 return convert(Duration.class, key, defaultValue, false); 1032 } 1033 1034 @Override 1035 public float getFloat(final String key) { 1036 final Float f = convert(Float.class, key, null, true); 1037 return checkNonNullValue(key, f).floatValue(); 1038 } 1039 1040 @Override 1041 public float getFloat(final String key, final float defaultValue) { 1042 return getFloat(key, Float.valueOf(defaultValue)).floatValue(); 1043 } 1044 1045 @Override 1046 public Float getFloat(final String key, final Float defaultValue) { 1047 return convert(Float.class, key, defaultValue, false); 1048 } 1049 1050 @Override 1051 public int getInt(final String key) { 1052 final Integer i = convert(Integer.class, key, null, true); 1053 return checkNonNullValue(key, i).intValue(); 1054 } 1055 1056 @Override 1057 public int getInt(final String key, final int defaultValue) { 1058 return getInteger(key, Integer.valueOf(defaultValue)).intValue(); 1059 } 1060 1061 @Override 1062 public Integer getInteger(final String key, final Integer defaultValue) { 1063 return convert(Integer.class, key, defaultValue, false); 1064 } 1065 1066 @Override 1067 public long getLong(final String key) { 1068 final Long l = convert(Long.class, key, null, true); 1069 return checkNonNullValue(key, l).longValue(); 1070 } 1071 1072 @Override 1073 public long getLong(final String key, final long defaultValue) { 1074 return getLong(key, Long.valueOf(defaultValue)).longValue(); 1075 } 1076 1077 @Override 1078 public Long getLong(final String key, final Long defaultValue) { 1079 return convert(Long.class, key, defaultValue, false); 1080 } 1081 1082 @Override 1083 public short getShort(final String key) { 1084 final Short s = convert(Short.class, key, null, true); 1085 return checkNonNullValue(key, s).shortValue(); 1086 } 1087 1088 @Override 1089 public short getShort(final String key, final short defaultValue) { 1090 return getShort(key, Short.valueOf(defaultValue)).shortValue(); 1091 } 1092 1093 @Override 1094 public Short getShort(final String key, final Short defaultValue) { 1095 return convert(Short.class, key, defaultValue, false); 1096 } 1097 1098 /** 1099 * {@inheritDoc} 1100 * 1101 * @see #setThrowExceptionOnMissing(boolean) 1102 */ 1103 @Override 1104 public BigDecimal getBigDecimal(final String key) { 1105 return convert(BigDecimal.class, key, null, true); 1106 } 1107 1108 @Override 1109 public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) { 1110 return convert(BigDecimal.class, key, defaultValue, false); 1111 } 1112 1113 /** 1114 * {@inheritDoc} 1115 * 1116 * @see #setThrowExceptionOnMissing(boolean) 1117 */ 1118 @Override 1119 public BigInteger getBigInteger(final String key) { 1120 return convert(BigInteger.class, key, null, true); 1121 } 1122 1123 @Override 1124 public BigInteger getBigInteger(final String key, final BigInteger defaultValue) { 1125 return convert(BigInteger.class, key, defaultValue, false); 1126 } 1127 1128 /** 1129 * {@inheritDoc} 1130 * 1131 * @see #setThrowExceptionOnMissing(boolean) 1132 */ 1133 @Override 1134 public String getString(final String key) { 1135 return convert(String.class, key, null, true); 1136 } 1137 1138 @Override 1139 public String getString(final String key, final String defaultValue) { 1140 final String result = convert(String.class, key, null, false); 1141 return result != null ? result : interpolate(defaultValue); 1142 } 1143 1144 /** 1145 * {@inheritDoc} This implementation delegates to {@link #getString(String)} in order to obtain the value of the passed 1146 * in key. This value is passed to the decoder. Because {@code getString()} is used behind the scenes all standard 1147 * features like handling of missing keys and interpolation work as expected. 1148 */ 1149 @Override 1150 public String getEncodedString(final String key, final ConfigurationDecoder decoder) { 1151 if (decoder == null) { 1152 throw new IllegalArgumentException("ConfigurationDecoder must not be null!"); 1153 } 1154 1155 final String value = getString(key); 1156 return value != null ? decoder.decode(value) : null; 1157 } 1158 1159 /** 1160 * {@inheritDoc} This implementation makes use of the {@code ConfigurationDecoder} set for this configuration. If no 1161 * such object has been set, an {@code IllegalStateException} exception is thrown. 1162 * 1163 * @throws IllegalStateException if no {@code ConfigurationDecoder} is set 1164 * @see #setConfigurationDecoder(ConfigurationDecoder) 1165 */ 1166 @Override 1167 public String getEncodedString(final String key) { 1168 final ConfigurationDecoder decoder = getConfigurationDecoder(); 1169 if (decoder == null) { 1170 throw new IllegalStateException("No default ConfigurationDecoder defined!"); 1171 } 1172 return getEncodedString(key, decoder); 1173 } 1174 1175 /** 1176 * Gets an array of strings associated with the given configuration key. If the key doesn't map to an existing object, an 1177 * empty array is returned. When a property is added to a configuration, it is checked whether it contains multiple 1178 * values. This is obvious if the added object is a list or an array. For strings the association 1179 * {@link ListDelimiterHandler} is consulted to find out whether the string can be split into multiple values. 1180 * 1181 * @param key The configuration key. 1182 * @return The associated string array if key is found. 1183 * 1184 * @throws ConversionException is thrown if the key maps to an object that is not a String/List of Strings. 1185 * @see #setListDelimiterHandler(ListDelimiterHandler) 1186 */ 1187 @Override 1188 public String[] getStringArray(final String key) { 1189 final String[] result = (String[]) getArray(String.class, key); 1190 return result == null ? ArrayUtils.EMPTY_STRING_ARRAY : result; 1191 } 1192 1193 /** 1194 * {@inheritDoc} 1195 * 1196 * @see #getStringArray(String) 1197 */ 1198 @Override 1199 public List<Object> getList(final String key) { 1200 return getList(key, new ArrayList<>()); 1201 } 1202 1203 @Override 1204 public List<Object> getList(final String key, final List<?> defaultValue) { 1205 final Object value = getProperty(key); 1206 final List<Object> list; 1207 1208 if (value instanceof String) { 1209 list = new ArrayList<>(1); 1210 list.add(interpolate((String) value)); 1211 } else if (value instanceof List) { 1212 list = new ArrayList<>(); 1213 final List<?> l = (List<?>) value; 1214 1215 // add the interpolated elements in the new list 1216 l.forEach(elem -> list.add(interpolate(elem))); 1217 } else if (value == null) { 1218 // This is okay because we just return this list to the caller 1219 @SuppressWarnings("unchecked") 1220 final List<Object> resultList = (List<Object>) defaultValue; 1221 list = resultList; 1222 } else if (value.getClass().isArray()) { 1223 return Arrays.asList((Object[]) value); 1224 } else if (isScalarValue(value)) { 1225 return Collections.singletonList((Object) value.toString()); 1226 } else { 1227 throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a " + value.getClass().getName()); 1228 } 1229 return list; 1230 } 1231 1232 @Override 1233 public <T> T get(final Class<T> cls, final String key) { 1234 return convert(cls, key, null, true); 1235 } 1236 1237 /** 1238 * {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual type conversion. 1239 */ 1240 @Override 1241 public <T> T get(final Class<T> cls, final String key, final T defaultValue) { 1242 return convert(cls, key, defaultValue, false); 1243 } 1244 1245 @Override 1246 public Object getArray(final Class<?> cls, final String key) { 1247 return getArray(cls, key, null); 1248 } 1249 1250 /** 1251 * {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual type conversion. 1252 * If this results in a <b>null</b> result (because the property is undefined), the default value is returned. It is 1253 * checked whether the default value is an array with the correct component type. If not, an exception is thrown. 1254 * 1255 * @throws IllegalArgumentException if the default value is not a compatible array 1256 */ 1257 @Override 1258 public Object getArray(final Class<?> cls, final String key, final Object defaultValue) { 1259 return convertToArray(cls, key, defaultValue); 1260 } 1261 1262 @Override 1263 public <T> List<T> getList(final Class<T> cls, final String key) { 1264 return getList(cls, key, null); 1265 } 1266 1267 /** 1268 * {@inheritDoc} This implementation delegates to the generic {@code getCollection()}. As target collection a newly 1269 * created {@code ArrayList} is passed in. 1270 */ 1271 @Override 1272 public <T> List<T> getList(final Class<T> cls, final String key, final List<T> defaultValue) { 1273 final List<T> result = new ArrayList<>(); 1274 if (getCollection(cls, key, result, defaultValue) == null) { 1275 return null; 1276 } 1277 return result; 1278 } 1279 1280 @Override 1281 public <T> Collection<T> getCollection(final Class<T> cls, final String key, final Collection<T> target) { 1282 return getCollection(cls, key, target, null); 1283 } 1284 1285 /** 1286 * {@inheritDoc} This implementation delegates to the {@link ConversionHandler} to perform the actual conversion. If no 1287 * target collection is provided, an {@code ArrayList} is created. 1288 */ 1289 @Override 1290 public <T> Collection<T> getCollection(final Class<T> cls, final String key, final Collection<T> target, final Collection<T> defaultValue) { 1291 final Object src = getProperty(key); 1292 if (src == null) { 1293 return handleDefaultCollection(target, defaultValue); 1294 } 1295 1296 final Collection<T> targetCol = target != null ? target : new ArrayList<>(); 1297 getConversionHandler().toCollection(src, cls, getInterpolator(), targetCol); 1298 return targetCol; 1299 } 1300 1301 /** 1302 * Checks whether the specified object is a scalar value. This method is called by {@code getList()} and 1303 * {@code getStringArray()} if the property requested is not a string, a list, or an array. If it returns <b>true</b>, 1304 * the calling method transforms the value to a string and returns a list or an array with this single element. This 1305 * implementation returns <b>true</b> if the value is of a wrapper type for a primitive type. 1306 * 1307 * @param value the value to be checked 1308 * @return a flag whether the value is a scalar 1309 * @since 1.7 1310 */ 1311 protected boolean isScalarValue(final Object value) { 1312 return ClassUtils.wrapperToPrimitive(value.getClass()) != null; 1313 } 1314 1315 /** 1316 * Copies the content of the specified configuration into this configuration. If the specified configuration contains a 1317 * key that is also present in this configuration, the value of this key will be replaced by the new value. 1318 * <em>Note:</em> This method won't work well when copying hierarchical configurations because it is not able to copy 1319 * information about the properties' structure (i.e. the parent-child-relationships will get lost). So when dealing with 1320 * hierarchical configuration objects their {@link BaseHierarchicalConfiguration#clone() clone()} methods should be 1321 * used. 1322 * 1323 * @param c the configuration to copy (can be <b>null</b>, then this operation will have no effect) 1324 * @since 1.5 1325 */ 1326 public void copy(final Configuration c) { 1327 if (c != null) { 1328 c.lock(LockMode.READ); 1329 try { 1330 c.getKeys().forEachRemaining(key -> setProperty(key, encodeForCopy(c.getProperty(key)))); 1331 } finally { 1332 c.unlock(LockMode.READ); 1333 } 1334 } 1335 } 1336 1337 /** 1338 * Appends the content of the specified configuration to this configuration. The values of all properties contained in 1339 * the specified configuration will be appended to this configuration. So if a property is already present in this 1340 * configuration, its new value will be a union of the values in both configurations. <em>Note:</em> This method won't 1341 * work well when appending hierarchical configurations because it is not able to copy information about the properties' 1342 * structure (i.e. the parent-child-relationships will get lost). So when dealing with hierarchical configuration 1343 * objects their {@link BaseHierarchicalConfiguration#clone() clone()} methods should be used. 1344 * 1345 * @param c the configuration to be appended (can be <b>null</b>, then this operation will have no effect) 1346 * @since 1.5 1347 */ 1348 public void append(final Configuration c) { 1349 if (c != null) { 1350 c.lock(LockMode.READ); 1351 try { 1352 c.getKeys().forEachRemaining(key -> addProperty(key, encodeForCopy(c.getProperty(key)))); 1353 } finally { 1354 c.unlock(LockMode.READ); 1355 } 1356 } 1357 } 1358 1359 /** 1360 * Returns a configuration with the same content as this configuration, but with all variables replaced by their actual 1361 * values. This method tries to clone the configuration and then perform interpolation on all properties. So property 1362 * values of the form {@code ${var}} will be resolved as far as possible (if a variable cannot be resolved, it remains 1363 * unchanged). This operation is useful if the content of a configuration is to be exported or processed by an external 1364 * component that does not support variable interpolation. 1365 * 1366 * @return a configuration with all variables interpolated 1367 * @throws org.apache.commons.configuration2.ex.ConfigurationRuntimeException if this configuration cannot be cloned 1368 * @since 1.5 1369 */ 1370 public Configuration interpolatedConfiguration() { 1371 // first clone this configuration 1372 final AbstractConfiguration c = (AbstractConfiguration) ConfigurationUtils.cloneConfiguration(this); 1373 1374 // now perform interpolation 1375 c.setListDelimiterHandler(new DisabledListDelimiterHandler()); 1376 getKeys().forEachRemaining(key -> c.setProperty(key, getList(key))); 1377 c.setListDelimiterHandler(getListDelimiterHandler()); 1378 return c; 1379 } 1380 1381 /** 1382 * Initializes the logger. Supports <b>null</b> input. This method can be called by derived classes in order to enable 1383 * logging. 1384 * 1385 * @param log the logger 1386 * @since 2.0 1387 */ 1388 protected final void initLogger(final ConfigurationLogger log) { 1389 this.log = log != null ? log : ConfigurationLogger.newDummyLogger(); 1390 } 1391 1392 /** 1393 * Encodes a property value so that it can be added to this configuration. This method deals with list delimiters. The 1394 * passed in object has to be escaped so that an add operation yields the same result. If it is a list, all of its 1395 * values have to be escaped. 1396 * 1397 * @param value the value to be encoded 1398 * @return the encoded value 1399 */ 1400 private Object encodeForCopy(final Object value) { 1401 if (value instanceof Collection) { 1402 return encodeListForCopy((Collection<?>) value); 1403 } 1404 return getListDelimiterHandler().escape(value, ListDelimiterHandler.NOOP_TRANSFORMER); 1405 } 1406 1407 /** 1408 * Encodes a list with property values so that it can be added to this configuration. This method calls 1409 * {@code encodeForCopy()} for all list elements. 1410 * 1411 * @param values the list to be encoded 1412 * @return a list with encoded elements 1413 */ 1414 private Object encodeListForCopy(final Collection<?> values) { 1415 return values.stream().map(this::encodeForCopy).collect(Collectors.toList()); 1416 } 1417 1418 /** 1419 * Obtains the property value for the specified key and converts it to the given target class. 1420 * 1421 * @param <T> the target type of the conversion 1422 * @param cls the target class 1423 * @param key the key of the desired property 1424 * @param defaultValue a default value 1425 * @return the converted value of this property 1426 * @throws ConversionException if the conversion cannot be performed 1427 */ 1428 private <T> T getAndConvertProperty(final Class<T> cls, final String key, final T defaultValue) { 1429 final Object value = getProperty(key); 1430 try { 1431 return ObjectUtils.defaultIfNull(getConversionHandler().to(value, cls, getInterpolator()), defaultValue); 1432 } catch (final ConversionException cex) { 1433 // improve error message 1434 throw new ConversionException(String.format("Key '%s' cannot be converted to class %s. Value is: '%s'.", key, cls.getName(), String.valueOf(value)), 1435 cex.getCause()); 1436 } 1437 } 1438 1439 /** 1440 * Helper method for obtaining a property value with a type conversion. 1441 * 1442 * @param <T> the target type of the conversion 1443 * @param cls the target class 1444 * @param key the key of the desired property 1445 * @param defValue a default value 1446 * @param throwOnMissing a flag whether an exception should be thrown for a missing value 1447 * @return the converted value 1448 */ 1449 private <T> T convert(final Class<T> cls, final String key, final T defValue, final boolean throwOnMissing) { 1450 if (cls.isArray()) { 1451 return cls.cast(convertToArray(cls.getComponentType(), key, defValue)); 1452 } 1453 1454 final T result = getAndConvertProperty(cls, key, defValue); 1455 if (result == null) { 1456 if (throwOnMissing && isThrowExceptionOnMissing()) { 1457 throwMissingPropertyException(key); 1458 } 1459 return defValue; 1460 } 1461 1462 return result; 1463 } 1464 1465 /** 1466 * Performs a conversion to an array result class. This implementation delegates to the {@link ConversionHandler} to 1467 * perform the actual type conversion. If this results in a <b>null</b> result (because the property is undefined), the 1468 * default value is returned. It is checked whether the default value is an array with the correct component type. If 1469 * not, an exception is thrown. 1470 * 1471 * @param cls the component class of the array 1472 * @param key the configuration key 1473 * @param defaultValue an optional default value 1474 * @return the converted array 1475 * @throws IllegalArgumentException if the default value is not a compatible array 1476 */ 1477 private Object convertToArray(final Class<?> cls, final String key, final Object defaultValue) { 1478 checkDefaultValueArray(cls, defaultValue); 1479 return ObjectUtils.defaultIfNull(getConversionHandler().toArray(getProperty(key), cls, getInterpolator()), defaultValue); 1480 } 1481 1482 /** 1483 * Checks an object provided as default value for the {@code getArray()} method. Throws an exception if this is not an 1484 * array with the correct component type. 1485 * 1486 * @param cls the component class for the array 1487 * @param defaultValue the default value object to be checked 1488 * @throws IllegalArgumentException if this is not a valid default object 1489 */ 1490 private static void checkDefaultValueArray(final Class<?> cls, final Object defaultValue) { 1491 if (defaultValue != null && (!defaultValue.getClass().isArray() || !cls.isAssignableFrom(defaultValue.getClass().getComponentType()))) { 1492 throw new IllegalArgumentException( 1493 "The type of the default value (" + defaultValue.getClass() + ")" + " is not an array of the specified class (" + cls + ")"); 1494 } 1495 } 1496 1497 /** 1498 * Handles the default collection for a collection conversion. This method fills the target collection with the content 1499 * of the default collection. Both collections may be <b>null</b>. 1500 * 1501 * @param target the target collection 1502 * @param defaultValue the default collection 1503 * @return the initialized target collection 1504 */ 1505 private static <T> Collection<T> handleDefaultCollection(final Collection<T> target, final Collection<T> defaultValue) { 1506 if (defaultValue == null) { 1507 return null; 1508 } 1509 1510 final Collection<T> result; 1511 if (target == null) { 1512 result = new ArrayList<>(defaultValue); 1513 } else { 1514 target.addAll(defaultValue); 1515 result = target; 1516 } 1517 return result; 1518 } 1519 1520 /** 1521 * Checks whether the specified value is <b>null</b> and throws an exception in this case. This method is used by 1522 * conversion methods returning primitive Java types. Here values to be returned must not be <b>null</b>. 1523 * 1524 * @param <T> the type of the object to be checked 1525 * @param key the key which caused the problem 1526 * @param value the value to be checked 1527 * @return the passed in value for chaining this method call 1528 * @throws NoSuchElementException if the value is <b>null</b> 1529 */ 1530 private static <T> T checkNonNullValue(final String key, final T value) { 1531 if (value == null) { 1532 throwMissingPropertyException(key); 1533 } 1534 return value; 1535 } 1536 1537 /** 1538 * Helper method for throwing an exception for a key that does not map to an existing object. 1539 * 1540 * @param key the key (to be part of the error message) 1541 */ 1542 private static void throwMissingPropertyException(final String key) { 1543 throw new NoSuchElementException(String.format("Key '%s' does not map to an existing object!", key)); 1544 } 1545 }