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.configuration.interpol;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Set;
022
023import org.apache.commons.lang.text.StrLookup;
024
025/**
026 * <p>
027 * A class that handles interpolation (variable substitution) for configuration
028 * objects.
029 * </p>
030 * <p>
031 * Each instance of {@code AbstractConfiguration} is associated with an
032 * object of this class. All interpolation tasks are delegated to this object.
033 * </p>
034 * <p>
035 * {@code ConfigurationInterpolator} works together with the
036 * {@code StrSubstitutor} class from <a
037 * href="http://commons.apache.org/lang">Commons Lang</a>. By extending
038 * {@code StrLookup} it is able to provide values for variables that
039 * appear in expressions.
040 * </p>
041 * <p>
042 * The basic idea of this class is that it can maintain a set of primitive
043 * {@code StrLookup} objects, each of which is identified by a special
044 * prefix. The variables to be processed have the form
045 * <code>${prefix:name}</code>. {@code ConfigurationInterpolator} will
046 * extract the prefix and determine, which primitive lookup object is registered
047 * for it. Then the name of the variable is passed to this object to obtain the
048 * actual value. It is also possible to define a default lookup object, which
049 * will be used for variables that do not have a prefix or that cannot be
050 * resolved by their associated lookup object.
051 * </p>
052 * <p>
053 * When a new instance of this class is created it is initialized with a default
054 * set of primitive lookup objects. This set can be customized using the static
055 * methods {@code registerGlobalLookup()} and
056 * {@code deregisterGlobalLookup()}. Per default it contains the
057 * following standard lookup objects:
058 * </p>
059 * <p>
060 * <table border="1">
061 * <tr>
062 * <th>Prefix</th>
063 * <th>Lookup object</th>
064 * </tr>
065 * <tr>
066 * <td valign="top">sys</td>
067 * <td>With this prefix a lookup object is associated that is able to resolve
068 * system properties.</td>
069 * </tr>
070 * <tr>
071 * <td valign="top">const</td>
072 * <td>The {@code const} prefix indicates that a variable is to be
073 * interpreted as a constant member field of a class (i.e. a field with the
074 * <b>static final</b> modifiers). The name of the variable must be of the form
075 * {@code <full qualified class name>.<field name>}, e.g.
076 * {@code org.apache.commons.configuration.interpol.ConfigurationInterpolator.PREFIX_CONSTANTS}.
077 * </td>
078 * </tr>
079 * </table>
080 * </p>
081 * <p>
082 * After an instance has been created the current set of lookup objects can be
083 * modified using the {@code registerLookup()} and
084 * {@code deregisterLookup()} methods. The default lookup object (that is
085 * invoked for variables without a prefix) can be set with the
086 * {@code setDefaultLookup()} method. (If a
087 * {@code ConfigurationInterpolator} instance is created by a
088 * configuration object, this lookup points to the configuration itself, so that
089 * variables are resolved using the configuration's properties. This ensures
090 * backward compatibility to earlier version of Commons Configuration.)
091 * </p>
092 * <p>
093 * Implementation node: Instances of this class are not thread-safe related to
094 * modifications of their current set of registered lookup objects. It is
095 * intended that each instance is associated with a single
096 * {@code Configuration} object and used for its interpolation tasks.
097 * </p>
098 *
099 * @version $Id: ConfigurationInterpolator.java 1295276 2012-02-29 21:11:35Z oheger $
100 * @since 1.4
101 * @author <a
102 * href="http://commons.apache.org/configuration/team-list.html">Commons
103 * Configuration team</a>
104 */
105public class ConfigurationInterpolator extends StrLookup
106{
107    /**
108     * Constant for the prefix of the standard lookup object for resolving
109     * system properties.
110     */
111    public static final String PREFIX_SYSPROPERTIES = "sys";
112
113    /**
114     * Constant for the prefix of the standard lookup object for resolving
115     * constant values.
116     */
117    public static final String PREFIX_CONSTANTS = "const";
118
119    /**
120     * Constant for the prefix of the standard lookup object for resolving
121     * environment properties.
122     * @since 1.7
123     */
124    public static final String PREFIX_ENVIRONMENT = "env";
125
126    /** Constant for the prefix separator. */
127    private static final char PREFIX_SEPARATOR = ':';
128
129    /** A map with the globally registered lookup objects. */
130    private static Map<String, StrLookup> globalLookups;
131
132    /** A map with the locally registered lookup objects. */
133    private Map<String, StrLookup> localLookups;
134
135    /** Stores the default lookup object. */
136    private StrLookup defaultLookup;
137
138    /** Stores a parent interpolator objects if the interpolator is nested hierarchically. */
139    private ConfigurationInterpolator parentInterpolator;
140
141    /**
142     * Creates a new instance of {@code ConfigurationInterpolator}.
143     */
144    public ConfigurationInterpolator()
145    {
146        synchronized (globalLookups)
147        {
148            localLookups = new HashMap<String, StrLookup>(globalLookups);
149        }
150    }
151
152    /**
153     * Registers the given lookup object for the specified prefix globally. This
154     * means that all instances that are created later will use this lookup
155     * object for this prefix. If for this prefix a lookup object is already
156     * registered, the new lookup object will replace the old one. Note that the
157     * lookup objects registered here will be shared between multiple clients.
158     * So they should be thread-safe.
159     *
160     * @param prefix the variable prefix (must not be <b>null</b>)
161     * @param lookup the lookup object to be used for this prefix (must not be
162     * <b>null</b>)
163     */
164    public static void registerGlobalLookup(String prefix, StrLookup lookup)
165    {
166        if (prefix == null)
167        {
168            throw new IllegalArgumentException(
169                    "Prefix for lookup object must not be null!");
170        }
171        if (lookup == null)
172        {
173            throw new IllegalArgumentException(
174                    "Lookup object must not be null!");
175        }
176        synchronized (globalLookups)
177        {
178            globalLookups.put(prefix, lookup);
179        }
180    }
181
182    /**
183     * Deregisters the global lookup object for the specified prefix. This means
184     * that this lookup object won't be available for later created instances
185     * any more. For already existing instances this operation does not have any
186     * impact.
187     *
188     * @param prefix the variable prefix
189     * @return a flag whether for this prefix a lookup object had been
190     * registered
191     */
192    public static boolean deregisterGlobalLookup(String prefix)
193    {
194        synchronized (globalLookups)
195        {
196            return globalLookups.remove(prefix) != null;
197        }
198    }
199
200    /**
201     * Registers the given lookup object for the specified prefix at this
202     * instance. From now on this lookup object will be used for variables that
203     * have the specified prefix.
204     *
205     * @param prefix the variable prefix (must not be <b>null</b>)
206     * @param lookup the lookup object to be used for this prefix (must not be
207     * <b>null</b>)
208     */
209    public void registerLookup(String prefix, StrLookup lookup)
210    {
211        if (prefix == null)
212        {
213            throw new IllegalArgumentException(
214                    "Prefix for lookup object must not be null!");
215        }
216        if (lookup == null)
217        {
218            throw new IllegalArgumentException(
219                    "Lookup object must not be null!");
220        }
221        localLookups.put(prefix, lookup);
222    }
223
224    /**
225     * Deregisters the lookup object for the specified prefix at this instance.
226     * It will be removed from this instance.
227     *
228     * @param prefix the variable prefix
229     * @return a flag whether for this prefix a lookup object had been
230     * registered
231     */
232    public boolean deregisterLookup(String prefix)
233    {
234        return localLookups.remove(prefix) != null;
235    }
236
237    /**
238     * Returns a set with the prefixes, for which lookup objects are registered
239     * at this instance. This means that variables with these prefixes can be
240     * processed.
241     *
242     * @return a set with the registered variable prefixes
243     */
244    public Set<String> prefixSet()
245    {
246        return localLookups.keySet();
247    }
248
249    /**
250     * Returns the default lookup object.
251     *
252     * @return the default lookup object
253     */
254    public StrLookup getDefaultLookup()
255    {
256        return defaultLookup;
257    }
258
259    /**
260     * Sets the default lookup object. This lookup object will be used for all
261     * variables without a special prefix. If it is set to <b>null</b>, such
262     * variables won't be processed.
263     *
264     * @param defaultLookup the new default lookup object
265     */
266    public void setDefaultLookup(StrLookup defaultLookup)
267    {
268        this.defaultLookup = defaultLookup;
269    }
270
271    /**
272     * Resolves the specified variable. This implementation will try to extract
273     * a variable prefix from the given variable name (the first colon (':') is
274     * used as prefix separator). It then passes the name of the variable with
275     * the prefix stripped to the lookup object registered for this prefix. If
276     * no prefix can be found or if the associated lookup object cannot resolve
277     * this variable, the default lookup object will be used.
278     *
279     * @param var the name of the variable whose value is to be looked up
280     * @return the value of this variable or <b>null</b> if it cannot be
281     * resolved
282     */
283    @Override
284    public String lookup(String var)
285    {
286        if (var == null)
287        {
288            return null;
289        }
290
291        int prefixPos = var.indexOf(PREFIX_SEPARATOR);
292        if (prefixPos >= 0)
293        {
294            String prefix = var.substring(0, prefixPos);
295            String name = var.substring(prefixPos + 1);
296            String value = fetchLookupForPrefix(prefix).lookup(name);
297            if (value == null && getParentInterpolator() != null)
298            {
299                value = getParentInterpolator().lookup(name);
300            }
301            if (value != null)
302            {
303                return value;
304            }
305        }
306        String value = fetchNoPrefixLookup().lookup(var);
307        if (value == null && getParentInterpolator() != null)
308        {
309            value = getParentInterpolator().lookup(var);
310        }
311        return value;
312    }
313
314    /**
315     * Returns the lookup object to be used for variables without a prefix. This
316     * implementation will check whether a default lookup object was set. If
317     * this is the case, it will be returned. Otherwise a <b>null</b> lookup
318     * object will be returned (never <b>null</b>).
319     *
320     * @return the lookup object to be used for variables without a prefix
321     */
322    protected StrLookup fetchNoPrefixLookup()
323    {
324        return (getDefaultLookup() != null) ? getDefaultLookup() : StrLookup.noneLookup();
325    }
326
327    /**
328     * Obtains the lookup object for the specified prefix. This method is called
329     * by the {@code lookup()} method. This implementation will check
330     * whether a lookup object is registered for the given prefix. If not, a
331     * <b>null</b> lookup object will be returned (never <b>null</b>).
332     *
333     * @param prefix the prefix
334     * @return the lookup object to be used for this prefix
335     */
336    protected StrLookup fetchLookupForPrefix(String prefix)
337    {
338        StrLookup lookup = localLookups.get(prefix);
339        if (lookup == null)
340        {
341            lookup = StrLookup.noneLookup();
342        }
343        return lookup;
344    }
345
346    /**
347     * Registers the local lookup instances for the given interpolator.
348     *
349     * @param interpolator the instance receiving the local lookups
350     * @since upcoming
351     */
352    public void registerLocalLookups(ConfigurationInterpolator interpolator)
353    {
354        interpolator.localLookups.putAll(localLookups);
355    }
356
357    /**
358     * Sets the parent interpolator. This object is used if the interpolation is nested
359     * hierarchically and the current interpolation object cannot resolve a variable.
360     *
361     * @param parentInterpolator the parent interpolator object or <b>null</b>
362     * @since upcoming
363     */
364    public void setParentInterpolator(ConfigurationInterpolator parentInterpolator)
365    {
366        this.parentInterpolator = parentInterpolator;
367    }
368
369    /**
370     * Requests the parent interpolator. This object is used if the interpolation is nested
371     * hierarchically and the current interpolation
372     *
373     * @return the parent interpolator or <b>null</b>
374     * @since upcoming
375     */
376    public ConfigurationInterpolator getParentInterpolator()
377    {
378        return this.parentInterpolator;
379    }
380
381    // static initializer, sets up the map with the standard lookups
382    static
383    {
384        globalLookups = new HashMap<String, StrLookup>();
385        globalLookups.put(PREFIX_SYSPROPERTIES, StrLookup.systemPropertiesLookup());
386        globalLookups.put(PREFIX_CONSTANTS, new ConstantLookup());
387        globalLookups.put(PREFIX_ENVIRONMENT, new EnvironmentLookup());
388    }
389}