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