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
018package org.apache.commons.jexl3;
019
020import java.math.MathContext;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Map;
024
025import org.apache.commons.jexl3.internal.Engine;
026
027/**
028 * Flags and properties that can alter the evaluation behavior.
029 * The flags, briefly explained, are the following:
030 * <ul>
031 * <li>silent: whether errors throw exception</li>
032 * <li>safe: whether navigation through null is <em>not</em>an error</li>
033 * <li>cancellable: whether thread interruption is an error</li>
034 * <li>lexical: whether redefining local variables is an error</li>
035 * <li>lexicalShade: whether local variables shade global ones even outside their scope</li>
036 * <li>strict: whether unknown or unsolvable identifiers are errors</li>
037 * <li>strictArithmetic: whether null as operand is an error</li>
038 * <li>sharedInstance: whether these options can be modified at runtime during execution (expert)</li>
039 * <li>constCapture: whether captured variables will throw an error if an attempt is made to change their value</li>
040 * <li>strictInterpolation: whether interpolation strings always return a string or attempt to parse and return integer</li>
041 * <li>booleanLogical: whether logical expressions (&quot;&quot; , ||) coerce their result to boolean</li>
042 * </ul>
043 * The sensible default is cancellable, strict and strictArithmetic.
044 * <p>This interface replaces the now deprecated JexlEngine.Options.
045 * @since 3.2
046 */
047public final class JexlOptions {
048    /** The boolean logical flag. */
049    private static final int BOOLEAN_LOGICAL = 10;
050    /** The interpolation string bit. */
051    private static final int STRICT_INTERPOLATION= 9;
052    /** The const capture bit. */
053    private static final int CONST_CAPTURE = 8;
054    /** The shared instance bit. */
055    private static final int SHARED = 7;
056    /** The local shade bit. */
057    private static final int SHADE = 6;
058    /** The antish var bit. */
059    private static final int ANTISH = 5;
060    /** The lexical scope bit. */
061    private static final int LEXICAL = 4;
062    /** The safe bit. */
063    private static final int SAFE = 3;
064    /** The silent bit. */
065    private static final int SILENT = 2;
066    /** The strict bit. */
067    private static final int STRICT = 1;
068    /** The cancellable bit. */
069    private static final int CANCELLABLE = 0;
070    /** The flag names ordered. */
071    private static final String[] NAMES = {
072        "cancellable", "strict", "silent", "safe", "lexical", "antish",
073        "lexicalShade", "sharedInstance", "constCapture", "strictInterpolation",
074        "booleanShortCircuit"
075    };
076    /** Default mask .*/
077    private static int DEFAULT = 1 /*<< CANCELLABLE*/ | 1 << STRICT | 1 << ANTISH | 1 << SAFE;
078
079    /**
080     * Checks the value of a flag in the mask.
081     * @param ordinal the flag ordinal
082     * @param mask the flags mask
083     * @return the mask value with this flag or-ed in
084     */
085    private static boolean isSet(final int ordinal, final int mask) {
086        return (mask & 1 << ordinal) != 0;
087    }
088
089    /**
090     * Parses flags by name.
091     * <p>A '+flag' or 'flag' will set flag as true, '-flag' set as false.
092     * The possible flag names are:
093     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
094     * @param initial the initial mask state
095     * @param flags the flags to set
096     * @return the flag mask updated
097     */
098    public static int parseFlags(final int initial, final String... flags) {
099        int mask = initial;
100        for (final String flag : flags) {
101            boolean b = true;
102            final String name;
103            if (flag.charAt(0) == '+') {
104                name = flag.substring(1);
105            } else if (flag.charAt(0) == '-') {
106                name = flag.substring(1);
107                b = false;
108            } else {
109                name = flag;
110            }
111            for (int f = 0; f < NAMES.length; ++f) {
112                if (NAMES[f].equals(name)) {
113                    if (b) {
114                        mask |= 1 << f;
115                    } else {
116                        mask &= ~(1 << f);
117                    }
118                    break;
119                }
120            }
121        }
122        return mask;
123    }
124
125    /**
126     * Sets the value of a flag in a mask.
127     * @param ordinal the flag ordinal
128     * @param mask the flags mask
129     * @param value true or false
130     * @return the new flags mask value
131     */
132    private static int set(final int ordinal, final int mask, final boolean value) {
133        return value? mask | 1 << ordinal : mask & ~(1 << ordinal);
134    }
135
136    /**
137     * Sets the default (static, shared) option flags.
138     * <p>
139     * Whenever possible, we recommend using JexlBuilder methods to unambiguously instantiate a JEXL
140     * engine; this method should only be used for testing / validation.
141     * <p>A '+flag' or 'flag' will set the option named 'flag' as true, '-flag' set as false.
142     * The possible flag names are:
143     * cancellable, strict, silent, safe, lexical, antish, lexicalShade
144     * <p>Calling JexlBuilder.setDefaultOptions("+safe") once before JEXL engine creation
145     * may ease validating JEXL3.2 in your environment.
146     * @param flags the flags to set
147     */
148    public static void setDefaultFlags(final String...flags) {
149        DEFAULT = parseFlags(DEFAULT, flags);
150    }
151
152    /** The arithmetic math context. */
153    private MathContext mathContext;
154
155    /** The arithmetic math scale. */
156    private int mathScale = Integer.MIN_VALUE;
157
158    /** The arithmetic strict math flag. */
159    private boolean strictArithmetic = true;
160
161    /** The default flags, all but safe. */
162    private int flags = DEFAULT;
163
164    /** The namespaces .*/
165    private Map<String, Object> namespaces = Collections.emptyMap();
166
167    /** The imports. */
168    private Collection<String> imports = Collections.emptySet();
169
170    /**
171     * Default ctor.
172     */
173    public JexlOptions() {
174        // all inits in members declarations
175    }
176
177    /**
178     * Creates a copy of this instance.
179     * @return a copy
180     */
181    public JexlOptions copy() {
182        return new JexlOptions().set(this);
183    }
184
185    /**
186     * Gets the optional set of imported packages.
187     * @return the set of imports, may be empty, not null
188     */
189    public Collection<String> getImports() {
190        return imports;
191    }
192
193    /**
194     * The MathContext instance used for +,-,/,*,% operations on big decimals.
195     * @return the math context
196     */
197    public MathContext getMathContext() {
198        return mathContext;
199    }
200
201    /**
202     * The BigDecimal scale used for comparison and coercion operations.
203     * @return the scale
204     */
205    public int getMathScale() {
206        return mathScale;
207    }
208
209    /**
210     * Gets the optional map of namespaces.
211     * @return the map of namespaces, may be empty, not null
212     */
213    public Map<String, Object> getNamespaces() {
214        return namespaces;
215    }
216
217    /**
218     * Checks whether evaluation will attempt resolving antish variable names.
219     * @return true if antish variables are solved, false otherwise
220     */
221    public boolean isAntish() {
222        return isSet(ANTISH, flags);
223    }
224
225    /**
226     * Gets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean; if set,
227     * an expression like (3 &quot;&quot; 4 &quot;&quot; 5) will evaluate to true. If not, it will evaluate to 5.
228     * To preserve strict compatibility with 3.4, set the flag to true.
229     * @return true if short-circuit logicals coerce their result to boolean, false otherwise
230     * @since 3.5.0
231     */
232    public boolean isBooleanLogical() {
233        return isSet(BOOLEAN_LOGICAL, flags);
234    }
235
236    /**
237     * Checks whether evaluation will throw JexlException.Cancel (true) or
238     * return null (false) if interrupted.
239     * @return true when cancellable, false otherwise
240     */
241    public boolean isCancellable() {
242        return isSet(CANCELLABLE, flags);
243    }
244
245    /**
246     * Are lambda captured-variables const?
247     * @return true if lambda captured-variables are const, false otherwise
248     */
249    public boolean isConstCapture() {
250        return isSet(CONST_CAPTURE, flags);
251    }
252
253    /**
254     * Checks whether runtime variable scope is lexical.
255     * <p>If true, lexical scope applies to local variables and parameters.
256     * Redefining a variable in the same lexical unit will generate errors.
257     * @return true if scope is lexical, false otherwise
258     */
259    public boolean isLexical() {
260        return isSet(LEXICAL, flags);
261    }
262
263    /**
264     * Checks whether local variables shade global ones.
265     * <p>After a symbol is defined as local, dereferencing it outside its
266     * scope will trigger an error instead of seeking a global variable of the
267     * same name. To further reduce potential naming ambiguity errors,
268     * global variables (ie non-local) must be declared to be assigned (@link JexlContext#has(String) )
269     * when this flag is on; attempting to set an undeclared global variables will
270     * raise an error.
271     * @return true if lexical shading is applied, false otherwise
272     */
273    public boolean isLexicalShade() {
274        return isSet(SHADE, flags);
275    }
276
277    /**
278     * Checks whether the engine considers null in navigation expression as
279     * errors during evaluation.
280     * @return true if safe, false otherwise
281     */
282    public boolean isSafe() {
283        return isSet(SAFE, flags);
284    }
285
286    /**
287     * Gets sharing state.
288     * @return false if a copy of these options is used during execution,
289     * true if those can potentially be modified
290     */
291    public boolean isSharedInstance() {
292        return isSet(SHARED, flags);
293    }
294
295    /**
296     * Checks whether the engine will throw a {@link JexlException} when an
297     * error is encountered during evaluation.
298     * @return true if silent, false otherwise
299     */
300    public boolean isSilent() {
301        return isSet(SILENT, flags);
302    }
303
304    /**
305     * Checks whether the engine considers unknown variables, methods and
306     * constructors as errors during evaluation.
307     * @return true if strict, false otherwise
308     */
309    public boolean isStrict() {
310        return isSet(STRICT, flags);
311    }
312
313    /**
314     * Checks whether the arithmetic triggers errors during evaluation when null
315     * is used as an operand.
316     * @return true if strict, false otherwise
317     */
318    public boolean isStrictArithmetic() {
319        return strictArithmetic;
320    }
321
322    /**
323     * Gets the strict-interpolation flag of this options instance.
324     * @return true if interpolation strings always return string, false otherwise
325     */
326    public boolean isStrictInterpolation() {
327        return isSet(STRICT_INTERPOLATION, flags);
328    }
329
330    /**
331     * Sets options from engine.
332     * @param jexl the engine
333     * @return this instance
334     */
335    public JexlOptions set(final JexlEngine jexl) {
336        if (jexl instanceof Engine) {
337            ((Engine) jexl).optionsSet(this);
338        }
339        return this;
340    }
341
342    /**
343     * Sets options from options.
344     * @param src the options
345     * @return this instance
346     */
347    public JexlOptions set(final JexlOptions src) {
348        mathContext = src.mathContext;
349        mathScale = src.mathScale;
350        strictArithmetic = src.strictArithmetic;
351        flags = src.flags;
352        namespaces = src.namespaces;
353        imports = src.imports;
354        return this;
355    }
356
357    /**
358     * Sets whether the engine will attempt solving antish variable names from
359     * context.
360     * @param flag true if antish variables are solved, false otherwise
361     */
362    public void setAntish(final boolean flag) {
363        flags = set(ANTISH, flags, flag);
364    }
365
366    /**
367     * Sets whether logical expressions (&quot;&quot; , ||) coerce their result to boolean.
368     * @param flag true or false
369     */
370    public void setBooleanLogical(final boolean flag) {
371        flags = set(BOOLEAN_LOGICAL, flags, flag);
372    }
373
374    /**
375     * Sets whether the engine will throw JexlException.Cancel (true) or return
376     * null (false) when interrupted during evaluation.
377     * @param flag true when cancellable, false otherwise
378     */
379    public void setCancellable(final boolean flag) {
380        flags = set(CANCELLABLE, flags, flag);
381    }
382
383    /**
384     * Sets whether lambda captured-variables are const or not.
385     * <p>
386     * When disabled, lambda-captured variables are implicitly converted to read-write local variable (let),
387     * when enabled, those are implicitly converted to read-only local variables (const).
388     * </p>
389     * @param flag true to enable, false to disable
390     */
391    public void setConstCapture(final boolean flag) {
392        flags = set(CONST_CAPTURE, flags, flag);
393    }
394
395    /**
396     * Sets this option flags using the +/- syntax.
397     * @param opts the option flags
398     */
399    public void setFlags(final String... opts) {
400        flags = parseFlags(flags, opts);
401    }
402
403    /**
404     * Sets the optional set of imports.
405     * @param imports the imported packages
406     */
407    public void setImports(final Collection<String> imports) {
408        this.imports = imports == null || imports.isEmpty()? Collections.emptySet() : imports;
409    }
410
411    /**
412     * Sets whether the engine uses a strict block lexical scope during
413     * evaluation.
414     * @param flag true if lexical scope is used, false otherwise
415     */
416    public void setLexical(final boolean flag) {
417        flags = set(LEXICAL, flags, flag);
418    }
419
420    /**
421     * Sets whether the engine strictly shades global variables.
422     * Local symbols shade globals after definition and creating global
423     * variables is prohibited during evaluation.
424     * If setting to lexical shade, lexical scope is also set.
425     * @param flag true if creation is allowed, false otherwise
426     */
427    public void setLexicalShade(final boolean flag) {
428        flags = set(SHADE, flags, flag);
429        if (flag) {
430            flags = set(LEXICAL, flags, true);
431        }
432    }
433
434    /**
435     * Sets the arithmetic math context.
436     * @param mcontext the context
437     */
438    public void setMathContext(final MathContext mcontext) {
439        this.mathContext = mcontext;
440    }
441
442    /**
443     * Sets the arithmetic math scale.
444     * @param mscale the scale
445     */
446    public void setMathScale(final int mscale) {
447        this.mathScale = mscale;
448    }
449
450    /**
451     * Sets the optional map of namespaces.
452     * @param ns a namespaces map
453     */
454    public void setNamespaces(final Map<String, Object> ns) {
455        this.namespaces = ns == null || ns.isEmpty()? Collections.emptyMap() : ns;
456    }
457
458    /**
459     * Sets whether the engine considers null in navigation expression as null or as errors
460     * during evaluation.
461     * <p>If safe, encountering null during a navigation expression - dereferencing a method or a field through a null
462     * object or property - will <em>not</em> be considered an error but evaluated as <em>null</em>. It is recommended
463     * to use <em>setSafe(false)</em> as an explicit default.</p>
464     * @param flag true if safe, false otherwise
465     */
466    public void setSafe(final boolean flag) {
467        flags = set(SAFE, flags, flag);
468    }
469
470    /**
471     * Sets wether these options are immutable at runtime.
472     * <p>Expert mode; allows instance handled through context to be shared
473     * instead of copied.
474     * @param flag true if shared, false if not
475     */
476    public void setSharedInstance(final boolean flag) {
477        flags = set(SHARED, flags, flag);
478    }
479
480    /**
481     * Sets whether the engine will throw a {@link JexlException} when an error
482     * is encountered during evaluation.
483     * @param flag true if silent, false otherwise
484     */
485    public void setSilent(final boolean flag) {
486        flags = set(SILENT, flags, flag);
487    }
488
489    /**
490     * Sets whether the engine considers unknown variables, methods and
491     * constructors as errors during evaluation.
492     * @param flag true if strict, false otherwise
493     */
494    public void setStrict(final boolean flag) {
495        flags = set(STRICT, flags, flag);
496    }
497
498    /**
499     * Sets the strict arithmetic flag.
500     * @param stricta true or false
501     */
502    public void setStrictArithmetic(final boolean stricta) {
503        this.strictArithmetic = stricta;
504    }
505
506    /**
507     * Sets the strict interpolation flag.
508     * <p>When strict, interpolation strings composed only of an expression (ie `${...}`) are evaluated
509     * as strings; when not strict, integer results are left untouched.</p>
510     * This can affect the results of expressions like <code>map.`${key}`</code> when key is
511     * an integer (or a string); it is almost always possible to use <code>map[key]</code> to ensure
512     * that the key type is not altered.
513     * @param strict true or false
514     */
515    public void setStrictInterpolation(final boolean strict) {
516        flags = set(STRICT_INTERPOLATION, flags, strict);
517    }
518
519    @Override public String toString() {
520        final StringBuilder strb = new StringBuilder();
521        for(int i = 0; i < NAMES.length; ++i) {
522            if (i > 0) {
523                strb.append(' ');
524            }
525            strb.append((flags & 1 << i) != 0? '+':'-');
526            strb.append(NAMES[i]);
527        }
528        return strb.toString();
529    }
530
531}