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