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