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 490375 2006-12-26 21:28:04Z oheger $
050: * @since 1.4
051: * @author <a
052: * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
053: * Configuration team</a>
054: */
055: public class ConstantLookup extends StrLookup {
056: /** Constant for the field separator. */
057: private static final char FIELD_SEPRATOR = '.';
058:
059: /** An internally used cache for already retrieved values. */
060: private static Map constantCache = new HashMap();
061:
062: /** The logger. */
063: private Log log = LogFactory.getLog(getClass());
064:
065: /**
066: * Tries to resolve the specified variable. The passed in variable name is
067: * interpreted as the name of a <b>static final</b> member field of a
068: * class. If the value has already been obtained, it can be retrieved from
069: * an internal cache. Otherwise this method will invoke the
070: * <code>resolveField()</code> method and pass in the name of the class
071: * and the field.
072: *
073: * @param var the name of the variable to be resolved
074: * @return the value of this variable or <b>null</b> if it cannot be
075: * resolved
076: */
077: public String lookup(String var) {
078: if (var == null) {
079: return null;
080: }
081:
082: String result;
083: synchronized (constantCache) {
084: result = (String) constantCache.get(var);
085: }
086: if (result != null) {
087: return result;
088: }
089:
090: int fieldPos = var.lastIndexOf(FIELD_SEPRATOR);
091: if (fieldPos < 0) {
092: return null;
093: }
094: try {
095: Object value = resolveField(var.substring(0, fieldPos), var
096: .substring(fieldPos + 1));
097: if (value != null) {
098: synchronized (constantCache) {
099: // In worst case, the value will be fetched multiple times
100: // because of this lax synchronisation, but for constant
101: // values
102: // this shouldn't be a problem.
103: constantCache.put(var, value);
104: }
105: result = value.toString();
106: }
107: } catch (Exception ex) {
108: log.warn("Could not obtain value for variable " + var, ex);
109: }
110:
111: return result;
112: }
113:
114: /**
115: * Clears the shared cache with the so far resolved constants.
116: */
117: public static void clear() {
118: synchronized (constantCache) {
119: constantCache.clear();
120: }
121: }
122:
123: /**
124: * Determines the value of the specified constant member field of a class.
125: * This implementation will call <code>fetchClass()</code> to obtain the
126: * <code>java.lang.Class</code> object for the target class. Then it will
127: * use reflection to obtain the field's value. For this to work the field
128: * must be accessable.
129: *
130: * @param className the name of the class
131: * @param fieldName the name of the member field of that class to read
132: * @return the field's value
133: * @throws Exception if an error occurs
134: */
135: protected Object resolveField(String className, String fieldName)
136: throws Exception {
137: Class clazz = fetchClass(className);
138: Field field = clazz.getField(fieldName);
139: return field.get(null);
140: }
141:
142: /**
143: * Loads the class with the specified name. If an application has special
144: * needs regarding the class loaders to be used, it can hook in here. This
145: * implementation delegates to the <code>getClass()</code> method of
146: * Commons Lang's
147: * <code><a href="http://jakarta.apache.org/commons/lang/api-release/org/apache/commons/lang/ClassUtils.html">
148: * ClassUtils</a></code>.
149: *
150: * @param className the name of the class to be loaded
151: * @return the corresponding class object
152: * @throws ClassNotFoundException if the class cannot be loaded
153: */
154: protected Class fetchClass(String className)
155: throws ClassNotFoundException {
156: return ClassUtils.getClass(className);
157: }
158: }
|