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 org.apache.commons.lang.text.StrLookup;
020 import org.apache.commons.lang.text.StrSubstitutor;
021 import org.apache.commons.lang.StringUtils;
022 import org.apache.commons.lang.ClassUtils;
023 import org.apache.commons.configuration.AbstractConfiguration;
024 import org.apache.commons.configuration.ConfigurationRuntimeException;
025 import org.apache.commons.jexl.JexlHelper;
026 import org.apache.commons.jexl.JexlContext;
027 import org.apache.commons.jexl.Expression;
028 import org.apache.commons.jexl.ExpressionFactory;
029
030 import java.util.Iterator;
031 import java.util.ArrayList;
032
033 /**
034 * Lookup that allows expressions to be evaluated.
035 *
036 * <pre>
037 * ExprLookup.Variables vars = new ExprLookup.Variables();
038 * vars.add(new ExprLookup.Variable("String", org.apache.commons.lang.StringUtils.class));
039 * vars.add(new ExprLookup.Variable("Util", new Utility("Hello")));
040 * vars.add(new ExprLookup.Variable("System", "Class:java.lang.System"));
041 * XMLConfiguration config = new XMLConfiguration(TEST_FILE);
042 * config.setLogger(log);
043 * ExprLookup lookup = new ExprLookup(vars);
044 * lookup.setConfiguration(config);
045 * String str = lookup.lookup("'$[element] ' + String.trimToEmpty('$[space.description]')");
046 * </pre>
047 *
048 * In the example above TEST_FILE contains xml that looks like:
049 * <pre>
050 * <configuration>
051 * <element>value</element>
052 * <space xml:space="preserve">
053 * <description xml:space="default"> Some text </description>
054 * </space>
055 * </configuration>
056 * </pre>
057 *
058 * The result will be "value Some text".
059 *
060 * This lookup uses Apache Commons Jexl and requires that the dependency be added to any
061 * projects which use this.
062 *
063 * @since 1.7
064 * @author <a
065 * href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
066 * @version $Id: ExprLookup.java 766914 2009-04-20 23:38:49Z rgoers $
067 */
068 public class ExprLookup extends StrLookup
069 {
070 /** Prefix to identify a Java Class object */
071 private static final String CLASS = "Class:";
072
073 /** The default prefix for subordinate lookup expressions */
074 private static final String DEFAULT_PREFIX = "$[";
075
076 /** The default suffix for subordinate lookup expressions */
077 private static final String DEFAULT_SUFFIX = "]";
078
079 /** Configuration being operated on */
080 private AbstractConfiguration configuration;
081
082 /** The JexlContext */
083 private JexlContext context = JexlHelper.createContext();
084
085 /** The String to use to start subordinate lookup expressions */
086 private String prefixMatcher = DEFAULT_PREFIX;
087
088 /** The String to use to terminate subordinate lookup expressions */
089 private String suffixMatcher = DEFAULT_SUFFIX;
090
091 /**
092 * The default constructor. Will get used when the Lookup is constructed via
093 * configuration.
094 */
095 public ExprLookup()
096 {
097 }
098
099 /**
100 * Constructor for use by applications.
101 * @param list The list of objects to be accessible in expressions.
102 */
103 public ExprLookup(Variables list)
104 {
105 setVariables(list);
106 }
107
108 /**
109 * Constructor for use by applications.
110 * @param list The list of objects to be accessible in expressions.
111 * @param prefix The prefix to use for subordinate lookups.
112 * @param suffix The suffix to use for subordinate lookups.
113 */
114 public ExprLookup(Variables list, String prefix, String suffix)
115 {
116 this(list);
117 setVariablePrefixMatcher(prefix);
118 setVariableSuffixMatcher(suffix);
119 }
120
121 /**
122 * Set the prefix to use to identify subordinate expressions. This cannot be the
123 * same as the prefix used for the primary expression.
124 * @param prefix The String identifying the beginning of the expression.
125 */
126 public void setVariablePrefixMatcher(String prefix)
127 {
128 prefixMatcher = prefix;
129 }
130
131
132 /**
133 * Set the suffix to use to identify subordinate expressions. This cannot be the
134 * same as the suffix used for the primary expression.
135 * @param suffix The String identifying the end of the expression.
136 */
137 public void setVariableSuffixMatcher(String suffix)
138 {
139 suffixMatcher = suffix;
140 }
141
142 /**
143 * Add the Variables that will be accessible within expressions.
144 * @param list The list of Variables.
145 */
146 public void setVariables(Variables list)
147 {
148 Iterator iter = list.iterator();
149 while (iter.hasNext())
150 {
151 Variable var = (Variable) iter.next();
152 context.getVars().put(var.getName(), var.getValue());
153 }
154 }
155
156 /**
157 * Returns the list of Variables that are accessible within expressions.
158 * @return the List of Variables that are accessible within expressions.
159 */
160 public Variables getVariables()
161 {
162 return null;
163 }
164
165 /**
166 * Set the configuration to be used to interpolate subordinate expressiosn.
167 * @param config The Configuration.
168 */
169 public void setConfiguration(AbstractConfiguration config)
170 {
171 this.configuration = config;
172 }
173
174 /**
175 * Evaluates the expression.
176 * @param var The expression.
177 * @return The String result of the expression.
178 */
179 public String lookup(String var)
180 {
181 ConfigurationInterpolator interp = configuration.getInterpolator();
182 StrSubstitutor subst = new StrSubstitutor(interp, prefixMatcher, suffixMatcher,
183 StrSubstitutor.DEFAULT_ESCAPE);
184
185 String result = subst.replace(var);
186
187 try
188 {
189 Expression exp = ExpressionFactory.createExpression(result);
190 result = (String) exp.evaluate(context);
191 }
192 catch (Exception e)
193 {
194 configuration.getLogger().debug("Error encountered evaluating " + result, e);
195 }
196
197 return result;
198 }
199
200 /**
201 * List wrapper used to allow the Variables list to be created as beans in
202 * DefaultConfigurationBuilder.
203 */
204 public static class Variables extends ArrayList
205 {
206 /*
207 public void setVariable(Variable var)
208 {
209 add(var);
210 } */
211
212 public Variable getVariable()
213 {
214 if (size() > 0)
215 {
216 return (Variable) get(size() - 1);
217 }
218 else
219 {
220 return null;
221 }
222 }
223
224 }
225
226 /**
227 * The key and corresponding object that will be made available to the
228 * JexlContext for use in expressions.
229 */
230 public static class Variable
231 {
232 /** The name to be used in expressions */
233 private String key;
234
235 /** The object to be accessed in expressions */
236 private Object value;
237
238 public Variable()
239 {
240 }
241
242 public Variable(String name, Object value)
243 {
244 setName(name);
245 setValue(value);
246 }
247
248 public String getName()
249 {
250 return key;
251 }
252
253 public void setName(String name)
254 {
255 this.key = name;
256 }
257
258 public Object getValue()
259 {
260 return value;
261 }
262
263 public void setValue(Object value) throws ConfigurationRuntimeException
264 {
265 try
266 {
267 if (!(value instanceof String))
268 {
269 this.value = value;
270 return;
271 }
272 String val = (String) value;
273 String name = StringUtils.removeStartIgnoreCase(val, CLASS);
274 Class clazz = ClassUtils.getClass(name);
275 if (name.length() == val.length())
276 {
277 this.value = clazz.newInstance();
278 }
279 else
280 {
281 this.value = clazz;
282 }
283 }
284 catch (Exception e)
285 {
286 throw new ConfigurationRuntimeException("Unable to create " + value, e);
287 }
288
289 }
290 }
291 }