commons-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hen...@apache.org
Subject svn commit: r1196677 [1/2] - in /commons/proper/jexl/trunk/src: main/java/org/apache/commons/jexl2/ main/java/org/apache/commons/jexl2/introspection/ main/java/org/apache/commons/jexl2/parser/ main/java/org/apache/commons/jexl2/scripting/ test/java/org...
Date Wed, 02 Nov 2011 16:32:40 GMT
Author: henrib
Date: Wed Nov  2 16:32:39 2011
New Revision: 1196677

URL: http://svn.apache.org/viewvc?rev=1196677&view=rev
Log:
JEXL-121: added templating features

Removed:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/CharSequenceReader.java
Modified:
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Debugger.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/ASTJexlScript.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlParser.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/StringParser.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java
    commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/UnifiedJEXLTest.java
    commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/parser/ParserTest.java

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Debugger.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Debugger.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Debugger.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Debugger.java Wed Nov  2 16:32:39 2011
@@ -402,7 +402,7 @@ final class Debugger implements ParserVi
         return infixChildren(node, " > ", false, data);
     }
 
-    // check identifiers that contain space, quote, double-quotes or backspace
+    /** Checks identifiers that contain space, quote, double-quotes or backspace. */
     private static final Pattern QUOTED_IDENTIFIER = Pattern.compile("['\"\\s\\\\]");
     
     /** {@inheritDoc} */

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/ExpressionImpl.java Wed Nov  2 16:32:39 2011
@@ -61,7 +61,7 @@ public class ExpressionImpl implements E
             return null;
         }
         Interpreter interpreter = jexl.createInterpreter(context);
-        interpreter.setArguments(script.getRegisters(), script.createArguments((Object[]) null));
+        interpreter.setFrame(script.createFrame((Object[]) null));
         return interpreter.interpret(script.jjtGetChild(0));
     }
 
@@ -103,7 +103,7 @@ public class ExpressionImpl implements E
      */
     public Object execute(JexlContext context) {
         Interpreter interpreter = jexl.createInterpreter(context);
-        interpreter.setArguments(script.getRegisters(), script.createArguments((Object[]) null));
+        interpreter.setFrame(script.createFrame((Object[]) null));
         return interpreter.interpret(script);
     }
 
@@ -112,7 +112,7 @@ public class ExpressionImpl implements E
      */
     public Object execute(JexlContext context, Object... args) {
         Interpreter interpreter = jexl.createInterpreter(context);
-        interpreter.setArguments(script.getRegisters(), script.createArguments(args));
+        interpreter.setFrame(script.createFrame(args));
         return interpreter.interpret(script);
     }
 
@@ -149,7 +149,7 @@ public class ExpressionImpl implements E
      */
     public Callable<Object> callable(JexlContext context, Object... args) {
         final Interpreter interpreter = jexl.createInterpreter(context);
-        interpreter.setArguments(script.getRegisters(), script.createArguments(args));
+        interpreter.setFrame(script.createFrame(args));
 
         return new Callable<Object>() {
             /** Use interpreter as marker for not having run. */

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/Interpreter.java Wed Nov  2 16:32:39 2011
@@ -144,7 +144,7 @@ public class Interpreter implements Pars
         this.strict = strictFlag;
         this.silent = silentFlag;
         this.cache = jexl.cache != null;
-        this.context = aContext;
+        this.context = aContext != null? aContext : JexlEngine.EMPTY_CONTEXT;
         this.functors = null;
     }
 
@@ -207,6 +207,14 @@ public class Interpreter implements Pars
             registers = null;
         }
     }
+    
+    /**
+     * Gets the context.
+     * @return the {@link JexlContext} used for evaluation.
+     */
+    protected JexlContext getContext() {
+        return context;
+    }
 
     /**
      * Gets the uberspect.
@@ -218,7 +226,7 @@ public class Interpreter implements Pars
 
     /**
      * Sets this interpreter registers for bean access/assign expressions.
-     * <p>Use setArguments(...) instead.</p>
+     * <p>Use setFrame(...) instead.</p>
      * @param theRegisters the array of registers
      */
     @Deprecated
@@ -235,12 +243,16 @@ public class Interpreter implements Pars
 
     /**
      * Sets this interpreter parameters and arguments.
-     * @param theParms the array of parameters
-     * @param theArgs the array of arguments
+     * @param frame the calling frame
      */
