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