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