ConfigurationInterpolator.java

  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. package org.apache.commons.configuration2.interpol;

  18. import java.lang.reflect.Array;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.Iterator;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Objects;
  27. import java.util.Properties;
  28. import java.util.Set;
  29. import java.util.concurrent.ConcurrentHashMap;
  30. import java.util.concurrent.CopyOnWriteArrayList;
  31. import java.util.function.Function;

  32. import org.apache.commons.text.StringSubstitutor;

  33. /**
  34.  * <p>
  35.  * A class that handles interpolation (variable substitution) for configuration objects.
  36.  * </p>
  37.  * <p>
  38.  * Each instance of {@code AbstractConfiguration} is associated with an object of this class. All interpolation tasks
  39.  * are delegated to this object.
  40.  * </p>
  41.  * <p>
  42.  * {@code ConfigurationInterpolator} internally uses the {@code StringSubstitutor} class from
  43.  * <a href="https://commons.apache.org/text">Commons Text</a>. Thus it supports the same syntax of variable expressions.
  44.  * </p>
  45.  * <p>
  46.  * The basic idea of this class is that it can maintain a set of primitive {@link Lookup} objects, each of which is
  47.  * identified by a special prefix. The variables to be processed have the form {@code ${prefix:name}}.
  48.  * {@code ConfigurationInterpolator} will extract the prefix and determine, which primitive lookup object is registered
  49.  * for it. Then the name of the variable is passed to this object to obtain the actual value. It is also possible to
  50.  * define an arbitrary number of default lookup objects, which are used for variables that do not have a prefix or that
  51.  * cannot be resolved by their associated lookup object. When adding default lookup objects their order matters; they
  52.  * are queried in this order, and the first non-<strong>null</strong> variable value is used.
  53.  * </p>
  54.  * <p>
  55.  * After an instance has been created it does not contain any {@code Lookup} objects. The current set of lookup objects
  56.  * can be modified using the {@code registerLookup()} and {@code deregisterLookup()} methods. Default lookup objects
  57.  * (that are invoked for variables without a prefix) can be added or removed with the {@code addDefaultLookup()} and
  58.  * {@code removeDefaultLookup()} methods respectively. (When a {@code ConfigurationInterpolator} instance is created by
  59.  * a configuration object, a default lookup object is added pointing to the configuration itself, so that variables are
  60.  * resolved using the configuration's properties.)
  61.  * </p>
  62.  * <p>
  63.  * The default usage scenario is that on a fully initialized instance the {@code interpolate()} method is called. It is
  64.  * passed an object value which may contain variables. All these variables are substituted if they can be resolved. The
  65.  * result is the passed in value with variables replaced. Alternatively, the {@code resolve()} method can be called to
  66.  * obtain the values of specific variables without performing interpolation.
  67.  * </p>
  68.  * <p><strong>String Conversion</strong></p>
  69.  * <p>
  70.  * When variables are part of larger interpolated strings, the variable values, which can be of any type, must be
  71.  * converted to strings to produce the full result. Each interpolator instance has a configurable
  72.  * {@link #setStringConverter(Function) string converter} to perform this conversion. The default implementation of this
  73.  * function simply uses the value's {@code toString} method in the majority of cases. However, for maximum
  74.  * consistency with
  75.  * {@link org.apache.commons.configuration2.convert.DefaultConversionHandler DefaultConversionHandler}, when a variable
  76.  * value is a container type (such as a collection or array), then only the first element of the container is converted
  77.  * to a string instead of the container itself. For example, if the variable {@code x} resolves to the integer array
  78.  * {@code [1, 2, 3]}, then the string <code>"my value = ${x}"</code> will by default be interpolated to
  79.  * {@code "my value = 1"}.
  80.  * </p>
  81.  * <p>
  82.  * <strong>Implementation note:</strong> This class is thread-safe. Lookup objects can be added or removed at any time
  83.  * concurrent to interpolation operations.
  84.  * </p>
  85.  *
  86.  * @since 1.4
  87.  */
  88. public class ConfigurationInterpolator {

  89.     /**
  90.      * Internal class used to construct the default {@link Lookup} map used by
  91.      * {@link ConfigurationInterpolator#getDefaultPrefixLookups()}.
  92.      */
  93.     static final class DefaultPrefixLookupsHolder {

  94.         /** Singleton instance, initialized with the system properties. */
  95.         static final DefaultPrefixLookupsHolder INSTANCE = new DefaultPrefixLookupsHolder(System.getProperties());

  96.         /**
  97.          * Add the prefix and lookup from {@code lookup} to {@code map}.
  98.          * @param lookup lookup to add
  99.          * @param map map to add to
  100.          */
  101.         private static void addLookup(final DefaultLookups lookup, final Map<String, Lookup> map) {
  102.             map.put(lookup.getPrefix(), lookup.getLookup());
  103.         }

  104.         /**
  105.          * Create the lookup map used when the user has requested no customization.
  106.          * @return default lookup map
  107.          */
  108.         private static Map<String, Lookup> createDefaultLookups() {
  109.             final Map<String, Lookup> lookupMap = new HashMap<>();

  110.             addLookup(DefaultLookups.BASE64_DECODER, lookupMap);
  111.             addLookup(DefaultLookups.BASE64_ENCODER, lookupMap);
  112.             addLookup(DefaultLookups.CONST, lookupMap);
  113.             addLookup(DefaultLookups.DATE, lookupMap);
  114.             addLookup(DefaultLookups.ENVIRONMENT, lookupMap);
  115.             addLookup(DefaultLookups.FILE, lookupMap);
  116.             addLookup(DefaultLookups.JAVA, lookupMap);
  117.             addLookup(DefaultLookups.LOCAL_HOST, lookupMap);
  118.             addLookup(DefaultLookups.PROPERTIES, lookupMap);
  119.             addLookup(DefaultLookups.RESOURCE_BUNDLE, lookupMap);
  120.             addLookup(DefaultLookups.SYSTEM_PROPERTIES, lookupMap);
  121.             addLookup(DefaultLookups.URL_DECODER, lookupMap);
  122.             addLookup(DefaultLookups.URL_ENCODER, lookupMap);
  123.             addLookup(DefaultLookups.XML, lookupMap);

  124.             return lookupMap;
  125.         }

  126.         /**
  127.          * Constructs a lookup map by parsing the given string. The string is expected to contain
  128.          * comma or space-separated names of values from the {@link DefaultLookups} enum.
  129.          * @param str string to parse; not null
  130.          * @return lookup map parsed from the given string
  131.          * @throws IllegalArgumentException if the string does not contain a valid default lookup
  132.          *      definition
  133.          */
  134.         private static Map<String, Lookup> parseLookups(final String str) {
  135.             final Map<String, Lookup> lookupMap = new HashMap<>();

  136.             try {
  137.                 for (final String lookupName : str.split("[\\s,]+")) {
  138.                     if (!lookupName.isEmpty()) {
  139.                         addLookup(DefaultLookups.valueOf(lookupName.toUpperCase()), lookupMap);
  140.                     }
  141.                 }
  142.             } catch (final IllegalArgumentException exc) {
  143.                 throw new IllegalArgumentException("Invalid default lookups definition: " + str, exc);
  144.             }

  145.             return lookupMap;
  146.         }

  147.         /** Default lookup map. */
  148.         private final Map<String, Lookup> defaultLookups;

  149.         /**
  150.          * Constructs a new instance initialized with the given properties.
  151.          * @param props initialization properties
  152.          */
  153.         DefaultPrefixLookupsHolder(final Properties props) {
  154.             final Map<String, Lookup> lookups = props.containsKey(DEFAULT_PREFIX_LOOKUPS_PROPERTY)
  155.                         ? parseLookups(props.getProperty(DEFAULT_PREFIX_LOOKUPS_PROPERTY))
  156.                         : createDefaultLookups();

  157.             defaultLookups = Collections.unmodifiableMap(lookups);
  158.         }

  159.         /**
  160.          * Gets the default prefix lookups map.
  161.          * @return default prefix lookups map
  162.          */
  163.         Map<String, Lookup> getDefaultPrefixLookups() {
  164.             return defaultLookups;
  165.         }
  166.     }

  167.     /** Class encapsulating the default logic to convert resolved variable values into strings.
  168.      * This class is thread-safe.
  169.      */
  170.     private static final class DefaultStringConverter implements Function<Object, String> {

  171.         /** Shared instance. */
  172.         static final DefaultStringConverter INSTANCE = new DefaultStringConverter();

  173.         /** {@inheritDoc} */
  174.         @Override
  175.         public String apply(final Object obj) {
  176.             return Objects.toString(extractSimpleValue(obj), null);
  177.         }

  178.         /** Attempt to extract a simple value from {@code obj} for use in string conversion.
  179.          * If the input represents a collection of some sort (for example, an iterable or array),
  180.          * the first item from the collection is returned.
  181.          * @param obj input object
  182.          * @return extracted simple object
  183.          */
  184.         private Object extractSimpleValue(final Object obj) {
  185.             if (!(obj instanceof String)) {
  186.                 if (obj instanceof Iterable) {
  187.                    return nextOrNull(((Iterable<?>) obj).iterator());
  188.                 }
  189.                 if (obj instanceof Iterator) {
  190.                     return nextOrNull((Iterator<?>) obj);
  191.                 }
  192.                 if (obj.getClass().isArray()) {
  193.                     return Array.getLength(obj) > 0
  194.                             ? Array.get(obj, 0)
  195.                             : null;
  196.                 }
  197.             }
  198.             return obj;
  199.         }

  200.         /** Return the next value from {@code it} or {@code null} if no values remain.
  201.          * @param <T> iterated type
  202.          * @param it iterator
  203.          * @return next value from {@code it} or {@code null} if no values remain
  204.          */
  205.         private <T> T nextOrNull(final Iterator<T> it) {
  206.             return it.hasNext()
  207.                     ? it.next()
  208.                     : null;
  209.         }
  210.     }

  211.     /**
  212.      * Name of the system property used to determine the lookups added by the
  213.      * {@link #getDefaultPrefixLookups()} method. Use of this property is only required
  214.      * in cases where the set of default lookups must be modified.
  215.      *
  216.      * @since 2.8.0
  217.      */
  218.     public static final String DEFAULT_PREFIX_LOOKUPS_PROPERTY =
  219.             "org.apache.commons.configuration2.interpol.ConfigurationInterpolator.defaultPrefixLookups";

  220.     /** Constant for the prefix separator. */
  221.     private static final char PREFIX_SEPARATOR = ':';

  222.     /** The variable prefix. */
  223.     private static final String VAR_START = "${";

  224.     /** The length of {@link #VAR_START}. */
  225.     private static final int VAR_START_LENGTH = VAR_START.length();

  226.     /** The variable suffix. */
  227.     private static final String VAR_END = "}";

  228.     /** The length of {@link #VAR_END}. */
  229.     private static final int VAR_END_LENGTH = VAR_END.length();

  230.     /**
  231.      * Creates a new instance based on the properties in the given specification object.
  232.      *
  233.      * @param spec the {@code InterpolatorSpecification}
  234.      * @return the newly created instance
  235.      */
  236.     private static ConfigurationInterpolator createInterpolator(final InterpolatorSpecification spec) {
  237.         final ConfigurationInterpolator ci = new ConfigurationInterpolator();
  238.         ci.addDefaultLookups(spec.getDefaultLookups());
  239.         ci.registerLookups(spec.getPrefixLookups());
  240.         ci.setParentInterpolator(spec.getParentInterpolator());
  241.         ci.setStringConverter(spec.getStringConverter());
  242.         return ci;
  243.     }

  244.     /**
  245.      * Extracts the variable name from a value that consists of a single variable.
  246.      *
  247.      * @param strValue the value
  248.      * @return the extracted variable name
  249.      */
  250.     private static String extractVariableName(final String strValue) {
  251.         return strValue.substring(VAR_START_LENGTH, strValue.length() - VAR_END_LENGTH);
  252.     }

  253.     /**
  254.      * Creates a new {@code ConfigurationInterpolator} instance based on the passed in specification object. If the
  255.      * {@code InterpolatorSpecification} already contains a {@code ConfigurationInterpolator} object, it is used directly.
  256.      * Otherwise, a new instance is created and initialized with the properties stored in the specification.
  257.      *
  258.      * @param spec the {@code InterpolatorSpecification} (must not be <strong>null</strong>)
  259.      * @return the {@code ConfigurationInterpolator} obtained or created based on the given specification
  260.      * @throws IllegalArgumentException if the specification is <strong>null</strong>
  261.      * @since 2.0
  262.      */
  263.     public static ConfigurationInterpolator fromSpecification(final InterpolatorSpecification spec) {
  264.         if (spec == null) {
  265.             throw new IllegalArgumentException("InterpolatorSpecification must not be null!");
  266.         }
  267.         return spec.getInterpolator() != null ? spec.getInterpolator() : createInterpolator(spec);
  268.     }

  269.     /**
  270.      * Gets a map containing the default prefix lookups. Every configuration object derived from
  271.      * {@code AbstractConfiguration} is by default initialized with a {@code ConfigurationInterpolator} containing
  272.      * these {@code Lookup} objects and their prefixes. The map cannot be modified.
  273.      *
  274.      * <p>
  275.      * All of the lookups present in the returned map are from {@link DefaultLookups}. However, not all of the
  276.      * available lookups are included by default. Specifically, lookups that can execute code (for example,
  277.      * {@link DefaultLookups#SCRIPT SCRIPT}) and those that can result in contact with remote servers (for example,
  278.      * {@link DefaultLookups#URL URL} and {@link DefaultLookups#DNS DNS}) are not included. If this behavior
  279.      * must be modified, users can define the {@value #DEFAULT_PREFIX_LOOKUPS_PROPERTY} system property
  280.      * with a comma-separated list of {@link DefaultLookups} enum names to be included in the set of defaults.
  281.      * For example, setting this system property to {@code "BASE64_ENCODER,ENVIRONMENT"} will only include the
  282.      * {@link DefaultLookups#BASE64_ENCODER BASE64_ENCODER} and
  283.      * {@link DefaultLookups#ENVIRONMENT ENVIRONMENT} lookups. Setting the property to the empty string will
  284.      * cause no defaults to be configured.
  285.      * </p>
  286.      *
  287.      * <table>
  288.      * <caption>Default Lookups</caption>
  289.      * <tr>
  290.      *  <th>Prefix</th>
  291.      *  <th>Lookup</th>
  292.      * </tr>
  293.      * <tr>
  294.      *  <td>"base64Decoder"</td>
  295.      *  <td>{@link DefaultLookups#BASE64_DECODER BASE64_DECODER}</td>
  296.      * </tr>
  297.      * <tr>
  298.      *  <td>"base64Encoder"</td>
  299.      *  <td>{@link DefaultLookups#BASE64_ENCODER BASE64_ENCODER}</td>
  300.      * </tr>
  301.      * <tr>
  302.      *  <td>"const"</td>
  303.      *  <td>{@link DefaultLookups#CONST CONST}</td>
  304.      * </tr>
  305.      * <tr>
  306.      *  <td>"date"</td>
  307.      *  <td>{@link DefaultLookups#DATE DATE}</td>
  308.      * </tr>
  309.      * <tr>
  310.      *  <td>"env"</td>
  311.      *  <td>{@link DefaultLookups#ENVIRONMENT ENVIRONMENT}</td>
  312.      * </tr>
  313.      * <tr>
  314.      *  <td>"file"</td>
  315.      *  <td>{@link DefaultLookups#FILE FILE}</td>
  316.      * </tr>
  317.      * <tr>
  318.      *  <td>"java"</td>
  319.      *  <td>{@link DefaultLookups#JAVA JAVA}</td>
  320.      * </tr>
  321.      * <tr>
  322.      *  <td>"localhost"</td>
  323.      *  <td>{@link DefaultLookups#LOCAL_HOST LOCAL_HOST}</td>
  324.      * </tr>
  325.      * <tr>
  326.      *  <td>"properties"</td>
  327.      *  <td>{@link DefaultLookups#PROPERTIES PROPERTIES}</td>
  328.      * </tr>
  329.      * <tr>
  330.      *  <td>"resourceBundle"</td>
  331.      *  <td>{@link DefaultLookups#RESOURCE_BUNDLE RESOURCE_BUNDLE}</td>
  332.      * </tr>
  333.      * <tr>
  334.      *  <td>"sys"</td>
  335.      *  <td>{@link DefaultLookups#SYSTEM_PROPERTIES SYSTEM_PROPERTIES}</td>
  336.      * </tr>
  337.      * <tr>
  338.      *  <td>"urlDecoder"</td>
  339.      *  <td>{@link DefaultLookups#URL_DECODER URL_DECODER}</td>
  340.      * </tr>
  341.      * <tr>
  342.      *  <td>"urlEncoder"</td>
  343.      *  <td>{@link DefaultLookups#URL_ENCODER URL_ENCODER}</td>
  344.      * </tr>
  345.      * <tr>
  346.      *  <td>"xml"</td>
  347.      *  <td>{@link DefaultLookups#XML XML}</td>
  348.      * </tr>
  349.      * </table>
  350.      *
  351.      * <table>
  352.      * <caption>Additional Lookups (not included by default)</caption>
  353.      * <tr>
  354.      *  <th>Prefix</th>
  355.      *  <th>Lookup</th>
  356.      * </tr>
  357.      * <tr>
  358.      *  <td>"dns"</td>
  359.      *  <td>{@link DefaultLookups#DNS DNS}</td>
  360.      * </tr>
  361.      * <tr>
  362.      *  <td>"url"</td>
  363.      *  <td>{@link DefaultLookups#URL URL}</td>
  364.      * </tr>
  365.      * <tr>
  366.      *  <td>"script"</td>
  367.      *  <td>{@link DefaultLookups#SCRIPT SCRIPT}</td>
  368.      * </tr>
  369.      * </table>
  370.      *
  371.      * @return a map with the default prefix {@code Lookup} objects and their prefixes
  372.      * @since 2.0
  373.      */
  374.     public static Map<String, Lookup> getDefaultPrefixLookups() {
  375.         return DefaultPrefixLookupsHolder.INSTANCE.getDefaultPrefixLookups();
  376.     }

  377.     /**
  378.      * Utility method for obtaining a {@code Lookup} object in a safe way. This method always returns a non-<strong>null</strong>
  379.      * {@code Lookup} object. If the passed in {@code Lookup} is not <strong>null</strong>, it is directly returned. Otherwise, result
  380.      * is a dummy {@code Lookup} which does not provide any values.
  381.      *
  382.      * @param lookup the {@code Lookup} to check
  383.      * @return a non-<strong>null</strong> {@code Lookup} object
  384.      * @since 2.0
  385.      */
  386.     public static Lookup nullSafeLookup(Lookup lookup) {
  387.         if (lookup == null) {
  388.             lookup = DummyLookup.INSTANCE;
  389.         }
  390.         return lookup;
  391.     }

  392.     /** A map with the currently registered lookup objects. */
  393.     private final Map<String, Lookup> prefixLookups;

  394.     /** Stores the default lookup objects. */
  395.     private final List<Lookup> defaultLookups;

  396.     /** The helper object performing variable substitution. */
  397.     private final StringSubstitutor substitutor;

  398.     /** Stores a parent interpolator objects if the interpolator is nested hierarchically. */
  399.     private volatile ConfigurationInterpolator parentInterpolator;

  400.     /** Function used to convert interpolated values to strings. */
  401.     private volatile Function<Object, String> stringConverter = DefaultStringConverter.INSTANCE;

  402.     /**
  403.      * Creates a new instance of {@code ConfigurationInterpolator}.
  404.      */
  405.     public ConfigurationInterpolator() {
  406.         prefixLookups = new ConcurrentHashMap<>();
  407.         defaultLookups = new CopyOnWriteArrayList<>();
  408.         substitutor = initSubstitutor();
  409.     }

  410.     /**
  411.      * Adds a default {@code Lookup} object. Default {@code Lookup} objects are queried (in the order they were added) for
  412.      * all variables without a special prefix. If no default {@code Lookup} objects are present, such variables won't be
  413.      * processed.
  414.      *
  415.      * @param defaultLookup the default {@code Lookup} object to be added (must not be <strong>null</strong>)
  416.      * @throws IllegalArgumentException if the {@code Lookup} object is <strong>null</strong>
  417.      */
  418.     public void addDefaultLookup(final Lookup defaultLookup) {
  419.         defaultLookups.add(defaultLookup);
  420.     }

  421.     /**
  422.      * Adds all {@code Lookup} objects in the given collection as default lookups. The collection can be <strong>null</strong>, then
  423.      * this method has no effect. It must not contain <strong>null</strong> entries.
  424.      *
  425.      * @param lookups the {@code Lookup} objects to be added as default lookups
  426.      * @throws IllegalArgumentException if the collection contains a <strong>null</strong> entry
  427.      */
  428.     public void addDefaultLookups(final Collection<? extends Lookup> lookups) {
  429.         if (lookups != null) {
  430.             defaultLookups.addAll(lookups);
  431.         }
  432.     }

  433.     /**
  434.      * Deregisters the {@code Lookup} object for the specified prefix at this instance. It will be removed from this
  435.      * instance.
  436.      *
  437.      * @param prefix the variable prefix
  438.      * @return a flag whether for this prefix a lookup object had been registered
  439.      */
  440.     public boolean deregisterLookup(final String prefix) {
  441.         return prefixLookups.remove(prefix) != null;
  442.     }

  443.     /**
  444.      * Obtains the lookup object for the specified prefix. This method is called by the {@code lookup()} method. This
  445.      * implementation will check whether a lookup object is registered for the given prefix. If not, a <strong>null</strong> lookup
  446.      * object will be returned (never <strong>null</strong>).
  447.      *
  448.      * @param prefix the prefix
  449.      * @return the lookup object to be used for this prefix
  450.      */
  451.     protected Lookup fetchLookupForPrefix(final String prefix) {
  452.         return nullSafeLookup(prefixLookups.get(prefix));
  453.     }

  454.     /**
  455.      * Gets a collection with the default {@code Lookup} objects added to this {@code ConfigurationInterpolator}. These
  456.      * objects are not associated with a variable prefix. The returned list is a snapshot copy of the internal collection of
  457.      * default lookups; so manipulating it does not affect this instance.
  458.      *
  459.      * @return the default lookup objects
  460.      */
  461.     public List<Lookup> getDefaultLookups() {
  462.         return new ArrayList<>(defaultLookups);
  463.     }

  464.     /**
  465.      * Gets a map with the currently registered {@code Lookup} objects and their prefixes. This is a snapshot copy of the
  466.      * internally used map. So modifications of this map do not effect this instance.
  467.      *
  468.      * @return a copy of the map with the currently registered {@code Lookup} objects
  469.      */
  470.     public Map<String, Lookup> getLookups() {
  471.         return new HashMap<>(prefixLookups);
  472.     }

  473.     /**
  474.      * Gets the parent {@code ConfigurationInterpolator}.
  475.      *
  476.      * @return the parent {@code ConfigurationInterpolator} (can be <strong>null</strong>)
  477.      */
  478.     public ConfigurationInterpolator getParentInterpolator() {
  479.         return this.parentInterpolator;
  480.     }

  481.     /** Gets the function used to convert interpolated values to strings.
  482.      * @return function used to convert interpolated values to strings
  483.      */
  484.     public Function<Object, String> getStringConverter() {
  485.         return stringConverter;
  486.     }

  487.     /**
  488.      * Creates and initializes a {@code StringSubstitutor} object which is used for variable substitution. This
  489.      * {@code StringSubstitutor} is assigned a specialized lookup object implementing the correct variable resolving
  490.      * algorithm.
  491.      *
  492.      * @return the {@code StringSubstitutor} used by this object
  493.      */
  494.     private StringSubstitutor initSubstitutor() {
  495.         return new StringSubstitutor(key -> {
  496.             final Object value = resolve(key);
  497.             return value != null
  498.                 ? stringConverter.apply(value)
  499.                 : null;
  500.         });
  501.     }

  502.     /**
  503.      * Performs interpolation of the passed in value. If the value is of type {@code String}, this method checks
  504.      * whether it contains variables. If so, all variables are replaced by their current values (if possible). For
  505.      * non string arguments, the value is returned without changes. In the special case where the value is a string
  506.      * consisting of a single variable reference, the interpolated variable value is <em>not</em> converted to a
  507.      * string before returning, so that callers can access the raw value. However, if the variable is part of a larger
  508.      * interpolated string, then the variable value is converted to a string using the configured
  509.      * {@link #getStringConverter() string converter}. (See the discussion on string conversion in the class
  510.      * documentation for more details.)
  511.      *
  512.      * <p><strong>Examples</strong></p>
  513.      * <p>
  514.      * For the following examples, assume that the default string conversion function is in place and that the
  515.      * variable {@code i} maps to the integer value {@code 42}.
  516.      * </p>
  517.      * <pre>
  518.      *      interpolator.interpolate(1) &rarr; 1 // non-string argument returned unchanged
  519.      *      interpolator.interpolate("${i}") &rarr; 42 // single variable value returned with raw type
  520.      *      interpolator.interpolate("answer = ${i}") &rarr; "answer = 42" // variable value converted to string
  521.      * </pre>
  522.      *
  523.      * @param value the value to be interpolated
  524.      * @return the interpolated value
  525.      */
  526.     public Object interpolate(final Object value) {
  527.         if (value instanceof String) {
  528.             final String strValue = (String) value;
  529.             if (isSingleVariable(strValue)) {
  530.                 final Object resolvedValue = resolveSingleVariable(strValue);
  531.                 if (resolvedValue != null && !(resolvedValue instanceof String)) {
  532.                     // If the value is again a string, it needs no special
  533.                     // treatment; it may also contain further variables which
  534.                     // must be resolved; therefore, the default mechanism is
  535.                     // applied.
  536.                     return resolvedValue;
  537.                 }
  538.             }
  539.             return substitutor.replace(strValue);
  540.         }
  541.         return value;
  542.     }

  543.     /**
  544.      * Sets a flag that variable names can contain other variables. If enabled, variable substitution is also done in
  545.      * variable names.
  546.      *
  547.      * @return the substitution in variables flag
  548.      */
  549.     public boolean isEnableSubstitutionInVariables() {
  550.         return substitutor.isEnableSubstitutionInVariables();
  551.     }

  552.     /**
  553.      * Checks whether a value to be interpolated consists of single, simple variable reference, for example,
  554.      * <code>${myvar}</code>. In this case, the variable is resolved directly without using the
  555.      * {@code StringSubstitutor}.
  556.      *
  557.      * @param strValue the value to be interpolated
  558.      * @return {@code true} if the value contains a single, simple variable reference
  559.      */
  560.     private boolean isSingleVariable(final String strValue) {
  561.         return strValue.startsWith(VAR_START)
  562.                 && strValue.indexOf(VAR_END, VAR_START_LENGTH) == strValue.length() - VAR_END_LENGTH;
  563.     }

  564.     /**
  565.      * Returns an unmodifiable set with the prefixes, for which {@code Lookup} objects are registered at this instance. This
  566.      * means that variables with these prefixes can be processed.
  567.      *
  568.      * @return a set with the registered variable prefixes
  569.      */
  570.     public Set<String> prefixSet() {
  571.         return Collections.unmodifiableSet(prefixLookups.keySet());
  572.     }

  573.     /**
  574.      * Registers the given {@code Lookup} object for the specified prefix at this instance. From now on this lookup object
  575.      * will be used for variables that have the specified prefix.
  576.      *
  577.      * @param prefix the variable prefix (must not be <strong>null</strong>)
  578.      * @param lookup the {@code Lookup} object to be used for this prefix (must not be <strong>null</strong>)
  579.      * @throws IllegalArgumentException if either the prefix or the {@code Lookup} object is <strong>null</strong>
  580.      */
  581.     public void registerLookup(final String prefix, final Lookup lookup) {
  582.         if (prefix == null) {
  583.             throw new IllegalArgumentException("Prefix for lookup object must not be null!");
  584.         }
  585.         if (lookup == null) {
  586.             throw new IllegalArgumentException("Lookup object must not be null!");
  587.         }
  588.         prefixLookups.put(prefix, lookup);
  589.     }

  590.     /**
  591.      * Registers all {@code Lookup} objects in the given map with their prefixes at this {@code ConfigurationInterpolator}.
  592.      * Using this method multiple {@code Lookup} objects can be registered at once. If the passed in map is <strong>null</strong>,
  593.      * this method does not have any effect.
  594.      *
  595.      * @param lookups the map with lookups to register (may be <strong>null</strong>)
  596.      * @throws IllegalArgumentException if the map contains <strong>entries</strong>
  597.      */
  598.     public void registerLookups(final Map<String, ? extends Lookup> lookups) {
  599.         if (lookups != null) {
  600.             prefixLookups.putAll(lookups);
  601.         }
  602.     }

  603.     /**
  604.      * Removes the specified {@code Lookup} object from the list of default {@code Lookup}s.
  605.      *
  606.      * @param lookup the {@code Lookup} object to be removed
  607.      * @return a flag whether this {@code Lookup} object actually existed and was removed
  608.      */
  609.     public boolean removeDefaultLookup(final Lookup lookup) {
  610.         return defaultLookups.remove(lookup);
  611.     }

  612.     /**
  613.      * Resolves the specified variable. This implementation tries to extract a variable prefix from the given variable name
  614.      * (the first colon (':') is used as prefix separator). It then passes the name of the variable with the prefix stripped
  615.      * to the lookup object registered for this prefix. If no prefix can be found or if the associated lookup object cannot
  616.      * resolve this variable, the default lookup objects are used. If this is not successful either and a parent
  617.      * {@code ConfigurationInterpolator} is available, this object is asked to resolve the variable.
  618.      *
  619.      * @param var the name of the variable whose value is to be looked up which may contain a prefix.
  620.      * @return the value of this variable or <strong>null</strong> if it cannot be resolved
  621.      */
  622.     public Object resolve(final String var) {
  623.         if (var == null) {
  624.             return null;
  625.         }

  626.         final int prefixPos = var.indexOf(PREFIX_SEPARATOR);
  627.         if (prefixPos >= 0) {
  628.             final String prefix = var.substring(0, prefixPos);
  629.             final String name = var.substring(prefixPos + 1);
  630.             final Object value = fetchLookupForPrefix(prefix).lookup(name);
  631.             if (value != null) {
  632.                 return value;
  633.             }
  634.         }

  635.         for (final Lookup lookup : defaultLookups) {
  636.             final Object value = lookup.lookup(var);
  637.             if (value != null) {
  638.                 return value;
  639.             }
  640.         }

  641.         final ConfigurationInterpolator parent = getParentInterpolator();
  642.         if (parent != null) {
  643.             return getParentInterpolator().resolve(var);
  644.         }
  645.         return null;
  646.     }

  647.     /**
  648.      * Interpolates a string value that consists of a single variable.
  649.      *
  650.      * @param strValue the string to be interpolated
  651.      * @return the resolved value or <strong>null</strong> if resolving failed
  652.      */
  653.     private Object resolveSingleVariable(final String strValue) {
  654.         return resolve(extractVariableName(strValue));
  655.     }

  656.     /**
  657.      * Sets the flag whether variable names can contain other variables. This flag corresponds to the
  658.      * {@code enableSubstitutionInVariables} property of the underlying {@code StringSubstitutor} object.
  659.      *
  660.      * @param f the new value of the flag
  661.      */
  662.     public void setEnableSubstitutionInVariables(final boolean f) {
  663.         substitutor.setEnableSubstitutionInVariables(f);
  664.     }

  665.     /**
  666.      * Sets the parent {@code ConfigurationInterpolator}. This object is used if the {@code Lookup} objects registered at
  667.      * this object cannot resolve a variable.
  668.      *
  669.      * @param parentInterpolator the parent {@code ConfigurationInterpolator} object (can be <strong>null</strong>)
  670.      */
  671.     public void setParentInterpolator(final ConfigurationInterpolator parentInterpolator) {
  672.         this.parentInterpolator = parentInterpolator;
  673.     }

  674.     /** Sets the function used to convert interpolated values to strings. Pass
  675.      * {@code null} to use the default conversion function.
  676.      * @param stringConverter function used to convert interpolated values to strings
  677.      *      or {@code null} to use the default conversion function
  678.      */
  679.     public void setStringConverter(final Function<Object, String> stringConverter) {
  680.         this.stringConverter = stringConverter != null
  681.                 ? stringConverter
  682.                 : DefaultStringConverter.INSTANCE;
  683.     }
  684. }