    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.lang.reflect.Field;
    020import java.util.HashMap;
    021import java.util.Map;
    022
    023import org.apache.commons.lang3.ClassUtils;
    024import org.apache.commons.logging.Log;
    025import org.apache.commons.logging.LogFactory;
    026
    027/**
    028 * <p>
    029 * A specialized lookup implementation that allows access to constant fields of
    030 * classes.
    031 * </p>
    032 * <p>
    033 * Sometimes it is necessary in a configuration file to refer to a constant
    034 * defined in a class. This can be done with this lookup implementation.
    035 * Variable names passed in must be of the form
    036 * {@code mypackage.MyClass.FIELD}. The {@code lookup()} method
    037 * will split the passed in string at the last dot, separating the fully
    038 * qualified class name and the name of the constant (i.e. <strong>static final</strong>)
    039 * member field. Then the class is loaded and the field's value is obtained
    040 * using reflection.
    041 * </p>
    042 * <p>
    043 * Once retrieved values are cached for fast access. This class is thread-safe.
    044 * It can be used as a standard (i.e. global) lookup object and serve multiple
    045 * clients concurrently.
    046 * </p>
    047 *
    048 * @since 1.4
    049 */
    050public class ConstantLookup implements Lookup
    051{
    052    /** Constant for the field separator. */
    053    private static final char FIELD_SEPRATOR = '.';
    054
    055    /** An internally used cache for already retrieved values. */
    056    private static Map<String, Object> constantCache = new HashMap<>();
    057
    058    /** The logger. */
    059    private final Log log = LogFactory.getLog(getClass());
    060
    061    /**
    062     * Tries to resolve the specified variable. The passed in variable name is
    063     * interpreted as the name of a <b>static final</b> member field of a
    064     * class. If the value has already been obtained, it can be retrieved from
    065     * an internal cache. Otherwise this method will invoke the
    066     * {@code resolveField()} method and pass in the name of the class
    067     * and the field.
    068     *
    069     * @param var the name of the variable to be resolved
    070     * @return the value of this variable or <b>null</b> if it cannot be
    071     * resolved
    072     */
    073    @Override
    074    public Object lookup(final String var)
    075    {
    076        if (var == null)
    077        {
    078            return null;
    079        }
    080
    081        Object result;
    082        synchronized (constantCache)
    083        {
    084            result = constantCache.get(var);
    085        }
    086        if (result != null)
    087        {
    088            return result;
    089        }
    090
    091        final int fieldPos = var.lastIndexOf(FIELD_SEPRATOR);
    092        if (fieldPos < 0)
    093        {
    094            return null;
    095        }
    096        try
    097        {
    098            final Object value = resolveField(var.substring(0, fieldPos), var
    099                    .substring(fieldPos + 1));
    100            if (value != null)
    101            {
    102                synchronized (constantCache)
    103                {
    104                    // In worst case, the value will be fetched multiple times
    105                    // because of this lax synchronization, but for constant
    106                    // values this shouldn't be a problem.
    107                    constantCache.put(var, value);
    108                }
    109                result = value;
    110            }
    111        }
    112        catch (final Exception ex)
    113        {
    114            log.warn("Could not obtain value for variable " + var, ex);
    115        }
    116
    117        return result;
    118    }
    119
    120    /**
    121     * Clears the shared cache with the so far resolved constants.
    122     */
    123    public static void clear()
    124    {
    125        synchronized (constantCache)
    126        {
    127            constantCache.clear();
    128        }
    129    }
    130
    131    /**
    132     * Determines the value of the specified constant member field of a class.
    133     * This implementation will call {@code fetchClass()} to obtain the
    134     * {@code java.lang.Class} object for the target class. Then it will
    135     * use reflection to obtain the field's value. For this to work the field
    136     * must be accessable.
    137     *
    138     * @param className the name of the class
    139     * @param fieldName the name of the member field of that class to read
    140     * @return the field's value
    141     * @throws Exception if an error occurs
    142     */
    143    protected Object resolveField(final String className, final String fieldName)
    144            throws Exception
    145    {
    146        final Class<?> clazz = fetchClass(className);
    147        final Field field = clazz.getField(fieldName);
    148        return field.get(null);
    149    }
    150
    151    /**
    152     * Loads the class with the specified name. If an application has special
    153     * needs regarding the class loaders to be used, it can hook in here. This
    154     * implementation delegates to the {@code getClass()} method of
    155     * Commons Lang's
    156     * <code><a href="https://commons.apache.org/lang/api-release/org/apache/commons/lang/ClassUtils.html">
    157     * ClassUtils</a></code>.
    158     *
    159     * @param className the name of the class to be loaded
    160     * @return the corresponding class object
    161     * @throws ClassNotFoundException if the class cannot be loaded
    162     */
    163    protected Class<?> fetchClass(final String className) throws ClassNotFoundException
    164    {
    165        return ClassUtils.getClass(className);
    166    }
    167}