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