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}