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     * &lt;configuration&gt;
051     *   &lt;element&gt;value&lt;/element&gt;
052     *   &lt;space xml:space="preserve"&gt;
053     *     &lt;description xml:space="default"&gt;     Some text      &lt;/description&gt;
054     *   &lt;/space&gt;
055     * &lt;/configuration&gt;
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    }