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