This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/master by this push: new 753d6e6 JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option 753d6e6 is described below commit 753d6e634d94022871b481896db78e323d7ccfed Author: henrib <hen...@apache.org> AuthorDate: Tue Nov 12 23:00:39 2019 +0100 JEXL-307: tidy API (made JexlOptions a concrete non derivable class), try to ensure lexical interpretation on lexical feature, allow runtime options to be controlled through pragma (jexl.options) Task #JEXL-307 - Variable redeclaration option --- .../java/org/apache/commons/jexl3/JexlBuilder.java | 21 +- .../java/org/apache/commons/jexl3/JexlContext.java | 16 ++ .../java/org/apache/commons/jexl3/JexlEngine.java | 10 +- .../java/org/apache/commons/jexl3/JexlInfo.java | 11 + .../java/org/apache/commons/jexl3/JexlOptions.java | 223 +++++++++++++++-- .../org/apache/commons/jexl3/internal/Engine.java | 68 ++++- .../apache/commons/jexl3/internal/Interpreter.java | 33 ++- .../commons/jexl3/internal/InterpreterBase.java | 7 +- .../org/apache/commons/jexl3/internal/Options.java | 274 --------------------- .../org/apache/commons/jexl3/internal/Script.java | 45 +++- .../jexl3/internal/TemplateInterpreter.java | 2 +- .../org/apache/commons/jexl3/AntishCallTest.java | 15 +- .../org/apache/commons/jexl3/Issues300Test.java | 25 +- .../java/org/apache/commons/jexl3/JXLTTest.java | 3 +- .../org/apache/commons/jexl3/JexlEvalContext.java | 3 +- .../org/apache/commons/jexl3/JexlTestCase.java | 2 +- .../java/org/apache/commons/jexl3/LexicalTest.java | 43 ++++ 17 files changed, 431 insertions(+), 370 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java index 35c8d5f..744dfe1 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlBuilder.java +++ b/src/main/java/org/apache/commons/jexl3/JexlBuilder.java @@ -18,7 +18,6 @@ package org.apache.commons.jexl3; import org.apache.commons.jexl3.internal.Engine; -import org.apache.commons.jexl3.internal.Options; import org.apache.commons.jexl3.introspection.JexlSandbox; import org.apache.commons.jexl3.introspection.JexlUberspect; import org.apache.commons.logging.Log; @@ -81,7 +80,7 @@ public class JexlBuilder { private Boolean cancellable = null; /** The options. */ - private final Options options = new Options(); + private final JexlOptions options = new JexlOptions(); /** Whether getVariables considers all potential equivalent syntactic forms. */ private Boolean collectAll = null; @@ -109,23 +108,7 @@ public class JexlBuilder { /** The features. */ private JexlFeatures features = null; - - /** - * Sets the default (static, shared) option flags. - * <p> - * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL - * engine; this method should only be used for testing / validation. - * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false. - * The possible flag names are: - * cancellable, strict, silent, safe, lexical, antish, lexicalShade - * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation - * may ease validating JEXL3.2 in your environment. - * @param flags the flags to set - */ - public static void setDefaultOptions(String...flags) { - Options.setDefaultFlags(flags); - } - + /** * Sets the JexlUberspect instance the engine will use. * diff --git a/src/main/java/org/apache/commons/jexl3/JexlContext.java b/src/main/java/org/apache/commons/jexl3/JexlContext.java index fdc85a3..ce730f7 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlContext.java +++ b/src/main/java/org/apache/commons/jexl3/JexlContext.java @@ -159,4 +159,20 @@ public interface JexlContext { */ JexlOptions getEngineOptions(); } + + /** + * A marker interface of the JexlContext that processes pragmas. + * It is called by the engine before interpreter creation; as a marker of + * JexlContext, it is expected to have access and interact with the context + * instance. + * @since 3.2 + */ + interface PragmaProcessor { + /** + * Process one pragma. + * @param key the key + * @param value the value + */ + void processPragma(String key, Object value); + } } diff --git a/src/main/java/org/apache/commons/jexl3/JexlEngine.java b/src/main/java/org/apache/commons/jexl3/JexlEngine.java index bc41a82..7b9d198 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlEngine.java +++ b/src/main/java/org/apache/commons/jexl3/JexlEngine.java @@ -352,7 +352,7 @@ public abstract class JexlEngine { * @return A {@link JexlScript} which can be executed using a {@link JexlContext} * @throws JexlException if there is a problem parsing the script */ - public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String[] names); + public abstract JexlScript createScript(JexlFeatures features, JexlInfo info, String source, String... names); /** * Creates a JexlScript from a String containing valid JEXL syntax. @@ -365,7 +365,7 @@ public abstract class JexlEngine { * @return A {@link JexlScript} which can be executed using a {@link JexlContext} * @throws JexlException if there is a problem parsing the script */ - public final JexlScript createScript(JexlInfo info, String source, String[] names) { + public final JexlScript createScript(JexlInfo info, String source, String... names) { return createScript(null, info, source, names); } @@ -378,7 +378,7 @@ public abstract class JexlEngine { * @throws JexlException if there is a problem parsing the script. */ public final JexlScript createScript(String scriptText) { - return createScript(null, null, scriptText, null); + return createScript(null, null, scriptText, (String[]) null); } /** @@ -404,7 +404,7 @@ public abstract class JexlEngine { * @throws JexlException if there is a problem reading or parsing the script. */ public final JexlScript createScript(File scriptFile) { - return createScript(null, null, readSource(scriptFile), null); + return createScript(null, null, readSource(scriptFile), (String[]) null); } /** @@ -445,7 +445,7 @@ public abstract class JexlEngine { * @throws JexlException if there is a problem reading or parsing the script. */ public final JexlScript createScript(URL scriptUrl) { - return createScript(null, readSource(scriptUrl), null); + return createScript(null, readSource(scriptUrl), (String[]) null); } /** diff --git a/src/main/java/org/apache/commons/jexl3/JexlInfo.java b/src/main/java/org/apache/commons/jexl3/JexlInfo.java index 19a4a72..296138b 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlInfo.java +++ b/src/main/java/org/apache/commons/jexl3/JexlInfo.java @@ -17,6 +17,8 @@ package org.apache.commons.jexl3; +import org.apache.commons.jexl3.internal.Script; + /** * Helper class to carry information such as a url/file name, line and column for * debugging information reporting. @@ -182,5 +184,14 @@ public class JexlInfo { public final int getColumn() { return column; } + + /** + * Gets the info from a script. + * @param script the script + * @return the info + */ + public static JexlInfo from(JexlScript script) { + return script instanceof Script? ((Script) script).getInfo() : null; + } } diff --git a/src/main/java/org/apache/commons/jexl3/JexlOptions.java b/src/main/java/org/apache/commons/jexl3/JexlOptions.java index dc35074..c650e9b 100644 --- a/src/main/java/org/apache/commons/jexl3/JexlOptions.java +++ b/src/main/java/org/apache/commons/jexl3/JexlOptions.java @@ -18,6 +18,7 @@ package org.apache.commons.jexl3; import java.math.MathContext; +import org.apache.commons.jexl3.internal.Engine; /** * Flags and properties that can alter the evaluation behavior. @@ -35,31 +36,150 @@ import java.math.MathContext; * <p>This interface replaces the now deprecated JexlEngine.Options. * @since 3.2 */ -public interface JexlOptions { +public final class JexlOptions { + /** The local shade bit. */ + private static final int SHADE = 6; + /** The antish var bit. */ + private static final int ANTISH = 5; + /** The lexical scope bit. */ + private static final int LEXICAL = 4; + /** The safe bit. */ + private static final int SAFE = 3; + /** The silent bit. */ + private static final int SILENT = 2; + /** The strict bit. */ + private static final int STRICT = 1; + /** The cancellable bit. */ + private static final int CANCELLABLE = 0; + /** The flags names ordered. */ + private static final String[] NAMES = { + "cancellable", "strict", "silent", "safe", "lexical", "antish", "lexicalShade" + }; + /** Default mask .*/ + private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE; + /** The arithmetic math context. */ + private MathContext mathContext = null; + /** The arithmetic math scale. */ + private int mathScale = Integer.MIN_VALUE; + /** The arithmetic strict math flag. */ + private boolean strictArithmetic = true; + /** The default flags, all but safe. */ + private int flags = DEFAULT; + + /** + * Sets the value of a flag in a mask. + * @param ordinal the flag ordinal + * @param mask the flags mask + * @param value true or false + * @return the new flags mask value + */ + private static int set(int ordinal, int mask, boolean value) { + return value? mask | (1 << ordinal) : mask & ~(1 << ordinal); + } + + /** + * Checks the value of a flag in the mask. + * @param ordinal the flag ordinal + * @param mask the flags mask + * @return the mask value with this flag or-ed in + */ + private static boolean isSet(int ordinal, int mask) { + return (mask & 1 << ordinal) != 0; + } + + /** + * Default ctor. + */ + public JexlOptions() {} + + /** + * Sets the default (static, shared) option flags. + * <p> + * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL + * engine; this method should only be used for testing / validation. + * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false. + * The possible flag names are: + * cancellable, strict, silent, safe, lexical, antish, lexicalShade + * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation + * may ease validating JEXL3.2 in your environment. + * @param flags the flags to set + */ + public static void setDefaultFlags(String...flags) { + DEFAULT = parseFlags(DEFAULT, flags); + } + + /** + * Parses flags by name. + * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false. + * The possible flag names are: + * cancellable, strict, silent, safe, lexical, antish, lexicalShade + * @param mask the initial mask state + * @param flags the flags to set + * @return the flag mask updated + */ + public static int parseFlags(int mask, String...flags) { + for(String name : flags) { + boolean b = true; + if (name.charAt(0) == '+') { + name = name.substring(1); + } else if (name.charAt(0) == '-') { + name = name.substring(1); + b = false; + } + for(int flag = 0; flag < NAMES.length; ++flag) { + if (NAMES[flag].equals(name)) { + if (b) { + mask |= (1 << flag); + } else { + mask &= ~(1 << flag); + } + break; + } + } + } + return mask; + } + + /** + * Sets this option flags using the +/- syntax. + * @param opts the option flags + */ + public void setFlags(String[] opts) { + flags = parseFlags(flags, opts); + } + /** * The MathContext instance used for +,-,/,*,% operations on big decimals. * @return the math context */ - MathContext getMathContext(); + public MathContext getMathContext() { + return mathContext; + } /** * The BigDecimal scale used for comparison and coercion operations. * @return the scale */ - int getMathScale(); + public int getMathScale() { + return mathScale; + } /** * Checks whether evaluation will attempt resolving antish variable names. * @return true if antish variables are solved, false otherwise */ - boolean isAntish(); + public boolean isAntish() { + return isSet(ANTISH, flags); + } /** * Checks whether evaluation will throw JexlException.Cancel (true) or * return null (false) if interrupted. * @return true when cancellable, false otherwise */ - boolean isCancellable(); + public boolean isCancellable() { + return isSet(CANCELLABLE, flags); + } /** * Checks whether runtime variable scope is lexical. @@ -67,7 +187,9 @@ public interface JexlOptions { * Redefining a variable in the same lexical unit will generate errors. * @return true if scope is lexical, false otherwise */ - boolean isLexical(); + public boolean isLexical() { + return isSet(LEXICAL, flags); + } /** * Checks whether local variables shade global ones. @@ -79,122 +201,169 @@ public interface JexlOptions { * raise an error. * @return true if lexical shading is applied, false otherwise */ - boolean isLexicalShade(); + public boolean isLexicalShade() { + return isSet(SHADE, flags); + } /** * Checks whether the engine considers null in navigation expression as * errors during evaluation.. * @return true if safe, false otherwise */ - boolean isSafe(); + public boolean isSafe() { + return isSet(SAFE, flags); + } /** * Checks whether the engine will throw a {@link JexlException} when an * error is encountered during evaluation. * @return true if silent, false otherwise */ - boolean isSilent(); + public boolean isSilent() { + return isSet(SILENT, flags); + } /** * Checks whether the engine considers unknown variables, methods and * constructors as errors during evaluation. * @return true if strict, false otherwise */ - boolean isStrict(); + public boolean isStrict() { + return isSet(STRICT, flags); + } /** * Checks whether the arithmetic triggers errors during evaluation when null * is used as an operand. * @return true if strict, false otherwise */ - boolean isStrictArithmetic(); + public boolean isStrictArithmetic() { + return strictArithmetic; + } /** * Sets whether the engine will attempt solving antish variable names from * context. * @param flag true if antish variables are solved, false otherwise */ - void setAntish(boolean flag); + public void setAntish(boolean flag) { + flags = set(ANTISH, flags, flag); + } /** * Sets whether the engine will throw JexlException.Cancel (true) or return * null (false) when interrupted during evaluation. * @param flag true when cancellable, false otherwise */ - void setCancellable(boolean flag); + public void setCancellable(boolean flag) { + flags = set(CANCELLABLE, flags, flag); + } /** * Sets whether the engine uses a strict block lexical scope during * evaluation. * @param flag true if lexical scope is used, false otherwise */ - void setLexical(boolean flag); + public void setLexical(boolean flag) { + flags = set(LEXICAL, flags, flag); + } /** * Sets whether the engine strictly shades global variables. * Local symbols shade globals after definition and creating global * variables is prohibited evaluation. + * If setting to lexical shade, lexical is also set. * @param flag true if creation is allowed, false otherwise */ - void setLexicalShade(boolean flag); + public void setLexicalShade(boolean flag) { + flags = set(SHADE, flags, flag); + if (flag) { + flags = set(LEXICAL, flags, true); + } + } /** * Sets the arithmetic math context. * @param mcontext the context */ - void setMathContext(MathContext mcontext); + public void setMathContext(MathContext mcontext) { + this.mathContext = mcontext; + } /** * Sets the arithmetic math scale. * @param mscale the scale */ - void setMathScale(int mscale); + public void setMathScale(int mscale) { + this.mathScale = mscale; + } /** * Sets whether the engine considers null in navigation expression as errors * during evaluation. * @param flag true if safe, false otherwise */ - void setSafe(boolean flag); + public void setSafe(boolean flag) { + flags = set(SAFE, flags, flag); + } /** * Sets whether the engine will throw a {@link JexlException} when an error * is encountered during evaluation. * @param flag true if silent, false otherwise */ - void setSilent(boolean flag); + public void setSilent(boolean flag) { + flags = set(SILENT, flags, flag); + } /** * Sets whether the engine considers unknown variables, methods and * constructors as errors during evaluation. * @param flag true if strict, false otherwise */ - void setStrict(boolean flag); + public void setStrict(boolean flag) { + flags = set(STRICT, flags, flag); + } /** * Sets the strict arithmetic flag. * @param stricta true or false */ - void setStrictArithmetic(boolean stricta); + public void setStrictArithmetic(boolean stricta) { + this.strictArithmetic = stricta; + } /** * Set options from engine. * @param jexl the engine * @return this instance - */ - JexlOptions set(JexlEngine jexl); + */ + public JexlOptions set(JexlEngine jexl) { + if (jexl instanceof Engine) { + ((Engine) jexl).optionsSet(this); + } + return this; + } /** * Set options from options. - * @param options the options + * @param src the options * @return this instance */ - JexlOptions set(JexlOptions options); - + public JexlOptions set(JexlOptions src) { + mathContext = src.mathContext; + mathScale = src.mathScale; + strictArithmetic = src.strictArithmetic; + flags = src.flags; + return this; + } + /** * Creates a copy of this instance. * @return a copy */ - JexlOptions copy(); + public JexlOptions copy() { + return new JexlOptions().set(this); + } } \ No newline at end of file diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java b/src/main/java/org/apache/commons/jexl3/internal/Engine.java index d752cca..4895578 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java @@ -74,6 +74,10 @@ public class Engine extends JexlEngine { private UberspectHolder() {} } /** + * The name of the options pragma. + */ + protected static final String PRAGMA_OPTIONS = "jexl.options"; + /** * The Log to which all JexlEngine messages will be logged. */ protected final Log logger; @@ -347,18 +351,62 @@ public class Engine extends JexlEngine { cache.clear(); } } - + + /** + * Sets options from this engine options. + * @param opts the options to set + * @return the options + */ + public JexlOptions optionsSet(JexlOptions opts) { + if (opts != null) { + opts.set(options); + } + return opts; + } + + /** + * Creates a script evaluation options. + * <p>This also calls the pragma processor if any + * @param script the script + * @param context the context + * @return the options + */ + protected JexlOptions createOptions(Script script, JexlContext context) { + JexlOptions opts = options(context); + Map<String, Object> pragmas = script.getPragmas(); + if (pragmas != null) { + JexlContext.PragmaProcessor processor = + context instanceof JexlContext.PragmaProcessor + ? (JexlContext.PragmaProcessor) context + : null; + for(Map.Entry<String, Object> pragma : pragmas.entrySet()) { + String key = pragma.getKey(); + Object value = pragma.getValue(); + if (PRAGMA_OPTIONS.equals(key) && opts != null) { + if (value instanceof String) { + String[] vs = ((String) value).split(" "); + opts.setFlags(vs); + } + } + if (processor != null) { + processor.processPragma(key, value); + } + } + } + return opts; + } + /** * Creates an interpreter. * @param context a JexlContext; if null, the empty context is used instead. * @param frame the interpreter frame + * @param opts the evaluation options * @return an Interpreter */ - protected Interpreter createInterpreter(JexlContext context, Frame frame) { - return new Interpreter(this, context, frame); + protected Interpreter createInterpreter(JexlContext context, Frame frame, JexlOptions opts) { + return new Interpreter(this, opts, context, frame); } - @Override public Script createExpression(JexlInfo info, String expression) { return createScript(expressionFeatures, info, expression, null); @@ -371,8 +419,12 @@ public class Engine extends JexlEngine { } String source = trimSource(scriptText); Scope scope = names == null ? null : new Scope(null, names); - ASTJexlScript tree = parse(info, features == null? scriptFeatures : features, source, scope); - return new Script(this, source, tree); + JexlFeatures ftrs = features == null? scriptFeatures : features; + ASTJexlScript tree = parse(info, ftrs, source, scope); + // when parsing lexical, try hard to run lexical + return ftrs.isLexical() + ? new Script.Lexical(this, source, tree) + : new Script(this, source, tree); } /** @@ -405,7 +457,7 @@ public class Engine extends JexlEngine { final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope); final JexlNode node = script.jjtGetChild(0); final Frame frame = script.createFrame(bean); - final Interpreter interpreter = createInterpreter(context, frame); + final Interpreter interpreter = createInterpreter(context, frame, null); return interpreter.visitLexicalNode(node, null); } catch (JexlException xjexl) { if (silent) { @@ -434,7 +486,7 @@ public class Engine extends JexlEngine { final ASTJexlScript script = parse(null, PROPERTY_FEATURES, src, scope); final JexlNode node = script.jjtGetChild(0); final Frame frame = script.createFrame(bean, value); - final Interpreter interpreter = createInterpreter(context, frame); + final Interpreter interpreter = createInterpreter(context, frame, null); interpreter.visitLexicalNode(node, null); } catch (JexlException xjexl) { if (silent) { diff --git a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java index ed7efa7..9c27f61 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Interpreter.java @@ -17,13 +17,18 @@ //CSOFF: FileLength package org.apache.commons.jexl3.internal; +import java.util.Iterator; +import java.util.concurrent.Callable; + import org.apache.commons.jexl3.JexlArithmetic; import org.apache.commons.jexl3.JexlContext; import org.apache.commons.jexl3.JexlEngine; import org.apache.commons.jexl3.JexlException; import org.apache.commons.jexl3.JexlOperator; +import org.apache.commons.jexl3.JexlOptions; import org.apache.commons.jexl3.JexlScript; import org.apache.commons.jexl3.JxltEngine; + import org.apache.commons.jexl3.introspection.JexlMethod; import org.apache.commons.jexl3.introspection.JexlPropertyGet; @@ -105,9 +110,6 @@ import org.apache.commons.jexl3.parser.ASTWhileStatement; import org.apache.commons.jexl3.parser.JexlNode; import org.apache.commons.jexl3.parser.Node; -import java.util.Iterator; -import java.util.concurrent.Callable; - /** * An interpreter of JEXL syntax. * @@ -130,11 +132,12 @@ public class Interpreter extends InterpreterBase { /** * Creates an interpreter. * @param engine the engine creating this interpreter - * @param aContext the context to evaluate expression - * @param eFrame the interpreter evaluation frame + * @param aContext the evaluation context, global variables, methods and functions + * @param opts the evaluation options, flags modifying evaluation behavior + * @param eFrame the evaluation frame, arguments and local variables */ - protected Interpreter(Engine engine, JexlContext aContext, Frame eFrame) { - super(engine, aContext); + protected Interpreter(Engine engine, JexlOptions opts, JexlContext aContext, Frame eFrame) { + super(engine, opts, aContext); this.frame = eFrame; } @@ -1122,7 +1125,7 @@ public class Interpreter extends InterpreterBase { JexlNode objectNode = null; JexlNode ptyNode = null; StringBuilder ant = null; - boolean antish = !(parent instanceof ASTReference) && options.isAntish(); + boolean antish = !(parent instanceof ASTReference); int v = 1; main: for (int c = 0; c < numChildren; c++) { @@ -1172,15 +1175,21 @@ public class Interpreter extends InterpreterBase { if (first instanceof ASTIdentifier) { ASTIdentifier afirst = (ASTIdentifier) first; ant = new StringBuilder(afirst.getName()); - // skip the first node case since it was trialed in jjtAccept above and returned null - if (c == 0) { - continue; - } + // skip the else...* } else { // not an identifier, not antish ptyNode = objectNode; break main; } + // *... and continue + if (!options.isAntish()) { + antish = false; + continue; + } + // skip the first node case since it was trialed in jjtAccept above and returned null + if (c == 0) { + continue; + } } // catch up to current node for (; v <= c; ++v) { diff --git a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java index 74c8f8f..504c1c1 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java +++ b/src/main/java/org/apache/commons/jexl3/internal/InterpreterBase.java @@ -78,16 +78,17 @@ public abstract class InterpreterBase extends ParserVisitor { /** * Creates an interpreter base. * @param engine the engine creating this interpreter - * @param aContext the context to evaluate expression + * @param opts the evaluation options + * @param aContext the evaluation context */ - protected InterpreterBase(Engine engine, JexlContext aContext) { + protected InterpreterBase(Engine engine, JexlOptions opts, JexlContext aContext) { this.jexl = engine; this.logger = jexl.logger; this.uberspect = jexl.uberspect; this.context = aContext != null ? aContext : Engine.EMPTY_CONTEXT; this.cache = engine.cache != null; JexlArithmetic jexla = jexl.arithmetic; - this.options = jexl.options(context); + this.options = opts == null? engine.options(aContext) : opts; this.arithmetic = jexla.options(options); if (arithmetic != jexla && !arithmetic.getClass().equals(jexla.getClass())) { logger.warn("expected arithmetic to be " + jexla.getClass().getSimpleName() diff --git a/src/main/java/org/apache/commons/jexl3/internal/Options.java b/src/main/java/org/apache/commons/jexl3/internal/Options.java deleted file mode 100644 index 04dd182..0000000 --- a/src/main/java/org/apache/commons/jexl3/internal/Options.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.commons.jexl3.internal; - -import org.apache.commons.jexl3.JexlOptions; -import org.apache.commons.jexl3.JexlEngine; - -import java.math.MathContext; - -/** - * A basic implementation of JexlOptions. - * <p>Thread safety is only guaranteed if no modifications (call set* method) - * occurs in different threads; note however that using the 'callable()' method to - * allow a script to run as such will use a copy. - */ -public class Options implements JexlOptions { - /** The local shade bit. */ - protected static final int SHADE = 6; - /** The antish var bit. */ - protected static final int ANTISH = 5; - /** The lexical scope bit. */ - protected static final int LEXICAL = 4; - /** The safe bit. */ - protected static final int SAFE = 3; - /** The silent bit. */ - protected static final int SILENT = 2; - /** The strict bit. */ - protected static final int STRICT = 1; - /** The cancellable bit. */ - protected static final int CANCELLABLE = 0; - /** The flags names ordered. */ - private static final String[] NAMES = { - "cancellable", "strict", "silent", "safe", "lexical", "antish", "lexicalShade" - }; - /** Default mask .*/ - protected static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE; - /** The arithmetic math context. */ - private MathContext mathContext = null; - /** The arithmetic math scale. */ - private int mathScale = Integer.MIN_VALUE; - /** The arithmetic strict math flag. */ - private boolean strictArithmetic = true; - /** The default flags, all but safe. */ - private int flags = DEFAULT; - - /** - * Sets the value of a flag in a mask. - * @param ordinal the flag ordinal - * @param mask the flags mask - * @param value true or false - * @return the new flags mask value - */ - protected static int set(int ordinal, int mask, boolean value) { - return value? mask | (1 << ordinal) : mask & ~(1 << ordinal); - } - - /** - * Checks the value of a flag in the mask. - * @param ordinal the flag ordinal - * @param mask the flags mask - * @return the mask value with this flag or-ed in - */ - protected static boolean isSet(int ordinal, int mask) { - return (mask & 1 << ordinal) != 0; - } - - /** - * Default ctor. - */ - public Options() {} - - /** - * Sets the default flags. - * <p>Used by tests to force default options. - * @param flags the flags to set - */ - public static void setDefaultFlags(String...flags) { - DEFAULT = parseFlags(DEFAULT, flags); - } - - /** - * Parses flags by name. - * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false. - * The possible flag names are: - * cancellable, strict, silent, safe, lexical, antish, lexicalShade - * @param mask the initial mask state - * @param flags the flags to set - * @return the flag mask updated - */ - public static int parseFlags(int mask, String...flags) { - for(String name : flags) { - boolean b = true; - if (name.charAt(0) == '+') { - name = name.substring(1); - } else if (name.charAt(0) == '-') { - name = name.substring(1); - b = false; - } - for(int flag = 0; flag < NAMES.length; ++flag) { - if (NAMES[flag].equals(name)) { - if (b) { - mask |= (1 << flag); - } else { - mask &= ~(1 << flag); - } - break; - } - } - } - return mask; - } - - @Override - public Options set(JexlEngine jexl) { - if (jexl instanceof Engine) { - set(((Engine) jexl).options); - } else { - mathContext = jexl.getArithmetic().getMathContext(); - mathScale = jexl.getArithmetic().getMathScale(); - strictArithmetic = jexl.getArithmetic().isStrict(); - set(STRICT, flags, jexl.isStrict()); - set(SILENT, flags, jexl.isSilent()); - set(SAFE, flags, jexl.isSafe()); - set(CANCELLABLE, flags, jexl.isCancellable()); - } - return this; - } - - @Override - public Options set(JexlOptions opts) { - if (opts instanceof Options) { - Options src = (Options) opts; - mathContext = src.mathContext; - mathScale = src.mathScale; - strictArithmetic = src.strictArithmetic; - flags = src.flags; - } else { - mathContext = opts.getMathContext(); - mathScale = opts.getMathScale(); - strictArithmetic = opts.isStrict(); - int mask = DEFAULT; - mask = set(STRICT, mask, opts.isStrict()); - mask = set(SILENT, mask, opts.isSilent()); - mask = set(SAFE, mask, opts.isSafe()); - mask = set(CANCELLABLE, mask, opts.isCancellable()); - mask = set(LEXICAL, mask, opts.isLexical()); - mask = set(SHADE, mask, opts.isLexicalShade()); - mask = set(ANTISH, mask, opts.isAntish()); - flags = mask; - } - return this; - } - - @Override - public Options copy() { - return new Options().set(this); - } - - @Override - public void setAntish(boolean flag) { - flags = set(ANTISH, flags, flag); - } - - @Override - public void setMathContext(MathContext mcontext) { - this.mathContext = mcontext; - } - - @Override - public void setMathScale(int mscale) { - this.mathScale = mscale; - } - - @Override - public void setStrictArithmetic(boolean stricta) { - this.strictArithmetic = stricta; - } - - @Override - public void setStrict(boolean flag) { - flags = set(STRICT, flags, flag); - } - - @Override - public void setSafe(boolean flag) { - flags = set(SAFE, flags, flag); - } - - @Override - public void setSilent(boolean flag) { - flags = set(SILENT, flags, flag); - } - - @Override - public void setCancellable(boolean flag) { - flags = set(CANCELLABLE, flags, flag); - } - - @Override - public void setLexical(boolean flag) { - flags = set(LEXICAL, flags, flag); - } - - @Override - public void setLexicalShade(boolean flag) { - flags = set(SHADE, flags, flag); - } - - @Override - public boolean isAntish() { - return isSet(ANTISH, flags); - } - - @Override - public boolean isSilent() { - return isSet(SILENT, flags); - } - - @Override - public boolean isStrict() { - return isSet(STRICT, flags); - } - - @Override - public boolean isSafe() { - return isSet(SAFE, flags); - } - - @Override - public boolean isCancellable() { - return isSet(CANCELLABLE, flags); - } - - @Override - public boolean isLexical() { - return isSet(LEXICAL, flags); - } - - @Override - public boolean isLexicalShade() { - return isSet(SHADE, flags); - } - - @Override - public boolean isStrictArithmetic() { - return strictArithmetic; - } - - @Override - public MathContext getMathContext() { - return mathContext; - } - - @Override - public int getMathScale() { - return mathScale; - } - -} diff --git a/src/main/java/org/apache/commons/jexl3/internal/Script.java b/src/main/java/org/apache/commons/jexl3/internal/Script.java index 62ecaca..593ad08 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/Script.java +++ b/src/main/java/org/apache/commons/jexl3/internal/Script.java @@ -17,6 +17,8 @@ package org.apache.commons.jexl3.internal; import org.apache.commons.jexl3.JexlContext; +import org.apache.commons.jexl3.JexlInfo; +import org.apache.commons.jexl3.JexlOptions; import org.apache.commons.jexl3.JexlEngine; import org.apache.commons.jexl3.JexlScript; import org.apache.commons.jexl3.JexlExpression; @@ -96,7 +98,39 @@ public class Script implements JexlScript, JexlExpression { protected Frame createFrame(Object[] args) { return script.createFrame(args); } + + /** + * Creates this script options for evaluation. + * <p>This also calls the pragma processor + * @param context the context + * @return the options + */ + protected JexlOptions createOptions(JexlContext context) { + return jexl.createOptions(this, context); + } + + /** + * A lexical script, ensures options are lexical. + */ + public static class Lexical extends Script { + /** + * Sole ctor. + * @param engine the engine + * @param expr the source. + * @param ref the ast + */ + protected Lexical(Engine engine, String expr, ASTJexlScript ref) { + super(engine, expr, ref); + } + @Override + public JexlOptions createOptions(JexlContext ctxt) { + JexlOptions opts = super.createOptions(ctxt); + opts.setLexical(true); + return opts; + } + } + /** * Creates this script interpreter. * @param context the context @@ -104,7 +138,7 @@ public class Script implements JexlScript, JexlExpression { * @return the interpreter */ protected Interpreter createInterpreter(JexlContext context, Frame frame) { - return jexl.createInterpreter(context, frame); + return jexl.createInterpreter(context, frame, createOptions(context)); } /** @@ -215,6 +249,13 @@ public class Script implements JexlScript, JexlExpression { public String[] getLocalVariables() { return script.getLocalVariables(); } + + /** + * @return the info + */ + public JexlInfo getInfo() { + return script.jexlInfo(); + } /** * Gets this script variables. @@ -259,7 +300,7 @@ public class Script implements JexlScript, JexlExpression { */ @Override public Callable callable(JexlContext context, Object... args) { - return new Callable(jexl.createInterpreter(context, script.createFrame(args))); + return new Callable(createInterpreter(context, script.createFrame(args))); } /** diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java index 17b0ff9..7babe6f 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java +++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateInterpreter.java @@ -48,7 +48,7 @@ public class TemplateInterpreter extends Interpreter { */ TemplateInterpreter(Engine jexl, JexlContext jcontext, Frame jframe, TemplateExpression[] expressions, Writer out) { - super(jexl, jcontext, jframe); + super(jexl, null, jcontext, jframe); exprs = expressions; writer = out; block = new LexicalFrame(frame, null); diff --git a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java index 5f23b11..8397ce0 100644 --- a/src/test/java/org/apache/commons/jexl3/AntishCallTest.java +++ b/src/test/java/org/apache/commons/jexl3/AntishCallTest.java @@ -173,7 +173,8 @@ public class AntishCallTest extends JexlTestCase { // JEXL-300 @Test public void testSafeAnt() throws Exception { - JexlContext ctxt = new MapContext(); + JexlEvalContext ctxt = new JexlEvalContext(); + JexlOptions options = ctxt.getEngineOptions(); ctxt.set("x.y.z", 42); JexlScript script; Object result; @@ -182,6 +183,18 @@ public class AntishCallTest extends JexlTestCase { result = script.execute(ctxt); Assert.assertEquals(42, result); Assert.assertEquals(42, ctxt.get("x.y.z")); + + options.setAntish(false); + try { + result = script.execute(ctxt); + Assert.fail("antish var shall not be resolved"); + } catch(JexlException.Variable xvar) { + Assert.assertTrue("x".equals(xvar.getVariable())); + } catch(JexlException xother) { + Assert.assertTrue(xother != null); + } finally { + options.setAntish(true); + } result = null; script = JEXL.createScript("x?.y?.z"); diff --git a/src/test/java/org/apache/commons/jexl3/Issues300Test.java b/src/test/java/org/apache/commons/jexl3/Issues300Test.java index d2b45ec..913133b 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues300Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues300Test.java @@ -324,25 +324,24 @@ public class Issues300Test { Assert.assertEquals(52, result); } - public static class ClazzB { - public int methodB() { - return 42; - } - } - public static class ClazzA { - public ClazzB methodA() { - return new ClazzB(); - } - } @Test - public void test317Tentative() throws Exception { + public void test317() throws Exception { JexlEngine jexl = new JexlBuilder().strict(true).create(); JexlContext ctxt = new MapContext(); JexlScript script; Object result; - script = jexl.createScript("x.methodA().methodB()", "x"); - result = script.execute(ctxt, new ClazzA()); + JexlInfo info = new JexlInfo("test317", 1, 1); + script = jexl.createScript(info, "var f = " + + "()-> {x + x }; f", + "x"); + result = script.execute(ctxt, 21); + Assert.assertTrue(result instanceof JexlScript); + script = (JexlScript) result; + info = JexlInfo.from(script); + Assert.assertNotNull(info); + Assert.assertEquals("test317", info.getName()); + result = script.execute(ctxt, 21); Assert.assertEquals(42, result); } } diff --git a/src/test/java/org/apache/commons/jexl3/JXLTTest.java b/src/test/java/org/apache/commons/jexl3/JXLTTest.java index c59193d..3986c2e 100644 --- a/src/test/java/org/apache/commons/jexl3/JXLTTest.java +++ b/src/test/java/org/apache/commons/jexl3/JXLTTest.java @@ -19,7 +19,6 @@ package org.apache.commons.jexl3; import org.apache.commons.jexl3.internal.TemplateDebugger; import org.apache.commons.jexl3.internal.TemplateScript; import org.apache.commons.jexl3.internal.Debugger; -import org.apache.commons.jexl3.internal.Options; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -806,7 +805,7 @@ public class JXLTTest extends JexlTestCase { } JexlOptions newOptions() { - options = new Options(); + options = new JexlOptions(); return options; } } diff --git a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java index 55fdcad..f0c233c 100644 --- a/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java +++ b/src/test/java/org/apache/commons/jexl3/JexlEvalContext.java @@ -19,7 +19,6 @@ package org.apache.commons.jexl3; import java.util.Collections; import java.util.Map; import org.apache.commons.jexl3.annotations.NoJexl; -import org.apache.commons.jexl3.internal.Options; /** * A JEXL evaluation environment wrapping variables, namespace and options. @@ -35,7 +34,7 @@ public class JexlEvalContext implements /** The namespace. */ private final JexlContext.NamespaceResolver ns; /** The options. */ - private final JexlOptions options = new Options(); + private final JexlOptions options = new JexlOptions(); diff --git a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java index f8d00d3..294ea4a 100644 --- a/src/test/java/org/apache/commons/jexl3/JexlTestCase.java +++ b/src/test/java/org/apache/commons/jexl3/JexlTestCase.java @@ -35,7 +35,7 @@ public class JexlTestCase { // important can be identified by the builder calling lexical(...). static { //JexlBuilder.setDefaultOptions("+safe", "-lexical"); - JexlBuilder.setDefaultOptions("-safe", "+lexical"); + JexlOptions.setDefaultFlags("-safe", "+lexical"); } /** No parameters signature for test run. */ private static final Class<?>[] NO_PARMS = {}; diff --git a/src/test/java/org/apache/commons/jexl3/LexicalTest.java b/src/test/java/org/apache/commons/jexl3/LexicalTest.java index cf8943e..891393a 100644 --- a/src/test/java/org/apache/commons/jexl3/LexicalTest.java +++ b/src/test/java/org/apache/commons/jexl3/LexicalTest.java @@ -19,6 +19,7 @@ package org.apache.commons.jexl3; import java.io.StringReader; import java.io.StringWriter; import java.util.Set; +import org.apache.commons.jexl3.internal.LexicalScope; import org.junit.Assert; import org.junit.Test; @@ -344,4 +345,46 @@ public class LexicalTest { Assert.assertNotNull(xany); } } + + @Test + public void testPragmaOptions() throws Exception { + // same as 6d but using a pragma + String str = "#pragma jexl.options '+strict +lexical +lexicalShade -safe'\n" + + "i = 0; for (var i : [42]) i; i"; + JexlEngine jexl = new JexlBuilder().strict(false).create(); + JexlScript e = jexl.createScript(str); + JexlContext ctxt = new MapContext(); + try { + Object o = e.execute(ctxt); + Assert.fail("i should be shaded"); + } catch (JexlException xany) { + Assert.assertNotNull(xany); + } + } + + @Test + public void testPragmaNoop() throws Exception { + // unknow pragma + String str = "#pragma jexl.options 'no effect'\ni = -42; for (var i : [42]) i; i"; + JexlEngine jexl = new JexlBuilder().lexical(false).strict(true).create(); + JexlScript e = jexl.createScript(str); + JexlContext ctxt = new MapContext(); + Object result = e.execute(ctxt); + Assert.assertEquals(42, result); + } + + + @Test + public void testScopeFrame() throws Exception { + LexicalScope scope = new LexicalScope(null); + for(int i = 0; i < 128; i += 2) { + Assert.assertTrue(scope.declareSymbol(i)); + Assert.assertFalse(scope.declareSymbol(i)); + } + for(int i = 0; i < 128; i += 2) { + Assert.assertTrue(scope.hasSymbol(i)); + Assert.assertFalse(scope.hasSymbol(i + 1)); + } + } + }