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    package org.apache.commons.jexl2;
018    
019    import java.util.ArrayList;
020    import org.apache.commons.jexl2.parser.JexlNode;
021    import org.apache.commons.jexl2.parser.StringParser;
022    
023    /**
024     * An evaluator similar to the Unified EL evaluator used in JSP/JSF based on JEXL.
025     * It is intended to be used in configuration modules, XML based frameworks or JSP taglibs
026     * and facilitate the implementation of expression evaluation.
027     * <p>
028     * An expression can mix immediate, deferred and nested sub-expressions as well as string constants;
029     * <ul>
030     * <li>The "immediate" syntax is of the form <code>"...${jexl-expr}..."</code></li>
031     * <li>The "deferred" syntax is of the form <code>"...#{jexl-expr}..."</code></li>
032     * <li>The "nested" syntax is of the form <code>"...#{...${jexl-expr0}...}..."</code></li>
033     * <li>The "composite" syntax is of the form <code>"...${jexl-expr0}... #{jexl-expr1}..."</code></li>
034     * </ul>
035     * </p>
036     * <p>
037     * Deferred & immediate expression carry different intentions:
038     * <ul>
039     * <li>An immediate expression indicate that evaluation is intended to be performed close to
040     * the definition/parsing point.</li>
041     * <li>A deferred expression indicate that evaluation is intended to occur at a later stage.</li>
042     * </ul>
043     * </p>
044     * <p>
045     * For instance: <code>"Hello ${name}, now is #{time}"</code> is a composite "deferred" expression since one
046     * of its subexpressions is deferred. Furthermore, this (composite) expression intent is
047     * to perform two evaluations; one close to its definition and another one in a later
048     * phase.
049     * </p>
050     * <p>
051     * The API reflects this feature in 2 methods, prepare and evaluate. The prepare method
052     * will evaluate the immediate subexpression and return an expression that contains only
053     * the deferred subexpressions (& constants), a prepared expression. Such a prepared expression
054     * is suitable for a later phase evaluation that may occur with a different JexlContext.
055     * Note that it is valid to call evaluate without prepare in which case the same JexlContext
056     * is used for the 2 evaluation phases.
057     * </p>
058     * <p>
059     * In the most common use-case where deferred expressions are to be kept around as properties of objects,
060     * one should parse & prepare an expression before storing it and evaluate it each time
061     * the property storing it is accessed.
062     * </p>
063     * <p>
064     * Note that nested expression use the JEXL syntax as in:
065     * <code>"#{${bar}+'.charAt(2)'}"</code>
066     * The most common mistake leading to an invalid expression being the following:
067     * <code>"#{${bar}charAt(2)}"</code>
068     * </p>
069     * <p>Also note that methods that parse evaluate expressions may throw <em>unchecked</em> ecxeptions;
070     * The {@link UnifiedJEXL.Exception} are thrown when the engine instance is in "non-silent" mode
071     * but since these are RuntimeException, user-code <em>should</em> catch them where appropriate.
072     * </p>
073     * @since 2.0
074     */
075    public final class UnifiedJEXL {
076        /** The JEXL engine instance. */
077        private final JexlEngine jexl;
078        /** The expression cache. */
079        private final JexlEngine.SoftCache<String,Expression> cache;
080        /** The default cache size. */
081        private static final int CACHE_SIZE = 256;
082        /**
083         * Creates a new instance of UnifiedJEXL with a default size cache.
084         * @param aJexl the JexlEngine to use.
085         */
086        public UnifiedJEXL(JexlEngine aJexl) {
087            this(aJexl, CACHE_SIZE);
088        }
089    
090        /**
091         * Creates a new instance of UnifiedJEXL creating a local cache.
092         * @param aJexl the JexlEngine to use.
093         * @param cacheSize the number of expressions in this cache
094         */
095        public UnifiedJEXL(JexlEngine aJexl, int cacheSize) {
096            this.jexl = aJexl;
097            this.cache = aJexl.new SoftCache<String,Expression>(cacheSize);
098        }
099    
100        /**
101         * Types of expressions.
102         * Each instance carries a counter index per (composite sub-) expression type.
103         * @see ExpressionBuilder
104         */
105        private static enum ExpressionType {
106            /** Constant expression, count index 0. */
107            CONSTANT(0),
108            /** Immediate expression, count index 1. */
109            IMMEDIATE(1),
110            /** Deferred expression, count index 2. */
111            DEFERRED(2),
112            /** Nested (which are deferred) expressions, count index 2. */
113            NESTED(2),
114            /** Composite expressions are not counted, index -1. */
115            COMPOSITE(-1);
116            /** The index in arrays of expression counters for composite expressions. */
117            private final int index;
118            /**
119             * Creates an ExpressionType.
120             * @param idx the index for this type in counters arrays.
121             */
122            ExpressionType(int idx) {
123                this.index = idx;
124            }
125        }
126    
127        /**
128         * A helper class to build expressions.
129         * Keeps count of sub-expressions by type.
130         */
131        private static class ExpressionBuilder {
132            /** Per expression type counters. */
133            private final int[] counts;
134            /** The list of expressions. */
135            private final ArrayList<Expression> expressions;
136    
137            /**
138             * Creates a builder.
139             * @param size the initial expression array size
140             */
141            ExpressionBuilder(int size) {
142                counts = new int[]{0, 0, 0};
143                expressions = new ArrayList<Expression>(size <= 0 ? 3 : size);
144            }
145    
146            /**
147             * Adds an expression to the list of expressions, maintain per-type counts.
148             * @param expr the expression to add
149             */
150            void add(Expression expr) {
151                counts[expr.getType().index] += 1;
152                expressions.add(expr);
153            }
154    
155            /**
156             * Builds an expression from a source, performs checks.
157             * @param el the unified el instance
158             * @param source the source expression
159             * @return an expression
160             */
161            Expression build(UnifiedJEXL el, Expression source) {
162                int sum = 0;
163                for (int count : counts) {
164                    sum += count;
165                }
166                if (expressions.size() != sum) {
167                    StringBuilder error = new StringBuilder("parsing algorithm error, exprs: ");
168                    error.append(expressions.size());
169                    error.append(", constant:");
170                    error.append(counts[ExpressionType.CONSTANT.index]);
171                    error.append(", immediate:");
172                    error.append(counts[ExpressionType.IMMEDIATE.index]);
173                    error.append(", deferred:");
174                    error.append(counts[ExpressionType.DEFERRED.index]);
175                    throw new IllegalStateException(error.toString());
176                }
177                // if only one sub-expr, no need to create a composite
178                if (expressions.size() == 1) {
179                    return expressions.get(0);
180                } else {
181                    return el.new CompositeExpression(counts, expressions, source);
182                }
183            }
184        }
185    
186        /**
187         * Gets the JexlEngine underlying the UnifiedJEXL.
188         * @return the JexlEngine
189         */
190        public JexlEngine getEngine() {
191            return jexl;
192        }
193    
194        /**
195         * The sole type of (runtime) exception the UnifiedJEXL can throw.
196         */
197        public static class Exception extends RuntimeException {
198            /** Serial version UID. */
199            private static final long serialVersionUID = -8201402995815975726L;
200            /**
201             * Creates a UnifiedJEXL.Exception.
202             * @param msg the exception message
203             * @param cause the exception cause
204             */
205            public Exception(String msg, Throwable cause) {
206                super(msg, cause);
207            }
208        }
209    
210        /**
211         * The abstract base class for all expressions, immediate '${...}' and deferred '#{...}'.
212         */
213        public abstract class Expression {
214            /** The source of this expression (see {@link UnifiedJEXL.Expression#prepare}). */
215            protected final Expression source;
216            /**
217             * Creates an expression.
218             * @param src the source expression if any
219             */
220            Expression(Expression src) {
221                this.source = src != null ? src : this;
222            }
223    
224            /**
225             * Formats this expression, adding its source string representation in
226             * comments if available: 'expression /*= source *\/'' .
227             * @return the formatted expression string
228             */
229            @Override
230            public String toString() {
231                StringBuilder strb = new StringBuilder();
232                if (source != this) {
233                    strb.append(source.toString());
234                    strb.append(" /*= ");
235                }
236                asString(strb);
237                if (source != this) {
238                    strb.append(" */");
239                }
240                return strb.toString();
241            }
242    
243            /**
244             * Generates this expression's string representation.
245             * @return the string representation
246             */
247            public String asString() {
248                StringBuilder strb = new StringBuilder();
249                asString(strb);
250                return strb.toString();
251            }
252    
253            /**
254             * Adds this expression's string representation to a StringBuilder.
255             * @param strb the builder to fill
256             */
257            abstract void asString(StringBuilder strb);
258    
259            /**
260             * When the expression is dependant upon immediate and deferred sub-expressions,
261             * evaluates the immediate sub-expressions with the context passed as parameter
262             * and returns this expression deferred form.
263             * <p>
264             * In effect, this binds the result of the immediate sub-expressions evaluation in the
265             * context, allowing to differ evaluation of the remaining (deferred) expression within another context.
266             * This only has an effect to nested & composite expressions that contain differed & immediate sub-expressions.
267             * </p>
268             * <p>
269             * If the underlying JEXL engine is silent, errors will be logged through its logger as warning.
270             * </p>
271             * @param context the context to use for immediate expression evaluations
272             * @return  an expression or null if an error occurs and the {@link JexlEngine} is silent
273             * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
274             */
275            public abstract Expression prepare(JexlContext context);
276    
277            /**
278             * Evaluates this expression.
279             * <p>
280             * If the underlying JEXL engine is silent, errors will be logged through its logger as warning.
281             * </p>
282             * @param context the variable context
283             * @return the result of this expression evaluation or null if an error occurs and the {@link JexlEngine} is
284             * silent
285             * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
286             */
287            public abstract Object evaluate(JexlContext context);
288    
289            /**
290             * Checks whether this expression is immediate.
291             * @return true if immediate, false otherwise
292             */
293            public boolean isImmediate() {
294                return true;
295            }
296    
297            /**
298             * Checks whether this expression is deferred.
299             * @return true if deferred, false otherwise
300             */
301            public final boolean isDeferred() {
302                return !isImmediate();
303            }
304    
305            /**
306             * Retrieves this expression's source expression.
307             * If this expression was prepared, this allows to retrieve the
308             * original expression that lead to it.
309             * Other expressions return themselves.
310             * @return the source expression
311             */
312            public final Expression getSource() {
313                return source;
314            }
315    
316            /**
317             * Gets this expression type.
318             * @return its type
319             */
320            abstract ExpressionType getType();
321    
322            /**
323             * Prepares a sub-expression for interpretation.
324             * @param interpreter a JEXL interpreter
325             * @return a prepared expression
326             * @throws JexlException (only for nested & composite)
327             */
328            abstract Expression prepare(Interpreter interpreter);
329    
330            /**
331             * Intreprets a sub-expression.
332             * @param interpreter a JEXL interpreter
333             * @return the result of interpretation
334             * @throws JexlException (only for nested & composite)
335             */
336            abstract Object evaluate(Interpreter interpreter);
337        }
338    
339    
340        /** A constant expression. */
341        private class ConstantExpression extends Expression {
342            /** The constant held by this expression. */
343            private final Object value;
344            /**
345             * Creates a constant expression.
346             * <p>
347             * If the wrapped constant is a string, it is treated
348             * as a JEXL strings with respect to escaping.
349             * </p>
350             * @param val the constant value
351             * @param source the source expression if any
352             */
353            ConstantExpression(Object val, Expression source) {
354                super(source);
355                if (val == null) {
356                    throw new NullPointerException("constant can not be null");
357                }
358                if (val instanceof String) {
359                    val = StringParser.buildString((String) val, false);
360                }
361                this.value = val;
362            }
363    
364            /** {@inheritDoc} */
365            @Override
366            public String asString() {
367                StringBuilder strb = new StringBuilder();
368                strb.append('"');
369                asString(strb);
370                strb.append('"');
371                return strb.toString();
372            }
373    
374            /** {@inheritDoc} */
375            @Override
376            ExpressionType getType() {
377                return ExpressionType.CONSTANT;
378            }
379    
380            /** {@inheritDoc} */
381            @Override
382            void asString(StringBuilder strb) {
383                String str = value.toString();
384                if (value instanceof String || value instanceof CharSequence) {
385                    for (int i = 0, size = str.length(); i < size; ++i) {
386                        char c = str.charAt(i);
387                        if (c == '"' || c == '\\') {
388                            strb.append('\\');
389                        }
390                        strb.append(c);
391                    }
392                } else {
393                    strb.append(str);
394                }
395            }
396    
397            /** {@inheritDoc} */
398            @Override
399            public Expression prepare(JexlContext context) {
400                return this;
401            }
402    
403            /** {@inheritDoc} */
404            @Override
405            Expression prepare(Interpreter interpreter) {
406                return this;
407            }
408    
409            /** {@inheritDoc} */
410            @Override
411            public Object evaluate(JexlContext context) {
412                return value;
413            }
414    
415            /** {@inheritDoc} */
416            @Override
417            Object evaluate(Interpreter interpreter) {
418                return value;
419            }
420        }
421    
422    
423        /** The base for Jexl based expressions. */
424        private abstract class JexlBasedExpression extends Expression {
425            /** The JEXL string for this expression. */
426            protected final CharSequence expr;
427            /** The JEXL node for this expression. */
428            protected final JexlNode node;
429            /**
430             * Creates a JEXL interpretable expression.
431             * @param theExpr the expression as a string
432             * @param theNode the expression as an AST
433             * @param theSource the source expression if any
434             */
435            protected JexlBasedExpression(CharSequence theExpr, JexlNode theNode, Expression theSource) {
436                super(theSource);
437                this.expr = theExpr;
438                this.node = theNode;
439            }
440    
441            /** {@inheritDoc} */
442            @Override
443            public String toString() {
444                StringBuilder strb = new StringBuilder(expr.length() + 3);
445                if (source != this) {
446                    strb.append(source.toString());
447                    strb.append(" /*= ");
448                }
449                strb.append(isImmediate() ? '$' : '#');
450                strb.append("{");
451                strb.append(expr);
452                strb.append("}");
453                if (source != this) {
454                    strb.append(" */");
455                }
456                return strb.toString();
457            }
458    
459            /** {@inheritDoc} */
460            @Override
461            public void asString(StringBuilder strb) {
462                strb.append(isImmediate() ? '$' : '#');
463                strb.append("{");
464                strb.append(expr);
465                strb.append("}");
466            }
467    
468            /** {@inheritDoc} */
469            @Override
470            public Expression prepare(JexlContext context) {
471                return this;
472            }
473    
474            /** {@inheritDoc} */
475            @Override
476            Expression prepare(Interpreter interpreter) {
477                return this;
478            }
479    
480            /** {@inheritDoc} */
481            @Override
482            public Object evaluate(JexlContext context) {
483                return UnifiedJEXL.this.evaluate(context, this);
484            }
485    
486            /** {@inheritDoc} */
487            @Override
488            Object evaluate(Interpreter interpreter) {
489                return interpreter.interpret(node);
490            }
491        }
492    
493    
494        /** An immediate expression: ${jexl}. */
495        private class ImmediateExpression extends JexlBasedExpression {
496            /**
497             * Creates an immediate expression.
498             * @param expr the expression as a string
499             * @param node the expression as an AST
500             * @param source the source expression if any
501             */
502            ImmediateExpression(CharSequence expr, JexlNode node, Expression source) {
503                super(expr, node, source);
504            }
505    
506            /** {@inheritDoc} */
507            @Override
508            ExpressionType getType() {
509                return ExpressionType.IMMEDIATE;
510            }
511    
512            /** {@inheritDoc} */
513            @Override
514            public boolean isImmediate() {
515                return true;
516            }
517        }
518    
519        /** An immediate expression: ${jexl}. */
520        private class DeferredExpression extends JexlBasedExpression {
521            /**
522             * Creates a deferred expression.
523             * @param expr the expression as a string
524             * @param node the expression as an AST
525             * @param source the source expression if any
526             */
527            DeferredExpression(CharSequence expr, JexlNode node, Expression source) {
528                super(expr, node, source);
529            }
530    
531            /** {@inheritDoc} */
532            @Override
533            ExpressionType getType() {
534                return ExpressionType.DEFERRED;
535            }
536    
537            /** {@inheritDoc} */
538            @Override
539            public boolean isImmediate() {
540                return false;
541            }
542        }
543    
544        /**
545         * A deferred expression that nests an immediate expression.
546         * #{...${jexl}...}
547         * Note that the deferred syntax is JEXL's, not UnifiedJEXL.
548         */
549        private class NestedExpression extends DeferredExpression {
550            /**
551             * Creates a nested expression.
552             * @param expr the expression as a string
553             * @param node the expression as an AST
554             * @param source the source expression if any
555             */
556            NestedExpression(CharSequence expr, JexlNode node, Expression source) {
557                super(expr, node, source);
558                if (this.source != this) {
559                    throw new IllegalArgumentException("Nested expression can not have a source");
560                }
561            }
562    
563            /** {@inheritDoc} */
564            @Override
565            ExpressionType getType() {
566                return ExpressionType.NESTED;
567            }
568    
569            /** {@inheritDoc} */
570            @Override
571            public String toString() {
572                return expr.toString();
573            }
574    
575            /** {@inheritDoc} */
576            @Override
577            public Expression prepare(JexlContext context) {
578                return UnifiedJEXL.this.prepare(context, this);
579            }
580    
581            /** {@inheritDoc} */
582            @Override
583            public Expression prepare(Interpreter interpreter) {
584                String value = interpreter.interpret(node).toString();
585                JexlNode dnode = toNode(value, jexl.isDebug()? node.getInfo() : null);
586                return new DeferredExpression(value, dnode, this);
587            }
588    
589            /** {@inheritDoc} */
590            @Override
591            public Object evaluate(Interpreter interpreter) {
592                return prepare(interpreter).evaluate(interpreter);
593            }
594        }
595    
596    
597        /** A composite expression: "... ${...} ... #{...} ...". */
598        private class CompositeExpression extends Expression {
599            /** Bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0. */
600            private final int meta;
601            /** The list of sub-expression resulting from parsing. */
602            private final Expression[] exprs;
603            /**
604             * Creates a composite expression.
605             * @param counters counters of expression per type
606             * @param list the sub-expressions
607             * @param src the source for this expresion if any
608             */
609            CompositeExpression(int[] counters, ArrayList<Expression> list, Expression src) {
610                super(src);
611                this.exprs = list.toArray(new Expression[list.size()]);
612                this.meta = (counters[ExpressionType.DEFERRED.index] > 0 ? 2 : 0)
613                          | (counters[ExpressionType.IMMEDIATE.index] > 0 ? 1 : 0);
614            }
615    
616            /** {@inheritDoc} */
617            @Override
618            ExpressionType getType() {
619                return ExpressionType.COMPOSITE;
620            }
621    
622            /** {@inheritDoc} */
623            @Override
624            public boolean isImmediate() {
625                // immediate if no deferred
626                return (meta & 2) == 0;
627            }
628    
629            /** {@inheritDoc} */
630            @Override
631            void asString(StringBuilder strb) {
632                for (Expression e : exprs) {
633                    e.asString(strb);
634                }
635            }
636    
637            /** {@inheritDoc} */
638            @Override
639            public Expression prepare(JexlContext context) {
640                return UnifiedJEXL.this.prepare(context, this);
641            }
642    
643            /** {@inheritDoc} */
644            @Override
645            Expression prepare(Interpreter interpreter) {
646                // if this composite is not its own source, it is already prepared
647                if (source != this) {
648                    return this;
649                }
650                // we need to eval immediate expressions if there are some deferred/nested
651                // ie both immediate & deferred counts > 0, bits 1 & 0 set, (1 << 1) & 1 == 3
652                final boolean evalImmediate = meta == 3;
653                final int size = exprs.length;
654                final ExpressionBuilder builder = new ExpressionBuilder(size);
655                // tracking whether prepare will return a different expression
656                boolean eq = true;
657                for (int e = 0; e < size; ++e) {
658                    Expression expr = exprs[e];
659                    Expression prepared = expr.prepare(interpreter);
660                    if (evalImmediate && prepared instanceof ImmediateExpression) {
661                        // evaluate immediate as constant
662                        Object value = prepared.evaluate(interpreter);
663                        prepared = value == null ? null : new ConstantExpression(value, prepared);
664                    }
665                    // add it if not null
666                    if (prepared != null) {
667                        builder.add(prepared);
668                    }
669                    // keep track of expression equivalence
670                    eq &= expr == prepared;
671                }
672                Expression ready = eq ? this : builder.build(UnifiedJEXL.this, this);
673                return ready;
674            }
675    
676            /** {@inheritDoc} */
677            @Override
678            public Object evaluate(JexlContext context) {
679                return UnifiedJEXL.this.evaluate(context, this);
680            }
681    
682            /** {@inheritDoc} */
683            @Override
684            Object evaluate(Interpreter interpreter) {
685                final int size = exprs.length;
686                Object value = null;
687                // common case: evaluate all expressions & concatenate them as a string
688                StringBuilder strb = new StringBuilder();
689                for (int e = 0; e < size; ++e) {
690                    value = exprs[e].evaluate(interpreter);
691                    if (value != null) {
692                        strb.append(value.toString());
693                    }
694                }
695                value = strb.toString();
696                return value;
697            }
698        }
699    
700        /** Creates a a {@link UnifiedJEXL.Expression} from an expression string.
701         *  Uses & fills up the expression cache if any.
702         * <p>
703         * If the underlying JEXL engine is silent, errors will be logged through its logger as warnings.
704         * </p>
705         * @param expression the UnifiedJEXL string expression
706         * @return the UnifiedJEXL object expression, null if silent and an error occured
707         * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
708         */
709        public Expression parse(String expression) {
710            Exception xuel = null;
711            Expression stmt = null;
712            try {
713                if (cache == null) {
714                    stmt = parseExpression(expression);
715                } else {
716                    synchronized (cache) {
717                        stmt = cache.get(expression);
718                        if (stmt == null) {
719                            stmt = parseExpression(expression);
720                            cache.put(expression, stmt);
721                        }
722                    }
723                }
724            } catch (JexlException xjexl) {
725                xuel = new Exception("failed to parse '" + expression + "'", xjexl);
726            } catch (Exception xany) {
727                xuel = xany;
728            } finally {
729                if (xuel != null) {
730                    if (jexl.isSilent()) {
731                        jexl.logger.warn(xuel.getMessage(), xuel.getCause());
732                        return null;
733                    }
734                    throw xuel;
735                }
736            }
737            return stmt;
738        }
739    
740        /**
741         * Prepares an expression (nested & composites), handles exception reporting.
742         *
743         * @param context the JEXL context to use
744         * @param expr the expression to prepare
745         * @return a prepared expression
746         * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
747         */
748        Expression prepare(JexlContext context, Expression expr) {
749            try {
750                Interpreter interpreter = jexl.createInterpreter(context);
751                interpreter.setSilent(false);
752                return expr.prepare(interpreter);
753            } catch (JexlException xjexl) {
754                Exception xuel = createException("prepare", expr, xjexl);
755                if (jexl.isSilent()) {
756                    jexl.logger.warn(xuel.getMessage(), xuel.getCause());
757                    return null;
758                }
759                throw xuel;
760            }
761        }
762    
763        /**
764         * Evaluates an expression (nested & composites), handles exception reporting.
765         *
766         * @param context the JEXL context to use
767         * @param expr the expression to prepare
768         * @return the result of the evaluation
769         * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
770         */
771        Object evaluate(JexlContext context, Expression expr) {
772            try {
773                Interpreter interpreter = jexl.createInterpreter(context);
774                interpreter.setSilent(false);
775                return expr.evaluate(interpreter);
776            } catch (JexlException xjexl) {
777                Exception xuel = createException("evaluate", expr, xjexl);
778                if (jexl.isSilent()) {
779                    jexl.logger.warn(xuel.getMessage(), xuel.getCause());
780                    return null;
781                }
782                throw xuel;
783            }
784        }
785    
786        /**
787         * Use the JEXL parser to create the AST for an expression.
788         * @param expression the expression to parse
789         * @return the AST
790         * @throws JexlException if an error occur during parsing
791         */
792        private JexlNode toNode(CharSequence expression) {
793            return jexl.parse(expression, null);
794        }
795        
796        /**
797         * Use the JEXL parser to create the AST for an expression.
798         * @param expression the expression to parse
799         * @param info debug information
800         * @return the AST
801         * @throws JexlException if an error occur during parsing
802         */
803        private JexlNode toNode(CharSequence expression, JexlInfo info) {
804            return jexl.parse(expression, info);
805        }
806    
807        /**
808         * Creates a UnifiedJEXL.Exception from a JexlException.
809         * @param action parse, prepare, evaluate
810         * @param expr the expression
811         * @param xany the exception
812         * @return an exception containing an explicit error message
813         */
814        private Exception createException(String action, Expression expr, java.lang.Exception xany) {
815            StringBuilder strb = new StringBuilder("failed to ");
816            strb.append(action);
817            strb.append(" '");
818            strb.append(expr.toString());
819            strb.append("'");
820            Throwable cause = xany.getCause();
821            if (cause != null) {
822                String causeMsg = cause.getMessage();
823                if (causeMsg != null) {
824                    strb.append(", ");
825                    strb.append(causeMsg);
826                }
827            }
828            return new Exception(strb.toString(), xany);
829        }
830    
831    
832        /** The different parsing states. */
833        private static enum ParseState {
834            /** Parsing a constant. */
835            CONST,
836            /** Parsing after $ .*/
837            IMMEDIATE0,
838            /** Parsing after # .*/
839            DEFERRED0,
840            /** Parsing after ${ .*/
841            IMMEDIATE1,
842            /** Parsing after #{ .*/
843            DEFERRED1,
844            /** Parsing after \ .*/
845            ESCAPE
846        }
847    
848        /**
849         * Parses a unified expression.
850         * @param expr the string expression
851         * @return the expression instance
852         * @throws JexlException if an error occur during parsing
853         */
854        private Expression parseExpression(String expr) {
855            final int size = expr.length();
856            ExpressionBuilder builder = new ExpressionBuilder(0);
857            StringBuilder strb = new StringBuilder(size);
858            ParseState state = ParseState.CONST;
859            int inner = 0;
860            boolean nested = false;
861            int inested = -1;
862            for (int i = 0; i < size; ++i) {
863                char c = expr.charAt(i);
864                switch (state) {
865                    default: // in case we ever add new expression type
866                        throw new UnsupportedOperationException("unexpected expression type");
867                    case CONST:
868                        if (c == '$') {
869                            state = ParseState.IMMEDIATE0;
870                        } else if (c == '#') {
871                            inested = i;
872                            state = ParseState.DEFERRED0;
873                        } else if (c == '\\') {
874                            state = ParseState.ESCAPE;
875                        } else {
876                            // do buildup expr
877                            strb.append(c);
878                        }
879                        break;
880                    case IMMEDIATE0: // $
881                        if (c == '{') {
882                            state = ParseState.IMMEDIATE1;
883                            // if chars in buffer, create constant
884                            if (strb.length() > 0) {
885                                Expression cexpr = new ConstantExpression(strb.toString(), null);
886                                builder.add(cexpr);
887                                strb.delete(0, Integer.MAX_VALUE);
888                            }
889                        } else {
890                            // revert to CONST
891                            strb.append('$');
892                            strb.append(c);
893                            state = ParseState.CONST;
894                        }
895                        break;
896                    case DEFERRED0: // #
897                        if (c == '{') {
898                            state = ParseState.DEFERRED1;
899                            // if chars in buffer, create constant
900                            if (strb.length() > 0) {
901                                Expression cexpr = new ConstantExpression(strb.toString(), null);
902                                builder.add(cexpr);
903                                strb.delete(0, Integer.MAX_VALUE);
904                            }
905                        } else {
906                            // revert to CONST
907                            strb.append('#');
908                            strb.append(c);
909                            state = ParseState.CONST;
910                        }
911                        break;
912                    case IMMEDIATE1: // ${...
913                        if (c == '}') {
914                            // materialize the immediate expr
915                            Expression iexpr = new ImmediateExpression(strb.toString(), toNode(strb), null);
916                            builder.add(iexpr);
917                            strb.delete(0, Integer.MAX_VALUE);
918                            state = ParseState.CONST;
919                        } else {
920                            // do buildup expr
921                            strb.append(c);
922                        }
923                        break;
924                    case DEFERRED1: // #{...
925                        // skip inner strings (for '}')
926                        if (c == '"' || c == '\'') {
927                            strb.append(c);
928                            i = StringParser.readString(strb, expr, i + 1, c);
929                            continue;
930                        }
931                        // nested immediate in deferred; need to balance count of '{' & '}'
932                        if (c == '{') {
933                            if (expr.charAt(i - 1) == '$') {
934                                inner += 1;
935                                strb.deleteCharAt(strb.length() - 1);
936                                nested = true;
937                            }
938                            continue;
939                        }
940                        // closing '}'
941                        if (c == '}') {
942                            // balance nested immediate
943                            if (inner > 0) {
944                                inner -= 1;
945                            } else {
946                                // materialize the nested/deferred expr
947                                Expression dexpr = null;
948                                if (nested) {
949                                    dexpr = new NestedExpression(expr.substring(inested, i + 1), toNode(strb), null);
950                                } else {
951                                    dexpr = new DeferredExpression(strb.toString(), toNode(strb), null);
952                                }
953                                builder.add(dexpr);
954                                strb.delete(0, Integer.MAX_VALUE);
955                                nested = false;
956                                state = ParseState.CONST;
957                            }
958                        } else {
959                            // do buildup expr
960                            strb.append(c);
961                        }
962                        break;
963                    case ESCAPE:
964                        if (c == '#') {
965                            strb.append('#');
966                        } else if (c == '$') {
967                            strb.append('$');
968                        } else {
969                            strb.append('\\');
970                            strb.append(c);
971                        }
972                        state = ParseState.CONST;
973                }
974            }
975            // we should be in that state
976            if (state != ParseState.CONST) {
977                throw new Exception("malformed expression: " + expr, null);
978            }
979            // if any chars were buffered, add them as a constant
980            if (strb.length() > 0) {
981                Expression cexpr = new ConstantExpression(strb.toString(), null);
982                builder.add(cexpr);
983            }
984            return builder.build(this, null);
985        }
986    }