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