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