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