    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</code>. The <code>lookup()</code> 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 588329 2007-10-25 20:01:31Z 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 constantCache = new HashMap();
    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()</code> 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        public String lookup(String var)
    079        {
    080            if (var == null)
    081            {
    082                return null;
    083            }
    084
    085            String result;
    086            synchronized (constantCache)
    087            {
    088                result = (String) constantCache.get(var);
    089            }
    090            if (result != null)
    091            {
    092                return result;
    093            }
    094
    095            int fieldPos = var.lastIndexOf(FIELD_SEPRATOR);
    096            if (fieldPos < 0)
    097            {
    098                return null;
    099            }
    100            try
    101            {
    102                Object value = resolveField(var.substring(0, fieldPos), var
    103                        .substring(fieldPos + 1));
    104                if (value != null)
    105                {
    106                    synchronized (constantCache)
    107                    {
    108                        // In worst case, the value will be fetched multiple times
    109                        // because of this lax synchronisation, but for constant
    110                        // values this shouldn't be a problem.
    111                        constantCache.put(var, String.valueOf(value));
    112                    }
    113                    result = value.toString();
    114                }
    115            }
    116            catch (Exception ex)
    117            {
    118                log.warn("Could not obtain value for variable " + var, ex);
    119            }
    120
    121            return result;
    122        }
    123
    124        /**
    125         * Clears the shared cache with the so far resolved constants.
    126         */
    127        public static void clear()
    128        {
    129            synchronized (constantCache)
    130            {
    131                constantCache.clear();
    132            }
    133        }
    134
    135        /**
    136         * Determines the value of the specified constant member field of a class.
    137         * This implementation will call <code>fetchClass()</code> to obtain the
    138         * <code>java.lang.Class</code> object for the target class. Then it will
    139         * use reflection to obtain the field's value. For this to work the field
    140         * must be accessable.
    141         *
    142         * @param className the name of the class
    143         * @param fieldName the name of the member field of that class to read
    144         * @return the field's value
    145         * @throws Exception if an error occurs
    146         */
    147        protected Object resolveField(String className, String fieldName)
    148                throws Exception
    149        {
    150            Class clazz = fetchClass(className);
    151            Field field = clazz.getField(fieldName);
    152            return field.get(null);
    153        }
    154
    155        /**
    156         * Loads the class with the specified name. If an application has special
    157         * needs regarding the class loaders to be used, it can hook in here. This
    158         * implementation delegates to the <code>getClass()</code> method of
    159         * Commons Lang's
    160         * <code><a href="http://commons.apache.org/lang/api-release/org/apache/commons/lang/ClassUtils.html">
    161         * ClassUtils</a></code>.
    162         *
    163         * @param className the name of the class to be loaded
    164         * @return the corresponding class object
    165         * @throws ClassNotFoundException if the class cannot be loaded
    166         */
    167        protected Class fetchClass(String className) throws ClassNotFoundException
    168        {
    169            return ClassUtils.getClass(className);
    170        }
    171    }