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