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 */ 017package org.apache.commons.configuration2.interpol; 018 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Set; 027import java.util.concurrent.ConcurrentHashMap; 028import java.util.concurrent.CopyOnWriteArrayList; 029 030import org.apache.commons.text.StringSubstitutor; 031import org.apache.commons.text.lookup.DefaultStringLookup; 032 033/** 034 * <p> 035 * A class that handles interpolation (variable substitution) for configuration 036 * objects. 037 * </p> 038 * <p> 039 * Each instance of {@code AbstractConfiguration} is associated with an object 040 * of this class. All interpolation tasks are delegated to this object. 041 * </p> 042 * <p> 043 * {@code ConfigurationInterpolator} internally uses the {@code StringSubstitutor} 044 * class from <a href="https://commons.apache.org/text">Commons Text</a>. Thus it 045 * supports the same syntax of variable expressions. 046 * </p> 047 * <p> 048 * The basic idea of this class is that it can maintain a set of primitive 049 * {@link Lookup} objects, each of which is identified by a special prefix. The 050 * variables to be processed have the form {@code ${prefix:name}}. 051 * {@code ConfigurationInterpolator} will extract the prefix and determine, 052 * which primitive lookup object is registered for it. Then the name of the 053 * variable is passed to this object to obtain the actual value. It is also 054 * possible to define an arbitrary number of default lookup objects, which are 055 * used for variables that do not have a prefix or that cannot be resolved by 056 * their associated lookup object. When adding default lookup objects their 057 * order matters; they are queried in this order, and the first non-<b>null</b> 058 * variable value is used. 059 * </p> 060 * <p> 061 * After an instance has been created it does not contain any {@code Lookup} 062 * objects. The current set of lookup objects can be modified using the 063 * {@code registerLookup()} and {@code deregisterLookup()} methods. Default 064 * lookup objects (that are invoked for variables without a prefix) can be added 065 * or removed with the {@code addDefaultLookup()} and 066 * {@code removeDefaultLookup()} methods respectively. (When a 067 * {@code ConfigurationInterpolator} instance is created by a configuration 068 * object, a default lookup object is added pointing to the configuration 069 * itself, so that variables are resolved using the configuration's properties.) 070 * </p> 071 * <p> 072 * The default usage scenario is that on a fully initialized instance the 073 * {@code interpolate()} method is called. It is passed an object value which 074 * may contain variables. All these variables are substituted if they can be 075 * resolved. The result is the passed in value with variables replaced. 076 * Alternatively, the {@code resolve()} method can be called to obtain the 077 * values of specific variables without performing interpolation. 078 * </p> 079 * <p> 080 * Implementation node: This class is thread-safe. Lookup objects can be added 081 * or removed at any time concurrent to interpolation operations. 082 * </p> 083 * 084 * @since 1.4 085 */ 086public class ConfigurationInterpolator 087{ 088 /** Constant for the prefix separator. */ 089 private static final char PREFIX_SEPARATOR = ':'; 090 091 /** The variable prefix. */ 092 private static final String VAR_START = "${"; 093 094 /** The length of {@link #VAR_START}. */ 095 private static final int VAR_START_LENGTH = VAR_START.length(); 096 097 /** The variable suffix. */ 098 private static final String VAR_END = "}"; 099 100 /** The length of {@link #VAR_END}. */ 101 private static final int VAR_END_LENGTH = VAR_END.length(); 102 103 /** A map containing the default prefix lookups. */ 104 private static final Map<String, Lookup> DEFAULT_PREFIX_LOOKUPS; 105 106 static 107 { 108 // TODO Perhaps a 3.0 version should only use Commons Text lookups. 109 // Add our own lookups. 110 final Map<String, Lookup> lookups = new HashMap<>(); 111 for (final DefaultLookups lookup : DefaultLookups.values()) 112 { 113 lookups.put(lookup.getPrefix(), lookup.getLookup()); 114 } 115 // Add Apache Commons Text lookups but don't override existing keys. 116 for (final DefaultStringLookup lookup : DefaultStringLookup.values()) 117 { 118 lookups.putIfAbsent(lookup.getKey(), new StringLookupAdapter(lookup.getStringLookup())); 119 } 120 DEFAULT_PREFIX_LOOKUPS = Collections.unmodifiableMap(lookups); 121 } 122 123 /** A map with the currently registered lookup objects. */ 124 private final Map<String, Lookup> prefixLookups; 125 126 /** Stores the default lookup objects. */ 127 private final List<Lookup> defaultLookups; 128 129 /** The helper object performing variable substitution. */ 130 private final StringSubstitutor substitutor; 131 132 /** Stores a parent interpolator objects if the interpolator is nested hierarchically. */ 133 private volatile ConfigurationInterpolator parentInterpolator; 134 135 /** 136 * Creates a new instance of {@code ConfigurationInterpolator}. 137 */ 138 public ConfigurationInterpolator() 139 { 140 prefixLookups = new ConcurrentHashMap<>(); 141 defaultLookups = new CopyOnWriteArrayList<>(); 142 substitutor = initSubstitutor(); 143 } 144 145 /** 146 * Creates a new instance based on the properties in the given specification 147 * object. 148 * 149 * @param spec the {@code InterpolatorSpecification} 150 * @return the newly created instance 151 */ 152 private static ConfigurationInterpolator createInterpolator( 153 final InterpolatorSpecification spec) 154 { 155 final ConfigurationInterpolator ci = new ConfigurationInterpolator(); 156 ci.addDefaultLookups(spec.getDefaultLookups()); 157 ci.registerLookups(spec.getPrefixLookups()); 158 ci.setParentInterpolator(spec.getParentInterpolator()); 159 return ci; 160 } 161 162 /** 163 * Extracts the variable name from a value that consists of a single 164 * variable. 165 * 166 * @param strValue the value 167 * @return the extracted variable name 168 */ 169 private static String extractVariableName(final String strValue) 170 { 171 return strValue.substring(VAR_START_LENGTH, 172 strValue.length() - VAR_END_LENGTH); 173 } 174 175 /** 176 * Creates a new {@code ConfigurationInterpolator} instance based on the 177 * passed in specification object. If the {@code InterpolatorSpecification} 178 * already contains a {@code ConfigurationInterpolator} object, it is used 179 * directly. Otherwise, a new instance is created and initialized with the 180 * properties stored in the specification. 181 * 182 * @param spec the {@code InterpolatorSpecification} (must not be 183 * <b>null</b>) 184 * @return the {@code ConfigurationInterpolator} obtained or created based 185 * on the given specification 186 * @throws IllegalArgumentException if the specification is <b>null</b> 187 * @since 2.0 188 */ 189 public static ConfigurationInterpolator fromSpecification( 190 final InterpolatorSpecification spec) 191 { 192 if (spec == null) 193 { 194 throw new IllegalArgumentException( 195 "InterpolatorSpecification must not be null!"); 196 } 197 return spec.getInterpolator() != null ? spec.getInterpolator() 198 : createInterpolator(spec); 199 } 200 201 /** 202 * Returns a map containing the default prefix lookups. Every configuration 203 * object derived from {@code AbstractConfiguration} is by default 204 * initialized with a {@code ConfigurationInterpolator} containing these 205 * {@code Lookup} objects and their prefixes. The map cannot be modified 206 * 207 * @return a map with the default prefix {@code Lookup} objects and their 208 * prefixes 209 * @since 2.0 210 */ 211 public static Map<String, Lookup> getDefaultPrefixLookups() 212 { 213 return DEFAULT_PREFIX_LOOKUPS; 214 } 215 216 /** 217 * Utility method for obtaining a {@code Lookup} object in a safe way. This 218 * method always returns a non-<b>null</b> {@code Lookup} object. If the 219 * passed in {@code Lookup} is not <b>null</b>, it is directly returned. 220 * Otherwise, result is a dummy {@code Lookup} which does not provide any 221 * values. 222 * 223 * @param lookup the {@code Lookup} to check 224 * @return a non-<b>null</b> {@code Lookup} object 225 * @since 2.0 226 */ 227 public static Lookup nullSafeLookup(Lookup lookup) 228 { 229 if (lookup == null) 230 { 231 lookup = DummyLookup.INSTANCE; 232 } 233 return lookup; 234 } 235 236 /** 237 * Adds a default {@code Lookup} object. Default {@code Lookup} objects are 238 * queried (in the order they were added) for all variables without a 239 * special prefix. If no default {@code Lookup} objects are present, such 240 * variables won't be processed. 241 * 242 * @param defaultLookup the default {@code Lookup} object to be added (must 243 * not be <b>null</b>) 244 * @throws IllegalArgumentException if the {@code Lookup} object is 245 * <b>null</b> 246 */ 247 public void addDefaultLookup(final Lookup defaultLookup) 248 { 249 defaultLookups.add(defaultLookup); 250 } 251 252 /** 253 * Adds all {@code Lookup} objects in the given collection as default 254 * lookups. The collection can be <b>null</b>, then this method has no 255 * effect. It must not contain <b>null</b> entries. 256 * 257 * @param lookups the {@code Lookup} objects to be added as default lookups 258 * @throws IllegalArgumentException if the collection contains a <b>null</b> 259 * entry 260 */ 261 public void addDefaultLookups(final Collection<? extends Lookup> lookups) 262 { 263 if (lookups != null) 264 { 265 defaultLookups.addAll(lookups); 266 } 267 } 268 269 /** 270 * Deregisters the {@code Lookup} object for the specified prefix at this 271 * instance. It will be removed from this instance. 272 * 273 * @param prefix the variable prefix 274 * @return a flag whether for this prefix a lookup object had been 275 * registered 276 */ 277 public boolean deregisterLookup(final String prefix) 278 { 279 return prefixLookups.remove(prefix) != null; 280 } 281 282 /** 283 * Obtains the lookup object for the specified prefix. This method is called 284 * by the {@code lookup()} method. This implementation will check 285 * whether a lookup object is registered for the given prefix. If not, a 286 * <b>null</b> lookup object will be returned (never <b>null</b>). 287 * 288 * @param prefix the prefix 289 * @return the lookup object to be used for this prefix 290 */ 291 protected Lookup fetchLookupForPrefix(final String prefix) 292 { 293 return nullSafeLookup(prefixLookups.get(prefix)); 294 } 295 296 /** 297 * Returns a collection with the default {@code Lookup} objects 298 * added to this {@code ConfigurationInterpolator}. These objects are not 299 * associated with a variable prefix. The returned list is a snapshot copy 300 * of the internal collection of default lookups; so manipulating it does 301 * not affect this instance. 302 * 303 * @return the default lookup objects 304 */ 305 public List<Lookup> getDefaultLookups() 306 { 307 return new ArrayList<>(defaultLookups); 308 } 309 310 /** 311 * Returns a map with the currently registered {@code Lookup} objects and 312 * their prefixes. This is a snapshot copy of the internally used map. So 313 * modifications of this map do not effect this instance. 314 * 315 * @return a copy of the map with the currently registered {@code Lookup} 316 * objects 317 */ 318 public Map<String, Lookup> getLookups() 319 { 320 return new HashMap<>(prefixLookups); 321 } 322 323 /** 324 * Returns the parent {@code ConfigurationInterpolator}. 325 * 326 * @return the parent {@code ConfigurationInterpolator} (can be <b>null</b>) 327 */ 328 public ConfigurationInterpolator getParentInterpolator() 329 { 330 return this.parentInterpolator; 331 } 332 333 /** 334 * Creates and initializes a {@code StringSubstitutor} object which is used for 335 * variable substitution. This {@code StringSubstitutor} is assigned a 336 * specialized lookup object implementing the correct variable resolving 337 * algorithm. 338 * 339 * @return the {@code StringSubstitutor} used by this object 340 */ 341 private StringSubstitutor initSubstitutor() 342 { 343 return new StringSubstitutor(key -> Objects.toString(resolve(key), null)); 344 } 345 346 /** 347 * Performs interpolation of the passed in value. If the value is of type 348 * String, this method checks whether it contains variables. If so, all 349 * variables are replaced by their current values (if possible). For non 350 * string arguments, the value is returned without changes. 351 * 352 * @param value the value to be interpolated 353 * @return the interpolated value 354 */ 355 public Object interpolate(final Object value) 356 { 357 if (value instanceof String) 358 { 359 final String strValue = (String) value; 360 if (looksLikeSingleVariable(strValue)) 361 { 362 final Object resolvedValue = resolveSingleVariable(strValue); 363 if (resolvedValue != null && !(resolvedValue instanceof String)) 364 { 365 // If the value is again a string, it needs no special 366 // treatment; it may also contain further variables which 367 // must be resolved; therefore, the default mechanism is 368 // applied. 369 return resolvedValue; 370 } 371 } 372 return substitutor.replace(strValue); 373 } 374 return value; 375 } 376 377 /** 378 * Sets a flag that variable names can contain other variables. If enabled, 379 * variable substitution is also done in variable names. 380 * 381 * @return the substitution in variables flag 382 */ 383 public boolean isEnableSubstitutionInVariables() 384 { 385 return substitutor.isEnableSubstitutionInVariables(); 386 } 387 388 /** 389 * Checks whether a value to be interpolated seems to be a single variable. 390 * In this case, it is resolved directly without using the 391 * {@code StringSubstitutor}. Note that it is okay if this method returns a 392 * false positive: In this case, resolving is going to fail, and standard 393 * mechanism is used. 394 * 395 * @param strValue the value to be interpolated 396 * @return a flag whether this value seems to be a single variable 397 */ 398 private boolean looksLikeSingleVariable(final String strValue) 399 { 400 return strValue.startsWith(VAR_START) && strValue.endsWith(VAR_END); 401 } 402 403 /** 404 * Returns an unmodifiable set with the prefixes, for which {@code Lookup} 405 * objects are registered at this instance. This means that variables with 406 * these prefixes can be processed. 407 * 408 * @return a set with the registered variable prefixes 409 */ 410 public Set<String> prefixSet() 411 { 412 return Collections.unmodifiableSet(prefixLookups.keySet()); 413 } 414 415 /** 416 * Registers the given {@code Lookup} object for the specified prefix at 417 * this instance. From now on this lookup object will be used for variables 418 * that have the specified prefix. 419 * 420 * @param prefix the variable prefix (must not be <b>null</b>) 421 * @param lookup the {@code Lookup} object to be used for this prefix (must 422 * not be <b>null</b>) 423 * @throws IllegalArgumentException if either the prefix or the 424 * {@code Lookup} object is <b>null</b> 425 */ 426 public void registerLookup(final String prefix, final Lookup lookup) 427 { 428 if (prefix == null) 429 { 430 throw new IllegalArgumentException( 431 "Prefix for lookup object must not be null!"); 432 } 433 if (lookup == null) 434 { 435 throw new IllegalArgumentException( 436 "Lookup object must not be null!"); 437 } 438 prefixLookups.put(prefix, lookup); 439 } 440 441 /** 442 * Registers all {@code Lookup} objects in the given map with their prefixes 443 * at this {@code ConfigurationInterpolator}. Using this method multiple 444 * {@code Lookup} objects can be registered at once. If the passed in map is 445 * <b>null</b>, this method does not have any effect. 446 * 447 * @param lookups the map with lookups to register (may be <b>null</b>) 448 * @throws IllegalArgumentException if the map contains <b>entries</b> 449 */ 450 public void registerLookups(final Map<String, ? extends Lookup> lookups) 451 { 452 if (lookups != null) 453 { 454 prefixLookups.putAll(lookups); 455 } 456 } 457 458 /** 459 * Removes the specified {@code Lookup} object from the list of default 460 * {@code Lookup}s. 461 * 462 * @param lookup the {@code Lookup} object to be removed 463 * @return a flag whether this {@code Lookup} object actually existed and 464 * was removed 465 */ 466 public boolean removeDefaultLookup(final Lookup lookup) 467 { 468 return defaultLookups.remove(lookup); 469 } 470 471 /** 472 * Resolves the specified variable. This implementation tries to extract 473 * a variable prefix from the given variable name (the first colon (':') is 474 * used as prefix separator). It then passes the name of the variable with 475 * the prefix stripped to the lookup object registered for this prefix. If 476 * no prefix can be found or if the associated lookup object cannot resolve 477 * this variable, the default lookup objects are used. If this is not 478 * successful either and a parent {@code ConfigurationInterpolator} is 479 * available, this object is asked to resolve the variable. 480 * 481 * @param var the name of the variable whose value is to be looked up which may contain a prefix. 482 * @return the value of this variable or <b>null</b> if it cannot be 483 * resolved 484 */ 485 public Object resolve(final String var) 486 { 487 if (var == null) 488 { 489 return null; 490 } 491 492 final int prefixPos = var.indexOf(PREFIX_SEPARATOR); 493 if (prefixPos >= 0) 494 { 495 final String prefix = var.substring(0, prefixPos); 496 final String name = var.substring(prefixPos + 1); 497 final Object value = fetchLookupForPrefix(prefix).lookup(name); 498 if (value != null) 499 { 500 return value; 501 } 502 } 503 504 for (final Lookup lookup : defaultLookups) 505 { 506 final Object value = lookup.lookup(var); 507 if (value != null) 508 { 509 return value; 510 } 511 } 512 513 final ConfigurationInterpolator parent = getParentInterpolator(); 514 if (parent != null) 515 { 516 return getParentInterpolator().resolve(var); 517 } 518 return null; 519 } 520 521 /** 522 * Interpolates a string value that seems to be a single variable. 523 * 524 * @param strValue the string to be interpolated 525 * @return the resolved value or <b>null</b> if resolving failed 526 */ 527 private Object resolveSingleVariable(final String strValue) 528 { 529 return resolve(extractVariableName(strValue)); 530 } 531 532 /** 533 * Sets the flag whether variable names can contain other variables. This 534 * flag corresponds to the {@code enableSubstitutionInVariables} property of 535 * the underlying {@code StringSubstitutor} object. 536 * 537 * @param f the new value of the flag 538 */ 539 public void setEnableSubstitutionInVariables(final boolean f) 540 { 541 substitutor.setEnableSubstitutionInVariables(f); 542 } 543 544 /** 545 * Sets the parent {@code ConfigurationInterpolator}. This object is used if 546 * the {@code Lookup} objects registered at this object cannot resolve a 547 * variable. 548 * 549 * @param parentInterpolator the parent {@code ConfigurationInterpolator} 550 * object (can be <b>null</b>) 551 */ 552 public void setParentInterpolator( 553 final ConfigurationInterpolator parentInterpolator) 554 { 555 this.parentInterpolator = parentInterpolator; 556 } 557}