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