View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.jexl3.internal;
18  
19  import org.apache.commons.jexl3.JexlContext;
20  import org.apache.commons.jexl3.JexlFeatures;
21  import org.apache.commons.jexl3.JexlInfo;
22  import org.apache.commons.jexl3.JexlOptions;
23  import org.apache.commons.jexl3.JexlEngine;
24  import org.apache.commons.jexl3.JexlScript;
25  import org.apache.commons.jexl3.JexlExpression;
26  import org.apache.commons.jexl3.parser.ASTJexlScript;
27  
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Objects;
31  import java.util.Set;
32  /**
33   * <p>A JexlScript implementation.</p>
34   * @since 1.1
35   */
36  public class Script implements JexlScript, JexlExpression {
37      /**
38       * The engine for this expression.
39       */
40      protected final Engine jexl;
41      /**
42       * Original expression stripped from leading and trailing spaces.
43       */
44      protected final String source;
45      /**
46       * The resulting AST we can interpret.
47       */
48      protected final ASTJexlScript script;
49      /**
50       * The engine version (as class loader change count) that last evaluated this script.
51       */
52      protected int version;
53  
54      /**
55       * @return the script AST
56       */
57      protected ASTJexlScript getScript() {
58          return script;
59      }
60  
61      /**
62       * Do not let this be generally instantiated with a 'new'.
63       *
64       * @param engine the interpreter to evaluate the expression
65       * @param expr   the expression source.
66       * @param ref    the parsed expression.
67       */
68      protected Script(final Engine engine, final String expr, final ASTJexlScript ref) {
69          jexl = engine;
70          source = expr;
71          script = ref;
72          version = jexl.getUberspect().getVersion();
73      }
74  
75      /**
76       * Checks that this script cached methods (wrt introspection) matches the engine version.
77       * <p>
78       * If the engine class loader has changed since we last evaluated this script, the script local cache
79       * is invalidated to drop references to obsolete methods. It is not strictly necessary since the tryExecute
80       * will fail because the class won't match but it seems cleaner nevertheless.
81       * </p>
82       */
83      protected void checkCacheVersion() {
84          final int uberVersion = jexl.getUberspect().getVersion();
85          if (version != uberVersion) {
86              // version 0 of the uberSpect is an illusion due to order of construction; no need to clear cache
87              if (version > 0) {
88                  script.clearCache();
89              }
90              version = uberVersion;
91          }
92      }
93  
94      /**
95       * Creates this script frame for evaluation.
96       * @param args the arguments to bind to parameters
97       * @return the frame (may be null)
98       */
99      protected Frame createFrame(final Object[] args) {
100         return script.createFrame(args);
101     }
102 
103     /**
104      * Creates this script interpreter.
105      * @param context the context
106      * @param frame the calling frame
107      * @return  the interpreter
108      */
109     protected Interpreter createInterpreter(final JexlContext context, final Frame frame) {
110         return createInterpreter(context, frame, null);
111     }
112 
113     /**
114      * Creates this script interpreter.
115      * @param context the context
116      * @param frame the calling frame
117      * @param options the interpreter options
118      * @return  the interpreter
119      */
120     protected Interpreter createInterpreter(final JexlContext context, final Frame frame, final JexlOptions options) {
121         return jexl.createInterpreter(context, frame, options != null? options : jexl.evalOptions(script, context));
122     }
123 
124     /**
125      * @return the engine that created this script
126      */
127     public JexlEngine getEngine() {
128         return jexl;
129     }
130 
131     @Override
132     public String getSourceText() {
133         return source;
134     }
135 
136     @Override
137     public String getParsedText() {
138         return getParsedText(2);
139     }
140 
141     @Override
142     public String getParsedText(final int indent) {
143         final Debugger debug = new Debugger();
144         debug.outputPragmas(true).indentation(indent).debug(script, false);
145         return debug.toString();
146     }
147 
148     @Override
149     public String toString() {
150         CharSequence src = source;
151         if (src == null) {
152             final Debugger debug = new Debugger();
153             debug.debug(script, false);
154             src = debug.toString();
155         }
156         return src.toString();
157     }
158 
159     @Override
160     public int hashCode() {
161         // CSOFF: Magic number
162         int hash = 17;
163         hash = 31 * hash + (this.jexl != null ? this.jexl.hashCode() : 0);
164         hash = 31 * hash + (this.source != null ? this.source.hashCode() : 0);
165         return hash;
166         // CSON: Magic number
167     }
168 
169     @Override
170     public boolean equals(final Object obj) {
171         if (obj == null) {
172             return false;
173         }
174         if (getClass() != obj.getClass()) {
175             return false;
176         }
177         final Script other = (Script) obj;
178         if (this.jexl != other.jexl) {
179             return false;
180         }
181         if (!Objects.equals(this.source, other.source)) {
182             return false;
183         }
184         return true;
185     }
186 
187     @Override
188     public Object evaluate(final JexlContext context) {
189         return execute(context);
190     }
191 
192     @Override
193     public Object execute(final JexlContext context) {
194         checkCacheVersion();
195         final Frame frame = createFrame(null);
196         final Interpreter interpreter = createInterpreter(context, frame);
197         return interpreter.interpret(script);
198     }
199 
200     @Override
201     public Object execute(final JexlContext context, final Object... args) {
202         checkCacheVersion();
203         final Frame frame = createFrame(args != null && args.length > 0 ? args : null);
204         final Interpreter interpreter = createInterpreter(context, frame);
205         return interpreter.interpret(script);
206     }
207 
208     @Override
209     public JexlScript curry(final Object... args) {
210         final String[] parms = script.getParameters();
211         if (parms == null || parms.length == 0) {
212             return this;
213         }
214         return new Closure(this, args);
215     }
216 
217     @Override
218     public String[] getParameters() {
219         return script.getParameters();
220     }
221 
222     @Override
223     public String[] getUnboundParameters() {
224         return getParameters();
225     }
226 
227     @Override
228     public String[] getLocalVariables() {
229         return script.getLocalVariables();
230     }
231 
232     /**
233      * Gets this script captured variable, i.e. symbols captured from outer scopes.
234      * @return the captured variable names
235      */
236     public String[] getCapturedVariables() {
237         return script.getCapturedVariables();
238     }
239 
240     /**
241      * @return the info
242      */
243     public JexlInfo getInfo() {
244         return script.jexlInfo();
245     }
246 
247     /**
248      * @return the script features
249      */
250     public JexlFeatures getFeatures() {
251         return script.getFeatures();
252     }
253 
254     /**
255      * Gets this script variables.
256      * <p>Note that since variables can be in an ant-ish form (ie foo.bar.quux), each variable is returned as
257      * a list of strings where each entry is a fragment of the variable ({"foo", "bar", "quux"} in the example.</p>
258      * @return the variables or null
259      */
260     @Override
261     public Set<List<String>> getVariables() {
262         return jexl.getVariables(script);
263     }
264 
265     /**
266      * Get this script pragmas
267      * <p>Pragma keys are ant-ish variables, their values are scalar literals..
268      * @return the pragmas
269      */
270     @Override
271     public Map<String, Object> getPragmas() {
272         return script.getPragmas();
273     }
274 
275     /**
276      * Creates a Callable from this script.
277      * <p>This allows to submit it to an executor pool and provides support for asynchronous calls.</p>
278      * <p>The interpreter will handle interruption/cancellation gracefully if needed.</p>
279      * @param context the context
280      * @return the callable
281      */
282     @Override
283     public Callable callable(final JexlContext context) {
284         return callable(context, (Object[]) null);
285     }
286 
287     /**
288      * Creates a Callable from this script.
289      * <p>This allows to submit it to an executor pool and provides support for asynchronous calls.</p>
290      * <p>The interpreter will handle interruption/cancellation gracefully if needed.</p>
291      * @param context the context
292      * @param args    the script arguments
293      * @return the callable
294      */
295     @Override
296     public Callable callable(final JexlContext context, final Object... args) {
297         return new Callable(createInterpreter(context, script.createFrame(args)));
298     }
299 
300     /**
301      * Implements the Future and Callable interfaces to help delegation.
302      */
303     public class Callable implements java.util.concurrent.Callable<Object> {
304         /** The actual interpreter. */
305         protected final Interpreter interpreter;
306         /** Use interpreter as marker for not having run. */
307         protected volatile Object result;
308 
309         /**
310          * The base constructor.
311          * @param intrprtr the interpreter to use
312          */
313         protected Callable(final Interpreter intrprtr) {
314             this.interpreter = intrprtr;
315             this.result = intrprtr;
316         }
317 
318         /**
319          * Run the interpreter.
320          * @return the evaluation result
321          */
322         protected Object interpret() {
323             return interpreter.interpret(script);
324         }
325 
326         @Override
327         public Object call() throws Exception {
328             synchronized(this) {
329                 if (result == interpreter) {
330                     checkCacheVersion();
331                     result = interpret();
332                 }
333                 return result;
334             }
335         }
336 
337         /**
338          * Soft cancel the execution.
339          * @return true if cancel was successful, false otherwise
340          */
341         public boolean cancel() {
342             return interpreter.cancel();
343         }
344 
345         /**
346          * @return true if evaluation was cancelled, false otherwise
347          */
348         public boolean isCancelled() {
349             return interpreter.isCancelled();
350         }
351 
352         /**
353          * @return true if interruption will throw a JexlException.Cancel, false otherwise
354          */
355         public boolean isCancellable() {
356             return interpreter.isCancellable();
357         }
358     }
359 }