-    protected void setArguments(String[] theParms, Object[] theArgs) {
-        this.parameters = theParms;
-        this.registers = theArgs;
+    protected void setFrame(JexlEngine.Frame frame) {
+        if (frame != null) {
+            this.parameters = frame.getParameters();
+            this.registers = frame.getRegisters();
+        } else {
+            this.parameters = null;
+            this.registers = null;
+        }
     }
 
     /**
@@ -416,7 +428,7 @@ public class Interpreter implements Pars
 
     /** {@inheritDoc} */
     public Object visit(ASTArrayAccess node, Object data) {
-            // first objectNode is the identifier
+        // first objectNode is the identifier
         Object object = node.jjtGetChild(0).jjtAccept(this, data);
         // can have multiple nodes - either an expression, integer literal or reference
         int numChildren = node.jjtGetNumChildren();
@@ -1056,7 +1068,7 @@ public class Interpreter implements Pars
         }
 
         JexlException xjexl = null;
-        try { 
+        try {
             // attempt to reuse last constructor cached in volatile JexlNode.value
             if (cache) {
                 Object cached = node.jjtGetValue();
@@ -1264,11 +1276,11 @@ public class Interpreter implements Pars
         }
         if (result == null) {
             if (isVariable && !isTernaryProtected(node)
-                // variable unknow in context and not (from) a register
-                && !(context.has(variableName.toString())
-                     || (numChildren == 1
-                         && node.jjtGetChild(0) instanceof ASTIdentifier
-                         && ((ASTIdentifier) node.jjtGetChild(0)).getRegister() >= 0))) {
+                    // variable unknow in context and not (from) a register
+                    && !(context.has(variableName.toString())
+                    || (numChildren == 1
+                    && node.jjtGetChild(0) instanceof ASTIdentifier
+                    && ((ASTIdentifier) node.jjtGetChild(0)).getRegister() >= 0))) {
                 JexlException xjexl = propertyName != null
                                       ? new JexlException.Property(node, propertyName)
                                       : new JexlException.Variable(node, variableName.toString());

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlEngine.java Wed Nov  2 16:32:39 2011
@@ -290,7 +290,7 @@ public class JexlEngine {
         if (arithmetic instanceof JexlThreadedArithmetic) {
             JexlThreadedArithmetic.setLenient(Boolean.valueOf(flag));
         } else {
-            strict = flag? Boolean.FALSE : Boolean.TRUE;
+            strict = flag ? Boolean.FALSE : Boolean.TRUE;
         }
     }
 
@@ -300,9 +300,9 @@ public class JexlEngine {
      * @return true if lenient, false if strict
      */
     public boolean isLenient() {
-        return strict == null? arithmetic.isLenient() : !strict.booleanValue();
+        return strict == null ? arithmetic.isLenient() : !strict.booleanValue();
     }
-        
+
     /**
      * Sets whether this engine behaves in strict or lenient mode.
      * Equivalent to setLenient(!flag).
@@ -311,7 +311,7 @@ public class JexlEngine {
     public final void setStrict(boolean flag) {
         setLenient(!flag);
     }
-    
+
     /**
      * Checks whether this engine behaves in strict or lenient mode.
      * Equivalent to !isLenient().
@@ -321,7 +321,6 @@ public class JexlEngine {
         return !isLenient();
     }
 
-
     /**
      * Sets the class loader used to discover classes in 'new' expressions.
      * <p>This method should be called as an optional step of the JexlEngine
@@ -447,7 +446,7 @@ public class JexlEngine {
     public Script createScript(String scriptText) {
         return createScript(scriptText, null, null);
     }
-    
+
     /**
      * Creates a Script from a String containing valid JEXL syntax.
      * This method parses the script which validates the syntax.
@@ -457,9 +456,10 @@ public class JexlEngine {
      * @return A {@link Script} which can be executed using a {@link JexlContext}.
      * @throws JexlException if there is a problem parsing the script.
      */
-    public Script createScript(String scriptText, String...names) {
+    public Script createScript(String scriptText, String... names) {
         return createScript(scriptText, null, names);
     }
+
     /**
      * Creates a Script from a String containing valid JEXL syntax.
      * This method parses the script which validates the syntax.
@@ -477,7 +477,7 @@ public class JexlEngine {
             throw new NullPointerException("scriptText is null");
         }
         // Parse the expression
-        ASTJexlScript tree = parse(scriptText, info, names);
+        ASTJexlScript tree = parse(scriptText, info, new Scope(names));
         return createScript(tree, scriptText);
     }
 
@@ -580,12 +580,12 @@ public class JexlEngine {
         expr = "#0" + (expr.charAt(0) == '[' ? "" : ".") + expr + ";";
         try {
             parser.ALLOW_REGISTERS = true;
-            String[] regStrs = {"#0"};
-            ASTJexlScript script = parse(expr, null, regStrs);
+            Scope frame = new Scope("#0");
+            ASTJexlScript script = parse(expr, null, frame);
             JexlNode node = script.jjtGetChild(0);
             Interpreter interpreter = createInterpreter(context);
-            // set register
-            interpreter.setArguments(script.getParameters(), script.createArguments(bean));
+            // set frame
+            interpreter.setFrame(script.createFrame(bean));
             return node.jjtAccept(interpreter, null);
         } catch (JexlException xjexl) {
             if (silent) {
@@ -635,12 +635,12 @@ public class JexlEngine {
         expr = "#0" + (expr.charAt(0) == '[' ? "" : ".") + expr + "=" + "#1" + ";";
         try {
             parser.ALLOW_REGISTERS = true;
-            String[] regStrs = {"#0", "#1"};
-            ASTJexlScript script = parse(expr, null, regStrs);
+            Scope frame = new Scope("#0", "#1");
+            ASTJexlScript script = parse(expr, null, frame);
             JexlNode node = script.jjtGetChild(0);
             Interpreter interpreter = createInterpreter(context);
             // set the registers
-            interpreter.setArguments(script.getParameters(), script.createArguments(bean, value));
+            interpreter.setFrame(script.createFrame(bean, value));
             node.jjtAccept(interpreter, null);
         } catch (JexlException xjexl) {
             if (silent) {
@@ -755,7 +755,7 @@ public class JexlEngine {
     protected Interpreter createInterpreter(JexlContext context) {
         return createInterpreter(context, isStrict(), isSilent());
     }
-    
+
     /**
      * Creates an interpreter.
      * @param context a JexlContext; if null, the EMPTY_CONTEXT is used instead.
@@ -764,7 +764,7 @@ public class JexlEngine {
      * @return an Interpreter
      */
     protected Interpreter createInterpreter(JexlContext context, boolean strictFlag, boolean silentFlag) {
-        return new Interpreter(this, context == null? EMPTY_CONTEXT : context, strictFlag, silentFlag);
+        return new Interpreter(this, context == null ? EMPTY_CONTEXT : context, strictFlag, silentFlag);
     }
 
     /**
@@ -970,27 +970,201 @@ public class JexlEngine {
     }
 
     /**
-     * Parses an expression.
-     * @param expression the expression to parse
-     * @param info debug information structure
-     * @return the parsed tree
-     * @throws JexlException if any error occured during parsing
-     * @deprecated 
+     * A script scope, stores the declaration of parameters and local variables.
+     */
+    public static final class Scope {
+        /**
+         * The number of parameters.
+         */
+        protected final int parms;
+        /**
+         * The map of named registers aka script parameters.
+         * Each parameter is associated to a register and is materialized as an offset in the registers array used
+         * during evaluation.
+         */
+        protected Map<String, Integer> namedRegisters = null;
+
+        /**
+         * Creates a new scope with a list of parameters.
+         * @param parameters the list of parameters
+         */
+        public Scope(String... parameters) {
+            if (parameters != null) {
+                parms = parameters.length;
+                namedRegisters = new LinkedHashMap<String, Integer>();
+                for (int p = 0; p < parms; ++p) {
+                    namedRegisters.put(parameters[p], p);
+                }
+            } else {
+                parms = 0;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return namedRegisters == null ? 0 : parms ^ namedRegisters.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o instanceof Scope && equals((Scope) o);
+        }
+
+        /**
+         * Whether this frame is equal to another.
+         * @param frame the frame to compare to
+         * @return true if equal, false otherwise
+         */
+        public boolean equals(Scope frame) {
+            return this == frame
+                    || parms == frame.parms
+                    && namedRegisters.equals(frame.namedRegisters);
+        }
+
+        /**
+         * Checks whether an identifier is a local variable or argument, ie stored in a register. 
+         * @param name the register name
+         * @return the register index
+         */
+        public Integer getRegister(String name) {
+            return namedRegisters != null ? namedRegisters.get(name) : null;
+        }
+
+        /**
+         * Declares a local variable.
+         * <p>
+         * This method creates an new entry in the named register map.
+         * </p>
+         * @param name the variable name
+         * @return the register index storing this variable
+         */
+        public Integer declareVariable(String name) {
+            if (namedRegisters == null) {
+                namedRegisters = new LinkedHashMap<String, Integer>();
+            }
+            Integer register = namedRegisters.get(name);
+            if (register == null) {
+                register = Integer.valueOf(namedRegisters.size());
+                namedRegisters.put(name, register);
+            }
+            return register;
+        }
+
+        /**
+         * Creates a frame by copying values up to the number of parameters.
+         * @param values the argument values
+         * @return the arguments array
+         */
+        public Frame createFrame(Object... values) {
+            if (namedRegisters != null) {
+                Object[] arguments = new Object[namedRegisters.size()];
+                if (values != null) {
+                    System.arraycopy(values, 0, arguments, 0, Math.min(parms, values.length));
+                }
+                return new Frame(arguments, namedRegisters.keySet().toArray(new String[0]));
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the (maximum) number of arguments this script expects.
+         * @return the number of parameters
+         */
+        public int getArgCount() {
+            return parms;
+        }
+
+        /**
+         * Gets this script registers, i.e. parameters and local variables.
+         * @return the register names
+         */
+        public String[] getRegisters() {
+            return namedRegisters != null ? namedRegisters.keySet().toArray(new String[0]) : new String[0];
+        }
+
+        /**
+         * Gets this script parameters, i.e. registers assigned before creating local variables.
+         * @return the parameter names
+         */
+        public String[] getParameters() {
+            if (namedRegisters != null && parms > 0) {
+                String[] pa = new String[parms];
+                int p = 0;
+                for (Map.Entry<String, Integer> entry : namedRegisters.entrySet()) {
+                    if (entry.getValue() < parms) {
+                        pa[p++] = entry.getKey();
+                    }
+                }
+                return pa;
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets this script local variable, i.e. registers assigned to local variables.
+         * @return the parameter names
+         */
+        public String[] getLocalVariables() {
+            if (namedRegisters != null && parms > 0) {
+                String[] pa = new String[parms];
+                int p = 0;
+                for (Map.Entry<String, Integer> entry : namedRegisters.entrySet()) {
+                    if (entry.getValue() >= parms) {
+                        pa[p++] = entry.getKey();
+                    }
+                }
+                return pa;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * A call frame, created from a scope, stores the arguments and local variables as "registers".
      */
-    @Deprecated
-    protected ASTJexlScript parse(CharSequence expression, JexlInfo info) {
-        return parse(expression, info, null);
+    public static final class Frame {
+        /** Registers or arguments. */
+        protected Object[] registers = null;
+        /** Parameter and argument names if any. */
+        protected String[] parameters = null;
+        
+        /**
+         * Creates a new frame.
+         * @param r the registers
+         * @param p the parameters
+         */
+        Frame(Object[] r, String[] p) {
+            registers = r;
+            parameters = p;
+        }
+        
+        /**
+         * @return the registers
+         */
+        public Object[] getRegisters() {
+            return registers;
+        }
+                
+        /**
+         * @return the parameters
+         */
+        public String[] getParameters() {
+            return parameters;
+        }
     }
 
     /**
      * Parses an expression.
      * @param expression the expression to parse
      * @param info debug information structure
-     * @param names the parameter names array
+     * @param frame the script frame to use
      * @return the parsed tree
      * @throws JexlException if any error occured during parsing
      */
-    protected ASTJexlScript parse(CharSequence expression, JexlInfo info, String[] names) {
+    protected ASTJexlScript parse(CharSequence expression, JexlInfo info, Scope frame) {
         String expr = cleanExpression(expression);
         ASTJexlScript script = null;
         DebugInfo dbgInfo = null;
@@ -998,7 +1172,10 @@ public class JexlEngine {
             if (cache != null) {
                 script = cache.get(expr);
                 if (script != null) {
-                    return script;
+                    Scope f = script.getScope();
+                    if ((f == null && frame == null) || (f != null && f.equals(frame))) {
+                        return script;
+                    }
                 }
             }
             try {
@@ -1011,20 +1188,12 @@ public class JexlEngine {
                 } else {
                     dbgInfo = info.debugInfo();
                 }
-                Map<String, Integer> params = null;
-                if (names != null) {
-                    params = new LinkedHashMap<String, Integer>();
-                    for (int n = 0; n < names.length; ++n) {
-                        params.put(names[n], Integer.valueOf(n));
-                    }
-                    parser.setNamedRegisters(params);
-                }
+                parser.setFrame(frame);
                 script = parser.parse(reader, dbgInfo);
                 // reaccess in case local variables have been declared
-                params = parser.getNamedRegisters();
-                if (params != null) {
-                    String[] registers = (new ArrayList<String>(params.keySet())).toArray(new String[0]);
-                    script.setParameters(names != null ? names.length : 0, registers);
+                frame = parser.getFrame();
+                if (frame != null) {
+                    script.setScope(frame);
                 }
                 if (cache != null) {
                     cache.put(expr, script);
@@ -1034,7 +1203,7 @@ public class JexlEngine {
             } catch (ParseException xparse) {
                 throw new JexlException.Parsing(dbgInfo, expression, xparse);
             } finally {
-                parser.setNamedRegisters(null);
+                parser.setFrame(null);
             }
         }
         return script;

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/JexlException.java Wed Nov  2 16:32:39 2011
@@ -33,6 +33,10 @@ public class JexlException extends Runti
     protected final transient JexlInfo info;
     /** A marker to use in NPEs stating a null operand error. */
     public static final String NULL_OPERAND = "jexl.null";
+    /** Minimum number of characters around exception location. */
+    private static final int MIN_EXCHARLOC = 5;
+    /** Maximum number of characters around exception location. */
+    private static final int MAX_EXCHARLOC = 10;
     
     /**
      * Creates a new JexlException.
@@ -145,14 +149,14 @@ public class JexlException extends Runti
         @Override
         protected String detailedMessage() {
             int begin = info.debugInfo().getColumn();
-            int end = begin + 5;
-            begin -= 5;
+            int end = begin + MIN_EXCHARLOC;
+            begin -= MIN_EXCHARLOC;
             if (begin < 0) {
-                end += 5;
+                end += MIN_EXCHARLOC;
                 begin = 0;
             }
             int length = getExpression().length();
-            if (length < 10) {
+            if (length < MAX_EXCHARLOC) {
                 return "parsing error in '" + getExpression() + "'";
             } else {
                 return "parsing error near '... "
@@ -202,14 +206,14 @@ public class JexlException extends Runti
         @Override
         protected String detailedMessage() {
             int begin = info.debugInfo().getColumn();
-            int end = begin + 5;
-            begin -= 5;
+            int end = begin + MIN_EXCHARLOC;
+            begin -= MIN_EXCHARLOC;
             if (begin < 0) {
-                end += 5;
+                end += MIN_EXCHARLOC;
                 begin = 0;
             }
             int length = getExpression().length();
-            if (length < 10) {
+            if (length < MAX_EXCHARLOC) {
                 return "parsing error in '" + getExpression() + "'";
             } else {
                 return "parsing error near '... "

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/UnifiedJEXL.java Wed Nov  2 16:32:39 2011
@@ -16,11 +16,20 @@
  */
 package org.apache.commons.jexl2;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
+import org.apache.commons.jexl2.introspection.JexlMethod;
+import org.apache.commons.jexl2.introspection.Uberspect;
+import org.apache.commons.jexl2.parser.ASTJexlScript;
 import org.apache.commons.jexl2.parser.JexlNode;
 import org.apache.commons.jexl2.parser.StringParser;
 
@@ -70,7 +79,7 @@ import org.apache.commons.jexl2.parser.S
  * The most common mistake leading to an invalid expression being the following:
  * <code>"#{${bar}charAt(2)}"</code>
  * </p>
- * <p>Also note that methods that parse evaluate expressions may throw <em>unchecked</em> ecxeptions;
+ * <p>Also note that methods that parse evaluate expressions may throw <em>unchecked</em> exceptions;
  * The {@link UnifiedJEXL.Exception} are thrown when the engine instance is in "non-silent" mode
  * but since these are RuntimeException, user-code <em>should</em> catch them where appropriate.
  * </p>
@@ -83,7 +92,10 @@ public final class UnifiedJEXL {
     private final JexlEngine.SoftCache<String, Expression> cache;
     /** The default cache size. */
     private static final int CACHE_SIZE = 256;
-
+    /** The first character for immediate expressions. */
+    private static final char IMM_CHAR = '$';
+    /** The first character for deferred expressions. */
+    private static final char DEF_CHAR = '#';
     /**
      * Creates a new instance of UnifiedJEXL with a default size cache.
      * @param aJexl the JexlEngine to use.
@@ -266,14 +278,12 @@ public final class UnifiedJEXL {
          * @return the formatted expression string
          */
         @Override
-        public String toString() {
+        public final String toString() {
             StringBuilder strb = new StringBuilder();
-            if (source != this) {
-                strb.append(source.toString());
-                strb.append(" /*= ");
-            }
             asString(strb);
             if (source != this) {
+                strb.append(" /*= ");
+                strb.append(source.toString());
                 strb.append(" */");
             }
             return strb.toString();
@@ -292,6 +302,7 @@ public final class UnifiedJEXL {
         /**
          * Adds this expression's string representation to a StringBuilder.
          * @param strb the builder to fill
+         * @return the builder argument
          */
         public abstract StringBuilder asString(StringBuilder strb);
 
@@ -331,7 +342,20 @@ public final class UnifiedJEXL {
          * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
          */
         public final Expression prepare(JexlContext context) {
-            return UnifiedJEXL.this.prepare(context, this);
+            try {
+                Interpreter interpreter = new Interpreter(jexl, context, !jexl.isLenient(), false);
+                if (context instanceof TemplateContext) {
+                    interpreter.setFrame(((TemplateContext) context).getFrame());
+                }
+                return prepare(interpreter);
+            } catch (JexlException xjexl) {
+                Exception xuel = createException("prepare", this, xjexl);
+                if (jexl.isSilent()) {
+                    jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+                    return null;
+                }
+                throw xuel;
+            }
         }
 
         /**
@@ -345,7 +369,20 @@ public final class UnifiedJEXL {
          * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
          */
         public final Object evaluate(JexlContext context) {
-            return UnifiedJEXL.this.evaluate(context, this);
+            try {
+                Interpreter interpreter = new Interpreter(jexl, context, !jexl.isLenient(), false);
+                if (context instanceof TemplateContext) {
+                    interpreter.setFrame(((TemplateContext) context).getFrame());
+                }
+                return evaluate(interpreter);
+            } catch (JexlException xjexl) {
+                Exception xuel = createException("prepare", this, xjexl);
+                if (jexl.isSilent()) {
+                    jexl.logger.warn(xuel.getMessage(), xuel.getCause());
+                    return null;
+                }
+                throw xuel;
+            }
         }
 
         /**
@@ -411,26 +448,9 @@ public final class UnifiedJEXL {
 
         /** {@inheritDoc} */
         @Override
-        public String toString() {
-            return value.toString();
-        }
-
-        /** {@inheritDoc} */
-        @Override
         public StringBuilder asString(StringBuilder strb) {
-            String str = value.toString();
-            if (value instanceof String || value instanceof CharSequence) {
-                strb.append('"');
-                for (int i = 0, size = str.length(); i < size; ++i) {
-                    char c = str.charAt(i);
-                    if (c == '"' || c == '\\') {
-                        strb.append('\\');
-                    }
-                    strb.append(c);
-                }
-                strb.append('"');
-            } else {
-                strb.append(str);
+            if (value != null) {
+                strb.append(value.toString());
             }
             return strb;
         }
@@ -463,14 +483,8 @@ public final class UnifiedJEXL {
 
         /** {@inheritDoc} */
         @Override
-        public String toString() {
-            return asString();
-        }
-
-        /** {@inheritDoc} */
-        @Override
         public StringBuilder asString(StringBuilder strb) {
-            strb.append(isImmediate() ? '$' : '#');
+            strb.append(isImmediate() ? IMM_CHAR : DEF_CHAR);
             strb.append("{");
             strb.append(expr);
             strb.append("}");
@@ -490,6 +504,12 @@ public final class UnifiedJEXL {
             getVariables(refs);
             return refs;
         }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void getVariables(Set<List<String>> refs) {
+            jexl.getVariables(node, refs, null);
+        }
     }
 
     /** An immediate expression: ${jexl}. */
@@ -512,17 +532,10 @@ public final class UnifiedJEXL {
 
         /** {@inheritDoc} */
         @Override
-        protected void getVariables(Set<List<String>> refs) {
-            jexl.getVariables(node, refs, null);
-        }
-
-        /** {@inheritDoc} */
-        /** {@inheritDoc} */
-        @Override
         protected Expression prepare(Interpreter interpreter) {
             // evaluate immediate as constant
             Object value = evaluate(interpreter);
-            return value == null ? null : new ConstantExpression(value, this);
+            return value != null ? new ConstantExpression(value, source) : null;
         }
     }
 
@@ -541,7 +554,7 @@ public final class UnifiedJEXL {
         /** {@inheritDoc} */
         @Override
         public boolean isImmediate() {
-            return true;
+            return false;
         }
 
         /** {@inheritDoc} */
@@ -555,14 +568,20 @@ public final class UnifiedJEXL {
         protected Expression prepare(Interpreter interpreter) {
             return new ImmediateExpression(expr, node, source);
         }
+
+        /** {@inheritDoc} */
+        @Override
+        protected void getVariables(Set<List<String>> refs) {
+            // noop
+        }
     }
 
     /**
-     * A deferred expression that nests an immediate expression.
+     * An immediate expression nested into a deferred expression.
      * #{...${jexl}...}
      * Note that the deferred syntax is JEXL's, not UnifiedJEXL.
      */
-    private class NestedExpression extends DeferredExpression {
+    private class NestedExpression extends JexlBasedExpression {
         /**
          * Creates a nested expression.
          * @param expr the expression as a string
@@ -575,24 +594,30 @@ public final class UnifiedJEXL {
                 throw new IllegalArgumentException("Nested expression can not have a source");
             }
         }
+        
+        @Override
+        public StringBuilder asString(StringBuilder strb) {
+            strb.append(expr);
+            return strb;
+        }
 
         /** {@inheritDoc} */
         @Override
-        ExpressionType getType() {
-            return ExpressionType.NESTED;
+        public boolean isImmediate() {
+            return false;
         }
 
         /** {@inheritDoc} */
         @Override
-        public String toString() {
-            return expr.toString();
+        ExpressionType getType() {
+            return ExpressionType.NESTED;
         }
 
         /** {@inheritDoc} */
         @Override
         protected Expression prepare(Interpreter interpreter) {
             String value = interpreter.interpret(node).toString();
-            JexlNode dnode = toNode(value, jexl.isDebug() ? node.debugInfo() : null);
+            JexlNode dnode = jexl.parse(value, jexl.isDebug() ? node.debugInfo() : null, null);
             return new ImmediateExpression(value, dnode, this);
         }
 
@@ -608,7 +633,7 @@ public final class UnifiedJEXL {
         /** Bit encoded (deferred count > 0) bit 1, (immediate count > 0) bit 0. */
         private final int meta;
         /** The list of sub-expression resulting from parsing. */
-        private final Expression[] exprs;
+        protected final Expression[] exprs;
 
         /**
          * Creates a composite expression.
@@ -638,16 +663,6 @@ public final class UnifiedJEXL {
 
         /** {@inheritDoc} */
         @Override
-        public String toString() {
-            StringBuilder strb = new StringBuilder();
-            for (Expression e : exprs) {
-                strb.append(e.toString());
-            }
-            return strb.toString();
-        }
-
-        /** {@inheritDoc} */
-        @Override
         public StringBuilder asString(StringBuilder strb) {
             for (Expression e : exprs) {
                 e.asString(strb);
@@ -700,9 +715,7 @@ public final class UnifiedJEXL {
             StringBuilder strb = new StringBuilder();
             for (int e = 0; e < size; ++e) {
                 value = exprs[e].evaluate(interpreter);
-                if (value instanceof Expression) {
-                    ((Expression) value).asString(strb);
-                } else if (value != null) {
+                if (value != null) {
                     strb.append(value.toString());
                 }
             }
@@ -725,12 +738,12 @@ public final class UnifiedJEXL {
         Expression stmt = null;
         try {
             if (cache == null) {
-                stmt = parseExpression(expression);
+                stmt = parseExpression(expression, null);
             } else {
                 synchronized (cache) {
                     stmt = cache.get(expression);
                     if (stmt == null) {
-                        stmt = parseExpression(expression);
+                        stmt = parseExpression(expression, null);
                         cache.put(expression, stmt);
                     }
                 }
@@ -752,74 +765,6 @@ public final class UnifiedJEXL {
     }
 
     /**
-     * Prepares an expression (nested & composites), handles exception reporting.
-     *
-     * @param context the JEXL context to use
-     * @param expr the expression to prepare
-     * @return a prepared expression
-     * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
-     */
-    Expression prepare(JexlContext context, Expression expr) {
-        try {
-            if (context == null) {
-                context = JexlEngine.EMPTY_CONTEXT;
-            }
-            Interpreter interpreter = new Interpreter(jexl, context, !jexl.isLenient(), false);
-            return expr.prepare(interpreter);
-        } catch (JexlException xjexl) {
-            Exception xuel = createException("prepare", expr, xjexl);
-            if (jexl.isSilent()) {
-                jexl.logger.warn(xuel.getMessage(), xuel.getCause());
-                return null;
-            }
-            throw xuel;
-        }
-    }
-
-    /**
-     * Evaluates an expression (nested & composites), handles exception reporting.
-     *
-     * @param context the JEXL context to use
-     * @param expr the expression to prepare
-     * @return the result of the evaluation
-     * @throws UnifiedJEXL.Exception if an error occurs and the {@link JexlEngine} is not silent
-     */
-    Object evaluate(JexlContext context, Expression expr) {
-        try {
-            Interpreter interpreter = jexl.createInterpreter(context, !jexl.isLenient(), false);
-            return expr.evaluate(interpreter);
-        } catch (JexlException xjexl) {
-            Exception xuel = createException("evaluate", expr, xjexl);
-            if (jexl.isSilent()) {
-                jexl.logger.warn(xuel.getMessage(), xuel.getCause());
-                return null;
-            }
-            throw xuel;
-        }
-    }
-
-    /**
-     * Use the JEXL parser to create the AST for an expression.
-     * @param expression the expression to parse
-     * @return the AST
-     * @throws JexlException if an error occur during parsing
-     */
-    private JexlNode toNode(CharSequence expression) {
-        return jexl.parse(expression, null, null);
-    }
-
-    /**
-     * Use the JEXL parser to create the AST for an expression.
-     * @param expression the expression to parse
-     * @param info debug information
-     * @return the AST
-     * @throws JexlException if an error occur during parsing
-     */
-    private JexlNode toNode(CharSequence expression, JexlInfo info) {
-        return jexl.parse(expression, info, null);
-    }
-
-    /**
      * Creates a UnifiedJEXL.Exception from a JexlException.
      * @param action parse, prepare, evaluate
      * @param expr the expression
@@ -829,9 +774,11 @@ public final class UnifiedJEXL {
     private Exception createException(String action, Expression expr, java.lang.Exception xany) {
         StringBuilder strb = new StringBuilder("failed to ");
         strb.append(action);
-        strb.append(" '");
-        strb.append(expr.toString());
-        strb.append("'");
+        if (expr != null) {
+            strb.append(" '");
+            strb.append(expr.toString());
+            strb.append("'");
+        }
         Throwable cause = xany.getCause();
         if (cause != null) {
             String causeMsg = cause.getMessage();
@@ -862,10 +809,11 @@ public final class UnifiedJEXL {
     /**
      * Parses a unified expression.
      * @param expr the string expression
+     * @param scope the expression scope
      * @return the expression instance
      * @throws JexlException if an error occur during parsing
      */
-    private Expression parseExpression(String expr) {
+    private Expression parseExpression(String expr, JexlEngine.Scope scope) {
         final int size = expr.length();
         ExpressionBuilder builder = new ExpressionBuilder(0);
         StringBuilder strb = new StringBuilder(size);
@@ -879,9 +827,9 @@ public final class UnifiedJEXL {
                 default: // in case we ever add new expression type
                     throw new UnsupportedOperationException("unexpected expression type");
                 case CONST:
-                    if (c == '$') {
+                    if (c == IMM_CHAR) {
                         state = ParseState.IMMEDIATE0;
-                    } else if (c == '#') {
+                    } else if (c == DEF_CHAR) {
                         inested = i;
                         state = ParseState.DEFERRED0;
                     } else if (c == '\\') {
@@ -902,7 +850,7 @@ public final class UnifiedJEXL {
                         }
                     } else {
                         // revert to CONST
-                        strb.append('$');
+                        strb.append(IMM_CHAR);
                         strb.append(c);
                         state = ParseState.CONST;
                     }
@@ -918,7 +866,7 @@ public final class UnifiedJEXL {
                         }
                     } else {
                         // revert to CONST
-                        strb.append('#');
+                        strb.append(DEF_CHAR);
                         strb.append(c);
                         state = ParseState.CONST;
                     }
@@ -926,7 +874,10 @@ public final class UnifiedJEXL {
                 case IMMEDIATE1: // ${...
                     if (c == '}') {
                         // materialize the immediate expr
-                        Expression iexpr = new ImmediateExpression(strb.toString(), toNode(strb), null);
+                        Expression iexpr = new ImmediateExpression(
+                                strb.toString(),
+                                jexl.parse(strb, null, scope),
+                                null);
                         builder.add(iexpr);
                         strb.delete(0, Integer.MAX_VALUE);
                         state = ParseState.CONST;
@@ -944,7 +895,7 @@ public final class UnifiedJEXL {
                     }
                     // nested immediate in deferred; need to balance count of '{' & '}'
                     if (c == '{') {
-                        if (expr.charAt(i - 1) == '$') {
+                        if (expr.charAt(i - 1) == IMM_CHAR) {
                             inner += 1;
                             strb.deleteCharAt(strb.length() - 1);
                             nested = true;
@@ -960,9 +911,15 @@ public final class UnifiedJEXL {
                             // materialize the nested/deferred expr
                             Expression dexpr = null;
                             if (nested) {
-                                dexpr = new NestedExpression(expr.substring(inested, i + 1), toNode(strb), null);
+                                dexpr = new NestedExpression(
+                                        expr.substring(inested, i + 1),
+                                        jexl.parse(strb, null, scope),
+                                        null);
                             } else {
-                                dexpr = new DeferredExpression(strb.toString(), toNode(strb), null);
+                                dexpr = new DeferredExpression(
+                                        strb.toString(),
+                                        jexl.parse(strb, null, scope),
+                                        null);
                             }
                             builder.add(dexpr);
                             strb.delete(0, Integer.MAX_VALUE);
@@ -975,10 +932,10 @@ public final class UnifiedJEXL {
                     }
                     break;
                 case ESCAPE:
-                    if (c == '#') {
-                        strb.append('#');
-                    } else if (c == '$') {
-                        strb.append('$');
+                    if (c == DEF_CHAR) {
+                        strb.append(DEF_CHAR);
+                    } else if (c == IMM_CHAR) {
+                        strb.append(IMM_CHAR);
                     } else {
                         strb.append('\\');
                         strb.append(c);
@@ -997,4 +954,485 @@ public final class UnifiedJEXL {
         }
         return builder.build(this, null);
     }
+
+    /**
+     * The enum capturing the difference between verbatim and code source fragments.
+     */
+    private static enum BlockType {
+        /** Block is to be output "as is". */
+        VERBATIM,
+        /** Block is a directive, ie a fragment of code. */
+        DIRECTIVE;
+    }
+
+    /**
+     * Abstract the source fragments, verbatim or immediate typed text blocks.
+     */
+    private static final class TemplateBlock {
+        /** The type of block, verbatim or directive. */
+        private final BlockType type;
+        /** The actual contexnt. */
+        private final String body;
+
+        /**
+         * Creates a new block. 
+         * @param theType the type
+         * @param theBlock the content
+         */
+        TemplateBlock(BlockType theType, String theBlock) {
+            type = theType;
+            body = theBlock;
+        }
+
+        @Override
+        public String toString() {
+            return body;
+        }
+    }
+
+    /**
+     * A Template is a script that evaluates by writing its content through a Writer.
+     * This is a simplified replacement for Velocity that uses JEXL (instead of OGNL/VTL) as the scripting
+     * language.
+     * <p>
+     * The source text is parsed considering each line beginning with '$$' (as default pattern) as JEXL script code
+     * and all others as Unified JEXL expressions; those expressions will be invoked from the script during
+     * evaluation and their output gathered through a writer. 
+     * It is thus possible to use looping or conditional construct "around" expressions generating output.
+     * </p>
+     * For instance:
+     * <p><blockquote><pre>
+     * $$ for(var x : [1, 3, 5, 42, 169]) {
+     * $$   if (x == 42) {
+     * Life, the universe, and everything
+     * $$   } else if (x > 42) {
+     * The value $(x} is over fourty-two
+     * $$   } else {
+     * The value ${x} is under fourty-two
+     * $$   }
+     * $$ }
+     * </pre></blockquote>
+     * Will evaluate as:
+     * <p><blockquote><pre>
+     * The value 1 is under fourty-two
+     * The value 3 is under fourty-two
+     * The value 5 is under fourty-two
+     * Life, the universe, and everything
+     * The value 169 is over fourty-two
+     * </pre></blockquote>
+     * <p>
+     * A template is expanded as one JEXL script and a list of UnifiedJEXL expressions; each UnifiedJEXL expression
+     * being replace in the script by a call to jexl:print(expr) (the expr is in fact the expr number in the template).
+     * This integration uses a specialized JexlContext (TemplateContext) that serves as a namespace (for jexl:)
+     * and stores the expression array and the writer (java.io.Writer) that the 'jexl:print(...)'
+     * delegates the output generation to.
+     * </p>
+     */
+    public final class Template {
+        /** The prefix marker. */
+        private final String prefix;
+        /** The array of source blocks. */
+        private final TemplateBlock[] source;
+        /** The resulting script. */
+        private final ASTJexlScript script;
+        /** The UnifiedJEXL expressions called by the script. */
+        private final Expression[] exprs;
+
+        /**
+         * Creates a new template from an input.
+         * @param directive the prefix for lines of code; can not be "$", "${", "#" or "#{"
+         * since this would preclude being able to differentiate directives and UnifiedJEXL expressions
+         * @param reader the input reader
+         * @param parms the parameter names
+         * @throws NullPointerException if either the directive prefix or input is null
+         * @throws IllegalArgumentException if the directive prefix is invalid
+         */
+        public Template(String directive, Reader reader, String... parms) {
+            if (directive == null) {
+                throw new NullPointerException("null prefix");
+            }
+            if ("$".equals(directive)
+                    || "${".equals(directive)
+                    || "#".equals(directive)
+                    || "#{".equals(directive)) {
+                throw new IllegalArgumentException(directive + ": is not a valid directive pattern");
+            }
+            if (reader == null) {
+                throw new NullPointerException("null input");
+            }
+            JexlEngine.Scope scope = new JexlEngine.Scope(parms);
+            prefix = directive;
+            List<TemplateBlock> blocks = readTemplate(prefix, reader);
+            List<Expression> uexprs = new ArrayList<Expression>();
+            StringBuilder strb = new StringBuilder();
+            int nuexpr = 0;
+            int codeStart = -1;
+            for (int b = 0; b < blocks.size(); ++b) {
+                TemplateBlock block = blocks.get(b);
+                if (block.type == BlockType.VERBATIM) {
+                    strb.append("jexl:print(");
+                    strb.append(nuexpr++);
+                    strb.append(");");
+                } else {
+                    // keep track of first block of code, the frame creator
+                    if (codeStart < 0) {
+                        codeStart = b;
+                    }
+                    strb.append(block.body);
+                }
+            }
+            // parse the script
+            script = getEngine().parse(strb.toString(), null, scope);
+            scope = script.getScope();
+            // parse the exprs using the code frame for those appearing after the first block of code
+            for (int b = 0; b < blocks.size(); ++b) {
+                TemplateBlock block = blocks.get(b);
+                if (block.type == BlockType.VERBATIM) {
+                    uexprs.add(UnifiedJEXL.this.parseExpression(block.body, b > codeStart ? scope : null));
+                }
+            }
+            source = blocks.toArray(new TemplateBlock[blocks.size()]);
+            exprs = uexprs.toArray(new Expression[uexprs.size()]);
+        }
+
+        /**
+         * Private ctor used to expand deferred expressions during prepare.
+         * @param thePrefix the directive prefix
+         * @param theSource the source
+         * @param theScript the script
+         * @param theExprs the expressions
+         */
+        private Template(String thePrefix, TemplateBlock[] theSource, ASTJexlScript theScript, Expression[] theExprs) {
+            prefix = thePrefix;
+            source = theSource;
+            script = theScript;
+            exprs = theExprs;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder strb = new StringBuilder();
+            for (TemplateBlock block : source) {
+                if (block.type == BlockType.DIRECTIVE) {
+                    strb.append(prefix);
+                }
+                strb.append(block.toString());
+                strb.append('\n');
+            }
+            return strb.toString();
+        }
+        
+        /**
+         * Recreate the template source from its inner components.
+         * @return the template source rewritten
+         */
+        public String asString() {
+            StringBuilder strb = new StringBuilder();
+            int e = 0;
+            for (int b = 0; b < source.length; ++b) {
+                TemplateBlock block = source[b];
+                if (block.type == BlockType.DIRECTIVE) {
+                    strb.append(prefix);
+                } else {
+                    exprs[e++].asString(strb);
+                }
+            }
+            return strb.toString();
+        }
+
+        /**
+         * Prepares this template by expanding any contained deferred expression.
+         * @param context the context to prepare against
+         * @return the prepared version of the template
+         */
+        public Template prepare(JexlContext context) {
+            JexlEngine.Frame frame = script.createFrame((Object[])null);
+            TemplateContext tcontext = new TemplateContext(context, frame, exprs, null);
+            Expression[] immediates = new Expression[exprs.length];
+            for (int e = 0; e < exprs.length; ++e) {
+                immediates[e] = exprs[e].prepare(tcontext);
+            }
+            return new Template(prefix, source, script, immediates);
+        }
+
+        /**
+         * Evaluates this template.
+         * @param context the context to use during evaluation
+         * @param writer the writer to use for output
+         */
+        public void evaluate(JexlContext context, Writer writer) {
+            evaluate(context, writer, (Object[]) null);
+        }
+
+        /**
+         * Evaluates this template.
+         * @param context the context to use during evaluation
+         * @param writer the writer to use for output
+         * @param args the arguments
+         */
+        public void evaluate(JexlContext context, Writer writer, Object... args) {
+            JexlEngine.Frame frame = script.createFrame(args);
+            TemplateContext tcontext = new TemplateContext(context, frame, exprs, writer);
+            Interpreter interpreter = jexl.createInterpreter(tcontext, !jexl.isLenient(), false);
+            interpreter.setFrame(frame);
+            interpreter.interpret(script);
+        }
+    }
+
+    /**
+     * The type of context to use during evaluation of templates.
+     * <p>public for introspection purpose.</p>
+     */
+    public final class TemplateContext implements JexlContext, NamespaceResolver {
+        /** The wrapped context. */
+        private final JexlContext wrap;
+        /** The array of UnifiedJEXL expressions. */
+        private final Expression[] exprs;
+        /** The writer used to output. */
+        private final PrintWriter writer;
+        /** The call frame. */
+        private final JexlEngine.Frame frame;
+
+        /**
+         * Creates a template context instance.
+         * @param jcontext the base context
+         * @param jframe the calling frame
+         * @param expressions the list of expression from the template to evaluate
+         * @param out the output writer
+         */
+        protected TemplateContext(JexlContext jcontext, JexlEngine.Frame jframe, Expression[] expressions, Writer out) {
+            wrap = jcontext;
+            frame = jframe;
+            exprs = expressions;
+            if (out == null) {
+                writer = null;
+            } else if (out instanceof PrintWriter) {
+                writer = (PrintWriter) out;
+            } else {
+                writer = new PrintWriter(out);
+            }
+        }
+
+        /**
+         * Gets this context calling frame.
+         * @return the engine frame
+         */
+        public JexlEngine.Frame getFrame() {
+            return frame;
+        }
+
+        /** {@inheritDoc} */
+        public Object get(String name) {
+            return wrap.get(name);
+        }
+
+        /** {@inheritDoc} */
+        public void set(String name, Object value) {
+            wrap.set(name, value);
+        }
+
+        /** {@inheritDoc} */
+        public boolean has(String name) {
+            return wrap.has(name);
+        }
+
+        /** {@inheritDoc} */
+        public Object resolveNamespace(String ns) {
+            if ("jexl".equals(ns)) {
+                return this;
+            } else if (wrap instanceof NamespaceResolver) {
+                return ((NamespaceResolver) wrap).resolveNamespace(ns);
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Includes a call to another template.
+         * <p>Evaluates a template using this template initial context and writer.</p>
+         * @param template the template to evaluate
+         * @param args the arguments
+         */
+        public void include(Template template, Object... args) {
+            template.evaluate(wrap, writer, args);
+        }
+
+        /**
+         * Prints an expression result.
+         * @param e the expression number
+         */
+        public void print(int e) {
+            Expression expr = exprs[e];
+            if (expr.isDeferred()) {
+                expr = expr.prepare(wrap);
+            }
+            if (expr instanceof CompositeExpression) {
+                printComposite((CompositeExpression) expr);
+            } else {
+                print(expr.evaluate(this));
+            }
+        }
+
+        /**
+         * Prints to output.
+         * <p>This will dynamically try to find the best suitable method in the writer through uberspection.
+         * Subclassing Writer should be the preferred way to specialize output.
+         * </p>
+         * @param arg the argument to print out
+         */
+        protected void print(Object arg) {
+            if (arg instanceof CharSequence) {
+                writer.print(arg.toString());
+            } else if (arg != null) {
+                Object[] value = {arg};
+                Uberspect uber = getEngine().getUberspect();
+                JexlMethod method = uber.getMethod(writer, "print", value, null);
+                if (method != null) {
+                    try {
+                        method.invoke(writer, value);
+                    } catch (java.lang.Exception xany) {
+                        throw createException("invoke print", null, xany);
+                    }
+                } else {
+                    writer.print(arg.toString());
+                }
+            }
+        }
+
+        /**
+         * Prints a composite expression.
+         * @param composite the composite expression
+         */
+        protected void printComposite(CompositeExpression composite) {
+            Expression[] cexprs = composite.exprs;
+            final int size = cexprs.length;
+            Object value = null;
+            for (int e = 0; e < size; ++e) {
+                value = cexprs[e].evaluate(this);
+                print(value);
+            }
+        }
+    }
+
+    /**
+     * Whether a sequence starts with a given set of characters (following spaces).
+     * <p>Space characters at beginning of line before the pattern are discarded.</p>
+     * @param sequence the sequence
+     * @param pattern the pattern to match at start of sequence
+     * @return the first position after end of pattern if it matches, -1 otherwise
+     */
+    protected int startsWith(CharSequence sequence, CharSequence pattern) {
+        int s = 0;
+        while (Character.isSpaceChar(sequence.charAt(s))) {
+            s += 1;
+        }
+        sequence = sequence.subSequence(s, sequence.length());
+        if (pattern.length() <= sequence.length()
+                && sequence.subSequence(0, pattern.length()).equals(pattern)) {
+            return s + pattern.length();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Reads lines of a template grouping them by typed blocks.
+     * @param prefix the directive prefix
+     * @param source the source reader
+     * @return the list of blocks
+     */
+    protected List<TemplateBlock> readTemplate(final String prefix, Reader source) {
+        try {
+            int prefixLen = prefix.length();
+            List<TemplateBlock> blocks = new ArrayList<TemplateBlock>();
+            BufferedReader reader;
+            if (source instanceof BufferedReader) {
+                reader = (BufferedReader) source;
+            } else {
+                reader = new BufferedReader(source);
+            }
+            StringBuilder strb = new StringBuilder();
+            BlockType type = null;
+            while (true) {
+                CharSequence line = reader.readLine();
+                if (line == null) {
+                    // at end
+                    TemplateBlock block = new TemplateBlock(type, strb.toString());
+                    blocks.add(block);
+                    break;
+                } else if (type == null) {
+                    // determine starting type if not known yet
+                    prefixLen = startsWith(line, prefix);
+                    if (prefixLen >= 0) {
+                        type = BlockType.DIRECTIVE;
+                        strb.append(line.subSequence(prefixLen, line.length()));
+                    } else {
+                        type = BlockType.VERBATIM;
+                        strb.append(line.subSequence(0, line.length()));
+                        strb.append('\n');
+                    }
+                } else if (type == BlockType.DIRECTIVE) {
+                    // switch to verbatim if necessary
+                    prefixLen = startsWith(line, prefix);
+                    if (prefixLen < 0) {
+                        TemplateBlock code = new TemplateBlock(BlockType.DIRECTIVE, strb.toString());
+                        strb.delete(0, Integer.MAX_VALUE);
+                        blocks.add(code);
+                        type = BlockType.VERBATIM;
+                        strb.append(line.subSequence(0, line.length()));
+                    } else {
+                        strb.append(line.subSequence(prefixLen, line.length()));
+                    }
+                } else if (type == BlockType.VERBATIM) {
+                    // switch to code if necessary(
+                    prefixLen = startsWith(line, prefix);
+                    if (prefixLen >= 0) {
+                        strb.append('\n');
+                        TemplateBlock verbatim = new TemplateBlock(BlockType.VERBATIM, strb.toString());
+                        strb.delete(0, Integer.MAX_VALUE);
+                        blocks.add(verbatim);
+                        type = BlockType.DIRECTIVE;
+                        strb.append(line.subSequence(prefixLen, line.length()));
+                    } else {
+                        strb.append(line.subSequence(0, line.length()));
+                    }
+                }
+            }
+            return blocks;
+        } catch (IOException xio) {
+            return null;
+        }
+    }
+
+    /**
+     * Creates a new template.
+     * @param prefix the directive prefix
+     * @param source the source
+     * @param parms the parameter names
+     * @return the template
+     */
+    public Template createTemplate(String prefix, Reader source, String... parms) {
+        return new Template(prefix, source, parms);
+    }
+
+    
+    /**
+     * Creates a new template.
+     * @param source the source
+     * @param parms the parameter names
+     * @return the template
+     */
+    public Template createTemplate(String source, String... parms) {
+        return new Template("$$", new StringReader(source), parms);
+    }
+
+    /**
+     * Creates a new template.
+     * @param source the source
+     * @return the template
+     */
+    public Template createTemplate(String source) {
+        return new Template("$$", new StringReader(source), (String[]) null);
+    }
+
 }
\ No newline at end of file

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/SandboxUberspectImpl.java Wed Nov  2 16:32:39 2011
@@ -53,7 +53,7 @@ public class SandboxUberspectImpl extend
      */
     @Override
     public JexlMethod getConstructor(Object ctorHandle, Object[] args, JexlInfo info) {
-        String className = null;
+        final String className;
         if (ctorHandle instanceof Class<?>) {
             Class<?> clazz = (Class<?>) ctorHandle;
             className = clazz.getName();

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/introspection/UberspectImpl.java Wed Nov  2 16:32:39 2011
@@ -257,7 +257,7 @@ public class UberspectImpl extends Intro
          * @param object the instance owning the container (not null)
          * @param key the property key (not null)
          * @return the property value
-         * @throws IntrospectionException if a property getter can not be found
+         * @throws Exception if invocation failed; IntrospectionException if a property getter could not be found
          */
         private Object invokeGet(Object object, Object key) throws Exception {
             if (getters != null) {
@@ -282,7 +282,7 @@ public class UberspectImpl extends Intro
          * @param key the property key (not null)
          * @param value the property value (not null)
          * @return the result of the method invocation (frequently null)
-         * @throws IntrospectionException if a property getter can not be found
+         * @throws Exception if invocation failed; IntrospectionException if a property setter could not be found
          */
         private Object invokeSet(Object object, Object key, Object value) throws Exception {
             if (setters != null) {

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/ASTJexlScript.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/ASTJexlScript.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/ASTJexlScript.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/ASTJexlScript.java Wed Nov  2 16:32:39 2011
@@ -16,14 +16,14 @@
  */
 package org.apache.commons.jexl2.parser;
 
+import org.apache.commons.jexl2.JexlEngine;
+
 /**
  * Enhanced script to allow parameters declaration.
  */
 public class ASTJexlScript extends JexlNode {
-    /** The number of parameters defined. */
-    private int parms = 0;
-    /** Each parameter will use a register but so do local script variables. */
-    private String[] registers = null;
+    /** The script scope. */
+    private JexlEngine.Scope scope = null;
 
     public ASTJexlScript(int id) {
         super(id);
@@ -40,32 +40,26 @@ public class ASTJexlScript extends JexlN
 
     /**
      * Sets the parameters and registers
-     * @param parms the number of parameters
-     * @param registers the array of register names
+     * @param theScope the scope
+     */
+    public void setScope(JexlEngine.Scope theScope) {
+        this.scope = theScope;
+    }
+    
+    /**
+     * Gets this script scope.
      */
-    public void setParameters(int parms, String[] registers) {
-        if (parms > registers.length) {
-            throw new IllegalArgumentException();
-        }
-        this.parms = parms;
-        this.registers = registers;
+    public JexlEngine.Scope getScope() {
+        return scope;
     }
     
     /**
-     * Creates an array of arguments by copying values up to the number of parameters 
+     * Creates an array of arguments by copying values up to the number of parameters.
      * @param values the argument values
      * @return the arguments array
      */
-    public Object[] createArguments(Object... values) {
-        if (registers != null) {
-            Object[] frame = new Object[registers.length];
-            if (values != null) {
-                System.arraycopy(values, 0, frame, 0, Math.min(parms, values.length));
-            }
-            return frame;
-        } else {
-            return null;
-        }
+    public JexlEngine.Frame createFrame(Object... values) {
+        return scope != null? scope.createFrame(values) : null;
     }
     
     /**
@@ -73,7 +67,7 @@ public class ASTJexlScript extends JexlN
      * @return the number of parameters
      */
     public int getArgCount() {
-        return parms;
+        return scope != null? scope.getArgCount() : 0;
     }
     
     /**
@@ -81,7 +75,7 @@ public class ASTJexlScript extends JexlN
      * @return the register names
      */
     public String[] getRegisters() {
-        return registers;
+        return scope != null? scope.getRegisters() : null;
     }
 
     /**
@@ -89,13 +83,7 @@ public class ASTJexlScript extends JexlN
      * @return the parameter names
      */
     public String[] getParameters() {
-        if (registers != null && parms > 0) {
-            String[] pa = new String[parms];
-            System.arraycopy(registers, 0, pa, 0, parms);
-            return pa;
-        } else {
-            return null;
-        }
+        return scope != null? scope.getParameters() : null;
     }
 
     /**
@@ -103,12 +91,6 @@ public class ASTJexlScript extends JexlN
      * @return the parameter names
      */
     public String[] getLocalVariables() {
-        if (registers != null) {
-            String[] pa = new String[registers.length - parms];
-            System.arraycopy(registers, parms, pa, 0, registers.length - parms);
-            return pa;
-        } else {
-            return null;
-        }
+        return scope != null? scope.getLocalVariables() : null;
     }
 }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlNode.java Wed Nov  2 16:32:39 2011
@@ -31,7 +31,6 @@ public abstract class JexlNode extends S
     public interface Literal<T> {
         T getLiteral();
     }
-
     /** token value. */
     public String image;
 
@@ -54,26 +53,26 @@ public abstract class JexlNode extends S
         }
         return null;
     }
-    
+
     /** {@inheritDoc} */
     public String debugString() {
         DebugInfo info = debugInfo();
-        return info != null? info.debugString() : "";
+        return info != null ? info.debugString() : "";
     }
-    
 
     /**
      * Whether this node is a constant node
      * Its value can not change after the first evaluation and can be cached indefinitely.
      * @return true if constant, false otherwise
      */
-    public boolean isConstant() {
+    public final boolean isConstant() {
         return isConstant(this instanceof JexlNode.Literal<?>);
     }
-    public boolean isConstant(boolean literal) {
+
+    private boolean isConstant(boolean literal) {
         if (literal) {
             if (children != null) {
-                for(JexlNode child : children) {
+                for (JexlNode child : children) {
                     if (!child.isConstant()) {
                         return false;
                     }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlParser.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlParser.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlParser.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/JexlParser.java Wed Nov  2 16:32:39 2011
@@ -16,13 +16,12 @@
  */
 package org.apache.commons.jexl2.parser;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
 import org.apache.commons.jexl2.DebugInfo;
+import org.apache.commons.jexl2.JexlEngine;
 import org.apache.commons.jexl2.JexlException;
 
 /**
- *
+ * The base class for parsing, manages the parameter/local variable frame.
  * @author henri
  */
 public class JexlParser extends StringParser {
@@ -31,30 +30,31 @@ public class JexlParser extends StringPa
      * Each parameter is associated to a register and is materialized as an offset in the registers array used
      * during evaluation.
      */
-    protected Map<String, Integer> namedRegisters = null;
+    protected JexlEngine.Scope frame;
 
     /**
-     * Sets the map of named registers in this parser.
+     * Sets the frame to use bythis parser.
      * <p>
      * This is used to allow parameters to be declared before parsing.
      * </p>
      * @param registers the register map
      */
-    public void setNamedRegisters(Map<String, Integer> registers) {
-        namedRegisters = registers;
+    public void setFrame(JexlEngine.Scope theFrame) {
+        frame = theFrame;
     }
-
+    
     /**
-     * Gets the map of registers used by this parser.
+     * Gets the frame used by this parser.
      * <p>
      * Since local variables create new named registers, it is important to regain access after
      * parsing to known which / how-many registers are needed.
      * </p>
      * @return the named register map
      */
-    public Map<String, Integer> getNamedRegisters() {
-        return namedRegisters;
+    public JexlEngine.Scope getFrame() {
+        return frame;
     }
+    
 
     /**
      * Checks whether an identifier is a local variable or argument, ie stored in a register. 
@@ -63,8 +63,8 @@ public class JexlParser extends StringPa
      * @return the image
      */
     public String checkVariable(ASTIdentifier identifier, String image) {
-        if (namedRegisters != null) {
-            Integer register = namedRegisters.get(image);
+        if (frame != null) {
+            Integer register = frame.getRegister(image);
             if (register != null) {
                 identifier.setRegister(register);
             }
@@ -81,14 +81,10 @@ public class JexlParser extends StringPa
      * @param image the variable name
      */
     public void declareVariable(ASTVar identifier, String image) {
-        if (namedRegisters == null) {
-            namedRegisters = new LinkedHashMap<String, Integer>();
-        }
-        Integer register = namedRegisters.get(image);
-        if (register == null) {
-            register = Integer.valueOf(namedRegisters.size());
-            namedRegisters.put(image, register);
+        if (frame == null) {
+            frame = new JexlEngine.Scope((String[])null);
         }
+        Integer register = frame.declareVariable(image);
         identifier.setRegister(register);
         identifier.image = image;
     }

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/StringParser.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/StringParser.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/StringParser.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/StringParser.java Wed Nov  2 16:32:39 2011
@@ -54,28 +54,6 @@ public class StringParser {
     }
 
     /**
-     * Reads lines beginning with a given prefix and append them to a StringBuilder without the prefix.
-     * @param strb the string builder to append to
-     * @param str the input char sequence
-     * @param index the beginning offset in the input char sequence
-     * @param prefix the prefix to use
-     * @return the number of characters read from the input sequence
-     */
-    public static int readLines(StringBuilder strb, CharSequence str, int index, String prefix) {
-        CharSequenceReader csr = new CharSequenceReader(str.subSequence(index, str.length()));
-        while (true) {
-            CharSequence line = csr.readLine();
-            if (line != null && (prefix == null || line.toString().startsWith(prefix))) {
-                strb.append(prefix == null? line.toString() : line.subSequence(prefix.length(), line.length()));
-                strb.append(' ');
-            } else {
-                break;
-            }
-        }
-        return csr.position();
-    }
-
-    /**
      * Read the remainder of a string till a given separator,
      * handles escaping through '\' syntax.
      * @param strb the destination buffer to copy characters into
@@ -173,13 +151,13 @@ public class StringParser {
      * @param str the string to escape
      * @return the escaped representation
      */
-    public static String escapeString(String str) {
+    public static String escapeString(String str, char delim) {
         if (str == null) {
             return null;
         }
         final int length = str.length();
         StringBuilder strb = new StringBuilder(length + 2);
-        strb.append('\'');
+        strb.append(delim);
         for (int i = 0; i < length; ++i) {
             char c = str.charAt(i);
             switch (c) {
@@ -224,7 +202,7 @@ public class StringParser {
                     }
             }
         }
-        strb.append('\'');
+        strb.append(delim);
         return strb.toString();
     }
 }
\ No newline at end of file

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/parser/TokenMgrError.java Wed Nov  2 16:32:39 2011
@@ -120,8 +120,8 @@ public class TokenMgrError extends Error
                 + line + ", column "
                 + column + ".  Encountered: "
                 + (eof ? "<EOF> "
-                   : ("\"" + StringParser.escapeString(String.valueOf(current)) + "\"") + " (" + (int) current + "), ")
-                + "after : \"" + StringParser.escapeString(after) + "\"");
+                   : (StringParser.escapeString(String.valueOf(current), '"')) + " (" + (int) current + "), ")
+                + "after : " + StringParser.escapeString(after, '"'));
     }
 
 

Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java
URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java?rev=1196677&r1=1196676&r2=1196677&view=diff
==============================================================================
--- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java (original)
+++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java Wed Nov  2 16:32:39 2011
@@ -101,7 +101,7 @@ public class JexlScriptEngineFactory imp
         if (toDisplay == null) {
             return "JEXL.out.print(null)";
         } else {
-            return "JEXL.out.print("+StringParser.escapeString(toDisplay)+")";
+            return "JEXL.out.print("+StringParser.escapeString(toDisplay, '\'')+")";
         }
     }
 



Mime
View raw message