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 */
017package org.apache.commons.jexl;
018
019import java.io.File;
020import java.net.URL;
021import org.apache.commons.jexl2.JexlEngine;
022import org.apache.commons.jexl2.Interpreter;
023import org.apache.commons.jexl2.JexlException;
024import org.apache.commons.jexl2.parser.JexlNode;
025import org.apache.commons.jexl2.parser.ASTJexlScript;
026
027/**
028 * This implements Jexl-1.x (Jelly) compatible behaviors on top of Jexl-2.0.
029 * @since 2.0
030 * @version $Id$
031 */
032public class JexlOne {
033    /**
034     * Default cache size.
035     */
036    private static final int CACHE_SIZE = 256;
037
038    /**
039     * Private constructor, ensure no instance.
040     */
041    protected JexlOne() {}
042
043
044    /**
045     * Lazy JexlEngine singleton through on demand holder idiom.
046     */
047    private static final class EngineHolder {
048        /** The shared instance. */
049        static final JexlOneEngine JEXL10 = new JexlOneEngine();
050        /**
051         * Non-instantiable.
052         */
053        private EngineHolder() {}
054    }
055
056
057    /**
058     * A Jexl1.x context wrapped into a Jexl2 context.
059     */
060    private static final class ContextAdapter implements org.apache.commons.jexl2.JexlContext {
061        /** The Jexl1.x context. */
062        private final JexlContext legacy;
063
064        /**
065         * Creates a jexl2.JexlContext from a jexl.JexlContext.
066         * @param ctxt10
067         */
068        ContextAdapter(JexlContext ctxt10) {
069            legacy = ctxt10;
070        }
071
072        /**
073         * {@inheritDoc}
074         */
075        public Object get(String name) {
076            return legacy.getVars().get(name);
077        }
078
079        /**
080         * {@inheritDoc}
081         */
082        public void set(String name, Object value) {
083            legacy.getVars().put(name, value);
084        }
085
086        /**
087         * {@inheritDoc}
088         */
089        public boolean has(String name) {
090            return legacy.getVars().containsKey(name);
091        }
092
093        /**
094         * Adapts a Jexl-1.x context to a Jexl-2.0 context.
095         * @param aContext a oac.jexl context
096         * @return an oac.jexl2 context
097         */
098        static final org.apache.commons.jexl2.JexlContext adapt(JexlContext aContext) {
099            return aContext == null ? JexlOneEngine.EMPTY_CONTEXT : new ContextAdapter(aContext);
100        }
101    }
102
103
104    /**
105     * An interpreter made compatible with v1.1 behavior (at least Jelly's expectations).
106     */
107    private static final class JexlOneInterpreter extends Interpreter {
108        /**
109         * Creates an instance.
110         * @param jexl the jexl engine
111         * @param aContext the jexl context
112         */
113        public JexlOneInterpreter(JexlEngine jexl, JexlContext aContext) {
114            super(jexl, ContextAdapter.adapt(aContext), false, false);
115        }
116
117        /**{@inheritDoc}*/
118        @Override
119        public Object interpret(JexlNode node) {
120            try {
121                return node.jjtAccept(this, null);
122            } catch (JexlException xjexl) {
123                Throwable e = xjexl.getCause();
124                if (e instanceof RuntimeException) {
125                    throw (RuntimeException) e;
126                }
127                if (e instanceof IllegalStateException) {
128                    throw (IllegalStateException) e;
129                }
130                throw new IllegalStateException(e.getMessage(), e);
131            }
132        }
133
134        /**{@inheritDoc}*/
135        @Override
136        protected Object invocationFailed(JexlException xjexl) {
137            throw xjexl;
138        }
139
140        /**{@inheritDoc}*/
141        @Override
142        protected Object unknownVariable(JexlException xjexl) {
143            return null;
144        }
145    }
146
147    
148    /**
149     * An engine that uses a JexlOneInterpreter.
150     */
151    private static final class JexlOneEngine extends JexlEngine {
152        /**
153         * Default ctor, creates a cache and sets instance to verbose (ie non-silent).
154         */
155        private JexlOneEngine() {
156            super();
157            setCache(CACHE_SIZE);
158            setSilent(false);
159        }
160
161        /**{@inheritDoc}*/
162        protected Interpreter createInterpreter(JexlContext context) {
163            return new JexlOneInterpreter(this, context);
164        }
165
166        /** {@inheritDoc} */
167        @Override
168        protected Script createScript(ASTJexlScript tree, String text) {
169            return new JexlOneExpression(this, text, tree);
170        }
171
172        /** {@inheritDoc} */
173        @Override
174        protected Expression createExpression(ASTJexlScript tree, String text) {
175            return new JexlOneExpression(this, text, tree);
176        }
177    }
178
179    /**
180     * The specific Jexl-1.x expressions implementation.
181     */
182    private static final class JexlOneExpression
183            extends org.apache.commons.jexl2.ExpressionImpl
184            implements Expression, Script {
185        /**
186         * Default local ctor.
187         *
188         * @param engine the interpreter to evaluate the expression
189         * @param expr the expression.
190         * @param ref the parsed expression.
191         */
192        private JexlOneExpression(JexlOne.JexlOneEngine engine, String expr, ASTJexlScript ref) {
193            super(engine, expr, ref);
194        }
195
196        /**
197         * {@inheritDoc}
198         */
199        public Object evaluate(JexlContext context) {
200            return super.evaluate(ContextAdapter.adapt(context));
201        }
202
203        /**
204         * {@inheritDoc}
205         */
206        public Object execute(JexlContext context) {
207            return super.execute(ContextAdapter.adapt(context));
208        }
209    }
210
211
212    /**
213     * Creates a Script from a String containing valid JEXL syntax.
214     * This method parses the script which validates the syntax.
215     *
216     * @param scriptText A String containing valid JEXL syntax
217     * @return A {@link Script} which can be executed with a
218     *      {@link JexlContext}.
219     * @throws Exception An exception can be thrown if there is a
220     *      problem parsing the script.
221     * @deprecated Create a JexlEngine and use the createScript method on that instead.
222     */
223    @Deprecated
224    public static Script createScript(String scriptText) throws Exception {
225        return (Script) EngineHolder.JEXL10.createScript(scriptText);
226    }
227
228    /**
229     * Creates a Script from a {@link File} containing valid JEXL syntax.
230     * This method parses the script and validates the syntax.
231     *
232     * @param scriptFile A {@link File} containing valid JEXL syntax.
233     *      Must not be null. Must be a readable file.
234     * @return A {@link Script} which can be executed with a
235     *      {@link JexlContext}.
236     * @throws Exception An exception can be thrown if there is a problem
237     *      parsing the script.
238     * @deprecated Create a JexlEngine and use the createScript method on that instead.
239     */
240    @Deprecated
241    public static Script createScript(File scriptFile) throws Exception {
242        return (Script) EngineHolder.JEXL10.createScript(scriptFile);
243    }
244
245    /**
246     * Creates a Script from a {@link URL} containing valid JEXL syntax.
247     * This method parses the script and validates the syntax.
248     *
249     * @param scriptUrl A {@link URL} containing valid JEXL syntax.
250     *      Must not be null. Must be a readable file.
251     * @return A {@link Script} which can be executed with a
252     *      {@link JexlContext}.
253     * @throws Exception An exception can be thrown if there is a problem
254     *      parsing the script.
255     * @deprecated Create a JexlEngine and use the createScript method on that instead.
256     */
257    @Deprecated
258    public static Script createScript(URL scriptUrl) throws Exception {
259        return (Script) EngineHolder.JEXL10.createScript(scriptUrl);
260    }
261
262    /**
263     * Creates an Expression from a String containing valid
264     * JEXL syntax.  This method parses the expression which
265     * must contain either a reference or an expression.
266     * @param expression A String containing valid JEXL syntax
267     * @return An Expression object which can be evaluated with a JexlContext
268     * @throws JexlException An exception can be thrown if there is a problem
269     *      parsing this expression, or if the expression is neither an
270     *      expression or a reference.
271     * @deprecated Create a JexlEngine and use createExpression() on that
272     */
273    @Deprecated
274    public static Expression createExpression(String expression) {
275        return (Expression) EngineHolder.JEXL10.createExpression(expression);
276    }
277}