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