You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2009/09/28 02:43:39 UTC

svn commit: r819435 [3/23] - in /struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper: ./ compiler/ compiler/tagplugin/ el/ runtime/ security/ servlet/ tagplugins/ tagplugins/jstl/ tagplugins/jstl/core/ util/ xmlparser/

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/AntCompiler.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,493 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.StringTokenizer;
+
+import org.apache.jasper.JasperException;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DefaultLogger;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.taskdefs.Javac;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.types.PatternSet;
+
+/**
+ * Main JSP compiler class. This class uses Ant for compiling.
+ *
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ * @author Mark Roth
+ */
+public class AntCompiler extends Compiler {
+
+    protected static Object javacLock = new Object();
+
+    static {
+        System.setErr(new SystemLogHandler(System.err));
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    protected Project project = null;
+    protected JasperAntLogger logger;
+
+    // ------------------------------------------------------------ Constructor
+
+    // Lazy eval - if we don't need to compile we probably don't need the project
+    protected Project getProject() {
+        
+        if (project != null)
+            return project;
+        
+        // Initializing project
+        project = new Project();
+        logger = new JasperAntLogger();
+        logger.setOutputPrintStream(System.out);
+        logger.setErrorPrintStream(System.err);
+        logger.setMessageOutputLevel(Project.MSG_INFO);
+        project.addBuildListener( logger);
+        if (System.getProperty("catalina.home") != null) {
+            project.setBasedir( System.getProperty("catalina.home"));
+        }
+        
+        if( options.getCompiler() != null ) {
+            if( log.isDebugEnabled() )
+                log.debug("Compiler " + options.getCompiler() );
+            project.setProperty("build.compiler", options.getCompiler() );
+        }
+        project.init();
+        return project;
+    }
+    
+    public class JasperAntLogger extends DefaultLogger {
+        
+        protected StringBuffer reportBuf = new StringBuffer();
+        
+        protected void printMessage(final String message,
+                final PrintStream stream,
+                final int priority) {
+        }
+        
+        protected void log(String message) {
+            reportBuf.append(message);
+            reportBuf.append(System.getProperty("line.separator"));
+        }
+        
+        protected String getReport() {
+            String report = reportBuf.toString();
+            reportBuf.setLength(0);
+            return report;
+        }
+    }
+    
+    // --------------------------------------------------------- Public Methods
+
+
+    /** 
+     * Compile the servlet from .java file to .class file
+     */
+    protected void generateClass(String[] smap)
+        throws FileNotFoundException, JasperException, Exception {
+        
+        long t1 = 0;
+        if (log.isDebugEnabled()) {
+            t1 = System.currentTimeMillis();
+        }
+
+        String javaEncoding = ctxt.getOptions().getJavaEncoding();
+        String javaFileName = ctxt.getServletJavaFileName();
+        String classpath = ctxt.getClassPath(); 
+        
+        String sep = System.getProperty("path.separator");
+        
+        StringBuffer errorReport = new StringBuffer();
+        
+        StringBuffer info=new StringBuffer();
+        info.append("Compile: javaFileName=" + javaFileName + "\n" );
+        info.append("    classpath=" + classpath + "\n" );
+        
+        // Start capturing the System.err output for this thread
+        SystemLogHandler.setThread();
+        
+        // Initializing javac task
+        getProject();
+        Javac javac = (Javac) project.createTask("javac");
+        
+        // Initializing classpath
+        Path path = new Path(project);
+        path.setPath(System.getProperty("java.class.path"));
+        info.append("    cp=" + System.getProperty("java.class.path") + "\n");
+        StringTokenizer tokenizer = new StringTokenizer(classpath, sep);
+        while (tokenizer.hasMoreElements()) {
+            String pathElement = tokenizer.nextToken();
+            File repository = new File(pathElement);
+            path.setLocation(repository);
+            info.append("    cp=" + repository + "\n");
+        }
+        
+        if( log.isDebugEnabled() )
+            log.debug( "Using classpath: " + System.getProperty("java.class.path") + sep
+                    + classpath);
+        
+        // Initializing sourcepath
+        Path srcPath = new Path(project);
+        srcPath.setLocation(options.getScratchDir());
+        
+        info.append("    work dir=" + options.getScratchDir() + "\n");
+        
+        // Initialize and set java extensions
+        String exts = System.getProperty("java.ext.dirs");
+        if (exts != null) {
+            Path extdirs = new Path(project);
+            extdirs.setPath(exts);
+            javac.setExtdirs(extdirs);
+            info.append("    extension dir=" + exts + "\n");
+        }
+
+        // Add endorsed directories if any are specified and we're forking
+        // See Bugzilla 31257
+        if(ctxt.getOptions().getFork()) {
+            String endorsed = System.getProperty("java.endorsed.dirs");
+            if(endorsed != null) {
+                Javac.ImplementationSpecificArgument endorsedArg = 
+                    javac.createCompilerArg();
+                endorsedArg.setLine("-J-Djava.endorsed.dirs=" +
+                        quotePathList(endorsed));
+                info.append("    endorsed dir=" + quotePathList(endorsed) +
+                        "\n");
+            } else {
+                info.append("    no endorsed dirs specified\n");
+            }
+        }
+        
+        // Configure the compiler object
+        javac.setEncoding(javaEncoding);
+        javac.setClasspath(path);
+        javac.setDebug(ctxt.getOptions().getClassDebugInfo());
+        javac.setSrcdir(srcPath);
+        javac.setTempdir(options.getScratchDir());
+        javac.setOptimize(! ctxt.getOptions().getClassDebugInfo() );
+        javac.setFork(ctxt.getOptions().getFork());
+        info.append("    srcDir=" + srcPath + "\n" );
+        
+        // Set the Java compiler to use
+        if (options.getCompiler() != null) {
+            javac.setCompiler(options.getCompiler());
+            info.append("    compiler=" + options.getCompiler() + "\n");
+        }
+
+        if (options.getCompilerTargetVM() != null) {
+            javac.setTarget(options.getCompilerTargetVM());
+            info.append("   compilerTargetVM=" + options.getCompilerTargetVM() + "\n");
+        }
+
+        if (options.getCompilerSourceVM() != null) {
+            javac.setSource(options.getCompilerSourceVM());
+            info.append("   compilerSourceVM=" + options.getCompilerSourceVM() + "\n");
+        }
+        
+        // Build includes path
+        PatternSet.NameEntry includes = javac.createInclude();
+        
+        includes.setName(ctxt.getJavaPath());
+        info.append("    include="+ ctxt.getJavaPath() + "\n" );
+        
+        BuildException be = null;
+        
+        try {
+            if (ctxt.getOptions().getFork()) {
+                javac.execute();
+            } else {
+                synchronized(javacLock) {
+                    javac.execute();
+                }
+            }
+        } catch (BuildException e) {
+            be = e;
+            log.error(Localizer.getMessage("jsp.error.javac"), e);
+            log.error(Localizer.getMessage("jsp.error.javac.env") + info.toString());
+        }
+        
+        errorReport.append(logger.getReport());
+
+        // Stop capturing the System.err output for this thread
+        String errorCapture = SystemLogHandler.unsetThread();
+        if (errorCapture != null) {
+            errorReport.append(System.getProperty("line.separator"));
+            errorReport.append(errorCapture);
+        }
+
+        if (!ctxt.keepGenerated()) {
+            File javaFile = new File(javaFileName);
+            javaFile.delete();
+        }
+        
+        if (be != null) {
+            String errorReportString = errorReport.toString();
+            log.error(Localizer.getMessage("jsp.error.compilation", javaFileName, errorReportString));
+            JavacErrorDetail[] javacErrors = ErrorDispatcher.parseJavacErrors(
+                    errorReportString, javaFileName, pageNodes);
+            if (javacErrors != null) {
+                errDispatcher.javacError(javacErrors);
+            } else {
+                errDispatcher.javacError(errorReportString, be);
+            }
+        }
+        
+        if( log.isDebugEnabled() ) {
+            long t2 = System.currentTimeMillis();
+            log.debug("Compiled " + ctxt.getServletJavaFileName() + " "
+                      + (t2-t1) + "ms");
+        }
+        
+        logger = null;
+        project = null;
+        
+        if (ctxt.isPrototypeMode()) {
+            return;
+        }
+        
+        // JSR45 Support
+        if (! options.isSmapSuppressed()) {
+            SmapUtil.installSmap(smap);
+        }
+    }
+
+    private String quotePathList(String list) {
+        StringBuffer result = new StringBuffer(list.length() + 10);
+        StringTokenizer st = new StringTokenizer(list, File.pathSeparator);
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken();
+            if (token.indexOf(' ') == -1) {
+                result.append(token);
+            } else {
+                result.append('\"');
+                result.append(token);
+                result.append('\"');
+            }
+            if (st.hasMoreTokens()) {
+                result.append(File.pathSeparatorChar);
+            }
+        }
+        return result.toString();
+    }
+
+
+    protected static class SystemLogHandler extends PrintStream {
+
+
+        // ----------------------------------------------------------- Constructors
+
+
+        /**
+         * Construct the handler to capture the output of the given steam.
+         */
+        public SystemLogHandler(PrintStream wrapped) {
+            super(wrapped);
+            this.wrapped = wrapped;
+        }
+
+
+        // ----------------------------------------------------- Instance Variables
+
+
+        /**
+         * Wrapped PrintStream.
+         */
+        protected PrintStream wrapped = null;
+
+
+        /**
+         * Thread <-> PrintStream associations.
+         */
+        protected static ThreadLocal streams = new ThreadLocal();
+
+
+        /**
+         * Thread <-> ByteArrayOutputStream associations.
+         */
+        protected static ThreadLocal data = new ThreadLocal();
+
+
+        // --------------------------------------------------------- Public Methods
+
+
+        public PrintStream getWrapped() {
+          return wrapped;
+        }
+
+        /**
+         * Start capturing thread's output.
+         */
+        public static void setThread() {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            data.set(baos);
+            streams.set(new PrintStream(baos));
+        }
+
+
+        /**
+         * Stop capturing thread's output and return captured data as a String.
+         */
+        public static String unsetThread() {
+            ByteArrayOutputStream baos = 
+                (ByteArrayOutputStream) data.get();
+            if (baos == null) {
+                return null;
+            }
+            streams.set(null);
+            data.set(null);
+            return baos.toString();
+        }
+
+
+        // ------------------------------------------------------ Protected Methods
+
+
+        /**
+         * Find PrintStream to which the output must be written to.
+         */
+        protected PrintStream findStream() {
+            PrintStream ps = (PrintStream) streams.get();
+            if (ps == null) {
+                ps = wrapped;
+            }
+            return ps;
+        }
+
+
+        // ---------------------------------------------------- PrintStream Methods
+
+
+        public void flush() {
+            findStream().flush();
+        }
+
+        public void close() {
+            findStream().close();
+        }
+
+        public boolean checkError() {
+            return findStream().checkError();
+        }
+
+        protected void setError() {
+            //findStream().setError();
+        }
+
+        public void write(int b) {
+            findStream().write(b);
+        }
+
+        public void write(byte[] b)
+            throws IOException {
+            findStream().write(b);
+        }
+
+        public void write(byte[] buf, int off, int len) {
+            findStream().write(buf, off, len);
+        }
+
+        public void print(boolean b) {
+            findStream().print(b);
+        }
+
+        public void print(char c) {
+            findStream().print(c);
+        }
+
+        public void print(int i) {
+            findStream().print(i);
+        }
+
+        public void print(long l) {
+            findStream().print(l);
+        }
+
+        public void print(float f) {
+            findStream().print(f);
+        }
+
+        public void print(double d) {
+            findStream().print(d);
+        }
+
+        public void print(char[] s) {
+            findStream().print(s);
+        }
+
+        public void print(String s) {
+            findStream().print(s);
+        }
+
+        public void print(Object obj) {
+            findStream().print(obj);
+        }
+
+        public void println() {
+            findStream().println();
+        }
+
+        public void println(boolean x) {
+            findStream().println(x);
+        }
+
+        public void println(char x) {
+            findStream().println(x);
+        }
+
+        public void println(int x) {
+            findStream().println(x);
+        }
+
+        public void println(long x) {
+            findStream().println(x);
+        }
+
+        public void println(float x) {
+            findStream().println(x);
+        }
+
+        public void println(double x) {
+            findStream().println(x);
+        }
+
+        public void println(char[] x) {
+            findStream().println(x);
+        }
+
+        public void println(String x) {
+            findStream().println(x);
+        }
+
+        public void println(Object x) {
+            findStream().println(x);
+        }
+
+    }
+
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/BeanRepository.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jasper.compiler;
+
+
+import java.util.HashMap;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Repository of {page, request, session, application}-scoped beans 
+ *
+ * @author Mandar Raje
+ * @author Remy Maucherat
+ */
+public class BeanRepository {
+
+    protected HashMap<String, String> beanTypes;
+    protected ClassLoader loader;
+    protected ErrorDispatcher errDispatcher;
+
+    /**
+     * Constructor.
+     */    
+    public BeanRepository(ClassLoader loader, ErrorDispatcher err) {
+        this.loader = loader;
+        this.errDispatcher = err;
+        beanTypes = new HashMap<String, String>();
+    }
+
+    public void addBean(Node.UseBean n, String s, String type, String scope)
+        throws JasperException {
+
+        if (!(scope == null || scope.equals("page") || scope.equals("request") 
+                || scope.equals("session") || scope.equals("application"))) {
+            errDispatcher.jspError(n, "jsp.error.usebean.badScope");
+        }
+
+        beanTypes.put(s, type);
+    }
+            
+    public Class getBeanType(String bean)
+        throws JasperException {
+        Class clazz = null;
+        try {
+            clazz = loader.loadClass(beanTypes.get(bean));
+        } catch (ClassNotFoundException ex) {
+            throw new JasperException (ex);
+        }
+        return clazz;
+    }
+      
+    public boolean checkVariable(String bean) {
+        return beanTypes.containsKey(bean);
+    }
+
+}
+
+
+
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Collector.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Collect info about the page and nodes, and make them availabe through
+ * the PageInfo object.
+ *
+ * @author Kin-man Chung
+ * @author Mark Roth
+ */
+
+class Collector {
+
+    /**
+     * A visitor for collecting information on the page and the body of
+     * the custom tags.
+     */
+    static class CollectVisitor extends Node.Visitor {
+
+        private boolean scriptingElementSeen = false;
+        private boolean usebeanSeen = false;
+        private boolean includeActionSeen = false;
+        private boolean paramActionSeen = false;
+        private boolean setPropertySeen = false;
+        private boolean hasScriptingVars = false;
+
+        public void visit(Node.ParamAction n) throws JasperException {
+            if (n.getValue().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            paramActionSeen = true;
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            if (n.getPage().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            includeActionSeen = true;
+            visitBody(n);
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            if (n.getPage().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            if (n.getValue() != null && n.getValue().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            setPropertySeen = true;
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            if (n.getBeanName() != null && n.getBeanName().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            usebeanSeen = true;
+                visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            if (n.getHeight() != null && n.getHeight().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            if (n.getWidth() != null && n.getWidth().isExpression()) {
+                scriptingElementSeen = true;
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            // Check to see what kinds of element we see as child elements
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        /**
+         * Check all child nodes for various elements and update the given
+         * ChildInfo object accordingly.  Visits body in the process.
+         */
+        private void checkSeen( Node.ChildInfo ci, Node n )
+            throws JasperException
+        {
+            // save values collected so far
+            boolean scriptingElementSeenSave = scriptingElementSeen;
+            scriptingElementSeen = false;
+            boolean usebeanSeenSave = usebeanSeen;
+            usebeanSeen = false;
+            boolean includeActionSeenSave = includeActionSeen;
+            includeActionSeen = false;
+            boolean paramActionSeenSave = paramActionSeen;
+            paramActionSeen = false;
+            boolean setPropertySeenSave = setPropertySeen;
+            setPropertySeen = false;
+            boolean hasScriptingVarsSave = hasScriptingVars;
+            hasScriptingVars = false;
+
+            // Scan attribute list for expressions
+            if( n instanceof Node.CustomTag ) {
+                Node.CustomTag ct = (Node.CustomTag)n;
+                Node.JspAttribute[] attrs = ct.getJspAttributes();
+                for (int i = 0; attrs != null && i < attrs.length; i++) {
+                    if (attrs[i].isExpression()) {
+                        scriptingElementSeen = true;
+                        break;
+                    }
+                }
+            }
+
+            visitBody(n);
+
+            if( (n instanceof Node.CustomTag) && !hasScriptingVars) {
+                Node.CustomTag ct = (Node.CustomTag)n;
+                hasScriptingVars = ct.getVariableInfos().length > 0 ||
+                    ct.getTagVariableInfos().length > 0;
+            }
+
+            // Record if the tag element and its body contains any scriptlet.
+            ci.setScriptless(! scriptingElementSeen);
+            ci.setHasUseBean(usebeanSeen);
+            ci.setHasIncludeAction(includeActionSeen);
+            ci.setHasParamAction(paramActionSeen);
+            ci.setHasSetProperty(setPropertySeen);
+            ci.setHasScriptingVars(hasScriptingVars);
+
+            // Propagate value of scriptingElementSeen up.
+            scriptingElementSeen = scriptingElementSeen || scriptingElementSeenSave;
+            usebeanSeen = usebeanSeen || usebeanSeenSave;
+            setPropertySeen = setPropertySeen || setPropertySeenSave;
+            includeActionSeen = includeActionSeen || includeActionSeenSave;
+            paramActionSeen = paramActionSeen || paramActionSeenSave;
+            hasScriptingVars = hasScriptingVars || hasScriptingVarsSave;
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+            if (n.getNameAttribute().isExpression())
+                scriptingElementSeen = true;
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; i < attrs.length; i++) {
+                if (attrs[i].isExpression()) {
+                    scriptingElementSeen = true;
+                    break;
+                }
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            checkSeen( n.getChildInfo(), n );
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+            scriptingElementSeen = true;
+        }
+
+        public void updatePageInfo(PageInfo pageInfo) {
+            pageInfo.setScriptless(! scriptingElementSeen);
+        }
+    }
+
+
+    public static void collect(Compiler compiler, Node.Nodes page)
+        throws JasperException {
+
+    CollectVisitor collectVisitor = new CollectVisitor();
+        page.visit(collectVisitor);
+        collectVisitor.updatePageInfo(compiler.getPageInfo());
+
+    }
+}
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,551 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jasper.JasperException;
+import org.apache.jasper.JspCompilationContext;
+import org.apache.jasper.Options;
+import org.apache.jasper.servlet.JspServletWrapper;
+
+/**
+ * Main JSP compiler class. This class uses Ant for compiling.
+ * 
+ * @author Anil K. Vijendran
+ * @author Mandar Raje
+ * @author Pierre Delisle
+ * @author Kin-man Chung
+ * @author Remy Maucherat
+ * @author Mark Roth
+ */
+public abstract class Compiler {
+    
+    protected org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+            .getLog(Compiler.class);
+
+    // ----------------------------------------------------- Instance Variables
+
+    protected JspCompilationContext ctxt;
+
+    protected ErrorDispatcher errDispatcher;
+
+    protected PageInfo pageInfo;
+
+    protected JspServletWrapper jsw;
+
+    protected TagFileProcessor tfp;
+
+    protected Options options;
+
+    protected Node.Nodes pageNodes;
+
+    // ------------------------------------------------------------ Constructor
+
+    public void init(JspCompilationContext ctxt, JspServletWrapper jsw) {
+        this.jsw = jsw;
+        this.ctxt = ctxt;
+        this.options = ctxt.getOptions();
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * <p>
+     * Retrieves the parsed nodes of the JSP page, if they are available. May
+     * return null. Used in development mode for generating detailed error
+     * messages. http://issues.apache.org/bugzilla/show_bug.cgi?id=37062.
+     * </p>
+     */
+    public Node.Nodes getPageNodes() {
+        return this.pageNodes;
+    }
+
+    /**
+     * Compile the jsp file into equivalent servlet in .java file
+     * 
+     * @return a smap for the current JSP page, if one is generated, null
+     *         otherwise
+     */
+    protected String[] generateJava() throws Exception {
+
+        String[] smapStr = null;
+
+        long t1, t2, t3, t4;
+
+        t1 = t2 = t3 = t4 = 0;
+
+        if (log.isDebugEnabled()) {
+            t1 = System.currentTimeMillis();
+        }
+
+        // Setup page info area
+        pageInfo = new PageInfo(new BeanRepository(ctxt.getClassLoader(),
+                errDispatcher), ctxt.getJspFile());
+
+        JspConfig jspConfig = options.getJspConfig();
+        JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(ctxt
+                .getJspFile());
+
+        /*
+         * If the current uri is matched by a pattern specified in a
+         * jsp-property-group in web.xml, initialize pageInfo with those
+         * properties.
+         */
+        if (jspProperty.isELIgnored() != null) {
+            pageInfo.setELIgnored(JspUtil.booleanValue(jspProperty
+                    .isELIgnored()));
+        }
+        if (jspProperty.isScriptingInvalid() != null) {
+            pageInfo.setScriptingInvalid(JspUtil.booleanValue(jspProperty
+                    .isScriptingInvalid()));
+        }
+        if (jspProperty.getIncludePrelude() != null) {
+            pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
+        }
+        if (jspProperty.getIncludeCoda() != null) {
+            pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
+        }
+        if (jspProperty.isDeferedSyntaxAllowedAsLiteral() != null) {
+            pageInfo.setDeferredSyntaxAllowedAsLiteral(JspUtil.booleanValue(jspProperty
+                    .isDeferedSyntaxAllowedAsLiteral()));
+        }
+        if (jspProperty.isTrimDirectiveWhitespaces() != null) {
+            pageInfo.setTrimDirectiveWhitespaces(JspUtil.booleanValue(jspProperty
+                    .isTrimDirectiveWhitespaces()));
+        }
+
+        ctxt.checkOutputDir();
+        String javaFileName = ctxt.getServletJavaFileName();
+
+        ServletWriter writer = null;
+        try {
+            /*
+             * The setting of isELIgnored changes the behaviour of the parser
+             * in subtle ways. To add to the 'fun', isELIgnored can be set in
+             * any file that forms part of the translation unit so setting it
+             * in a file included towards the end of the translation unit can
+             * change how the parser should have behaved when parsing content
+             * up to the point where isELIgnored was set. Arghh!
+             * Previous attempts to hack around this have only provided partial
+             * solutions. We now use two passes to parse the translation unit.
+             * The first just parses the directives and the second parses the
+             * whole translation unit once we know how isELIgnored has been set.
+             * TODO There are some possible optimisations of this process.  
+             */ 
+            // Parse the file
+            ParserController parserCtl = new ParserController(ctxt, this);
+
+            // Pass 1 - the directives
+            Node.Nodes directives =
+                parserCtl.parseDirectives(ctxt.getJspFile());
+            Validator.validateDirectives(this, directives);
+
+            // Pass 2 - the whole translation unit
+            pageNodes = parserCtl.parse(ctxt.getJspFile());
+
+            if (ctxt.isPrototypeMode()) {
+                // generate prototype .java file for the tag file
+                writer = setupContextWriter(javaFileName);
+                Generator.generate(writer, this, pageNodes);
+                writer.close();
+                writer = null;
+                return null;
+            }
+
+            // Validate and process attributes - don't re-validate the
+            // directives we validated in pass 1
+            Validator.validateExDirectives(this, pageNodes);
+
+            if (log.isDebugEnabled()) {
+                t2 = System.currentTimeMillis();
+            }
+
+            // Collect page info
+            Collector.collect(this, pageNodes);
+
+            // Compile (if necessary) and load the tag files referenced in
+            // this compilation unit.
+            tfp = new TagFileProcessor();
+            tfp.loadTagFiles(this, pageNodes);
+
+            if (log.isDebugEnabled()) {
+                t3 = System.currentTimeMillis();
+            }
+
+            // Determine which custom tag needs to declare which scripting vars
+            ScriptingVariabler.set(pageNodes, errDispatcher);
+
+            // Optimizations by Tag Plugins
+            TagPluginManager tagPluginManager = options.getTagPluginManager();
+            tagPluginManager.apply(pageNodes, errDispatcher, pageInfo);
+
+            // Optimization: concatenate contiguous template texts.
+            TextOptimizer.concatenate(this, pageNodes);
+
+            // Generate static function mapper codes.
+            ELFunctionMapper.map(this, pageNodes);
+
+            // generate servlet .java file
+            writer = setupContextWriter(javaFileName);
+            Generator.generate(writer, this, pageNodes);
+            writer.close();
+            writer = null;
+
+            // The writer is only used during the compile, dereference
+            // it in the JspCompilationContext when done to allow it
+            // to be GC'd and save memory.
+            ctxt.setWriter(null);
+
+            if (log.isDebugEnabled()) {
+                t4 = System.currentTimeMillis();
+                log.debug("Generated " + javaFileName + " total=" + (t4 - t1)
+                        + " generate=" + (t4 - t3) + " validate=" + (t2 - t1));
+            }
+
+        } catch (Exception e) {
+            if (writer != null) {
+                try {
+                    writer.close();
+                    writer = null;
+                } catch (Exception e1) {
+                    // do nothing
+                }
+            }
+            // Remove the generated .java file
+            new File(javaFileName).delete();
+            throw e;
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (Exception e2) {
+                    // do nothing
+                }
+            }
+        }
+
+        // JSR45 Support
+        if (!options.isSmapSuppressed()) {
+            smapStr = SmapUtil.generateSmap(ctxt, pageNodes);
+        }
+
+        // If any proto type .java and .class files was generated,
+        // the prototype .java may have been replaced by the current
+        // compilation (if the tag file is self referencing), but the
+        // .class file need to be removed, to make sure that javac would
+        // generate .class again from the new .java file just generated.
+        tfp.removeProtoTypeFiles(ctxt.getClassFileName());
+
+        return smapStr;
+    }
+
+	private ServletWriter setupContextWriter(String javaFileName)
+			throws FileNotFoundException, JasperException {
+		ServletWriter writer;
+		// Setup the ServletWriter
+		String javaEncoding = ctxt.getOptions().getJavaEncoding();
+		OutputStreamWriter osw = null;
+
+		try {
+		    osw = new OutputStreamWriter(
+		            new FileOutputStream(javaFileName), javaEncoding);
+		} catch (UnsupportedEncodingException ex) {
+		    errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
+		            javaEncoding);
+		}
+
+		writer = new ServletWriter(new PrintWriter(osw));
+		ctxt.setWriter(writer);
+		return writer;
+	}
+
+    /**
+     * Compile the servlet from .java file to .class file
+     */
+    protected abstract void generateClass(String[] smap)
+            throws FileNotFoundException, JasperException, Exception;
+
+    /**
+     * Compile the jsp file from the current engine context
+     */
+    public void compile() throws FileNotFoundException, JasperException,
+            Exception {
+        compile(true);
+    }
+
+    /**
+     * Compile the jsp file from the current engine context. As an side- effect,
+     * tag files that are referenced by this page are also compiled.
+     * 
+     * @param compileClass
+     *            If true, generate both .java and .class file If false,
+     *            generate only .java file
+     */
+    public void compile(boolean compileClass) throws FileNotFoundException,
+            JasperException, Exception {
+        compile(compileClass, false);
+    }
+
+    /**
+     * Compile the jsp file from the current engine context. As an side- effect,
+     * tag files that are referenced by this page are also compiled.
+     * 
+     * @param compileClass
+     *            If true, generate both .java and .class file If false,
+     *            generate only .java file
+     * @param jspcMode
+     *            true if invoked from JspC, false otherwise
+     */
+    public void compile(boolean compileClass, boolean jspcMode)
+            throws FileNotFoundException, JasperException, Exception {
+        if (errDispatcher == null) {
+            this.errDispatcher = new ErrorDispatcher(jspcMode);
+        }
+
+        try {
+            String[] smap = generateJava();
+            if (compileClass) {
+                generateClass(smap);
+                // Fix for bugzilla 41606
+                // Set JspServletWrapper.servletClassLastModifiedTime after successful compile
+                String targetFileName = ctxt.getClassFileName();
+                if (targetFileName != null) {
+                    File targetFile = new File(targetFileName);
+                    if (targetFile.exists() && jsw != null) {
+                        jsw.setServletClassLastModifiedTime(targetFile.lastModified());
+                    }
+                }
+            }
+        } finally {
+            if (tfp != null && ctxt.isPrototypeMode()) {
+                tfp.removeProtoTypeFiles(null);
+            }
+            // Make sure these object which are only used during the
+            // generation and compilation of the JSP page get
+            // dereferenced so that they can be GC'd and reduce the
+            // memory footprint.
+            tfp = null;
+            errDispatcher = null;
+            pageInfo = null;
+
+            // Only get rid of the pageNodes if in production.
+            // In development mode, they are used for detailed
+            // error messages.
+            // http://issues.apache.org/bugzilla/show_bug.cgi?id=37062
+            if (!this.options.getDevelopment()) {
+                pageNodes = null;
+            }
+
+            if (ctxt.getWriter() != null) {
+                ctxt.getWriter().close();
+                ctxt.setWriter(null);
+            }
+        }
+    }
+
+    /**
+     * This is a protected method intended to be overridden by subclasses of
+     * Compiler. This is used by the compile method to do all the compilation.
+     */
+    public boolean isOutDated() {
+        return isOutDated(true);
+    }
+
+    /**
+     * Determine if a compilation is necessary by checking the time stamp of the
+     * JSP page with that of the corresponding .class or .java file. If the page
+     * has dependencies, the check is also extended to its dependeants, and so
+     * on. This method can by overidden by a subclasses of Compiler.
+     * 
+     * @param checkClass
+     *            If true, check against .class file, if false, check against
+     *            .java file.
+     */
+    public boolean isOutDated(boolean checkClass) {
+
+        String jsp = ctxt.getJspFile();
+
+        if (jsw != null
+                && (ctxt.getOptions().getModificationTestInterval() > 0)) {
+
+            if (jsw.getLastModificationTest()
+                    + (ctxt.getOptions().getModificationTestInterval() * 1000) > System
+                    .currentTimeMillis()) {
+                return false;
+            } else {
+                jsw.setLastModificationTest(System.currentTimeMillis());
+            }
+        }
+
+        long jspRealLastModified = 0;
+        try {
+            URL jspUrl = ctxt.getResource(jsp);
+            if (jspUrl == null) {
+                ctxt.incrementRemoved();
+                return false;
+            }
+            URLConnection uc = jspUrl.openConnection();
+            if (uc instanceof JarURLConnection) {
+                jspRealLastModified =
+                    ((JarURLConnection) uc).getJarEntry().getTime();
+            } else {
+                jspRealLastModified = uc.getLastModified();
+            }
+            uc.getInputStream().close();
+        } catch (Exception e) {
+            return true;
+        }
+
+        long targetLastModified = 0;
+        File targetFile;
+
+        if (checkClass) {
+            targetFile = new File(ctxt.getClassFileName());
+        } else {
+            targetFile = new File(ctxt.getServletJavaFileName());
+        }
+
+        if (!targetFile.exists()) {
+            return true;
+        }
+
+        targetLastModified = targetFile.lastModified();
+        if (checkClass && jsw != null) {
+            jsw.setServletClassLastModifiedTime(targetLastModified);
+        }
+        if (targetLastModified < jspRealLastModified) {
+            if (log.isDebugEnabled()) {
+                log.debug("Compiler: outdated: " + targetFile + " "
+                        + targetLastModified);
+            }
+            return true;
+        }
+
+        // determine if source dependent files (e.g. includes using include
+        // directives) have been changed.
+        if (jsw == null) {
+            return false;
+        }
+
+        List depends = jsw.getDependants();
+        if (depends == null) {
+            return false;
+        }
+
+        Iterator it = depends.iterator();
+        while (it.hasNext()) {
+            String include = (String) it.next();
+            try {
+                URL includeUrl = ctxt.getResource(include);
+                if (includeUrl == null) {
+                    return true;
+                }
+
+                URLConnection iuc = includeUrl.openConnection();
+                long includeLastModified = 0;
+                if (iuc instanceof JarURLConnection) {
+                    includeLastModified =
+                        ((JarURLConnection) iuc).getJarEntry().getTime();
+                } else {
+                    includeLastModified = iuc.getLastModified();
+                }
+                iuc.getInputStream().close();
+
+                if (includeLastModified > targetLastModified) {
+                    return true;
+                }
+            } catch (Exception e) {
+                return true;
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Gets the error dispatcher.
+     */
+    public ErrorDispatcher getErrorDispatcher() {
+        return errDispatcher;
+    }
+
+    /**
+     * Gets the info about the page under compilation
+     */
+    public PageInfo getPageInfo() {
+        return pageInfo;
+    }
+
+    public JspCompilationContext getCompilationContext() {
+        return ctxt;
+    }
+
+    /**
+     * Remove generated files
+     */
+    public void removeGeneratedFiles() {
+        try {
+            String classFileName = ctxt.getClassFileName();
+            if (classFileName != null) {
+                File classFile = new File(classFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + classFile);
+                classFile.delete();
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+        try {
+            String javaFileName = ctxt.getServletJavaFileName();
+            if (javaFileName != null) {
+                File javaFile = new File(javaFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + javaFile);
+                javaFile.delete();
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+    }
+
+    public void removeGeneratedClassFiles() {
+        try {
+            String classFileName = ctxt.getClassFileName();
+            if (classFileName != null) {
+                File classFile = new File(classFileName);
+                if (log.isDebugEnabled())
+                    log.debug("Deleting " + classFile);
+                classFile.delete();
+            }
+        } catch (Exception e) {
+            // Remove as much as possible, ignore possible exceptions
+        }
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import org.apache.jasper.JasperException;
+
+/**
+ * Default implementation of ErrorHandler interface.
+ *
+ * @author Jan Luehe
+ */
+class DefaultErrorHandler implements ErrorHandler {
+    
+    /*
+     * Processes the given JSP parse error.
+     *
+     * @param fname Name of the JSP file in which the parse error occurred
+     * @param line Parse error line number
+     * @param column Parse error column number
+     * @param errMsg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String fname, int line, int column, String errMsg,
+            Exception ex) throws JasperException {
+        throw new JasperException(fname + "(" + line + "," + column + ")"
+                + " " + errMsg, ex);
+    }
+    
+    /*
+     * Processes the given JSP parse error.
+     *
+     * @param errMsg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String errMsg, Exception ex) throws JasperException {
+        throw new JasperException(errMsg, ex);
+    }
+    
+    /*
+     * Processes the given javac compilation errors.
+     *
+     * @param details Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    public void javacError(JavacErrorDetail[] details) throws JasperException {
+        
+        if (details == null) {
+            return;
+        }
+        
+        Object[] args = null;
+        StringBuffer buf = new StringBuffer();
+        
+        for (int i=0; i < details.length; i++) {
+            if (details[i].getJspBeginLineNumber() >= 0) {
+                args = new Object[] {
+                        new Integer(details[i].getJspBeginLineNumber()), 
+                        details[i].getJspFileName() };
+                buf.append("\n\n");
+                buf.append(Localizer.getMessage("jsp.error.single.line.number",
+                        args));
+                buf.append("\n");
+                buf.append(details[i].getErrorMessage());
+                buf.append("\n");
+                buf.append(details[i].getJspExtract());
+            } else {
+                args = new Object[] {
+                        new Integer(details[i].getJavaLineNumber()) };
+                buf.append("\n\n");
+                buf.append(Localizer.getMessage("jsp.error.java.line.number",
+                        args));
+                buf.append("\n");
+                buf.append(details[i].getErrorMessage());
+            }
+        }
+        buf.append("\n\nStacktrace:");
+        throw new JasperException(
+                Localizer.getMessage("jsp.error.unable.compile") + ": " + buf);
+    }
+    
+    /**
+     * Processes the given javac error report and exception.
+     *
+     * @param errorReport Compilation error report
+     * @param exception Compilation exception
+     */
+    public void javacError(String errorReport, Exception exception)
+    throws JasperException {
+        
+        throw new JasperException(
+                Localizer.getMessage("jsp.error.unable.compile"), exception);
+    }
+    
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import org.xml.sax.Attributes;
+import org.apache.jasper.JasperException;
+
+class Dumper {
+
+    static class DumpVisitor extends Node.Visitor {
+	private int indent = 0;
+
+	private String getAttributes(Attributes attrs) {
+	    if (attrs == null)
+		return "";
+
+	    StringBuffer buf = new StringBuffer();
+	    for (int i=0; i < attrs.getLength(); i++) {
+		buf.append(" " + attrs.getQName(i) + "=\""
+			   + attrs.getValue(i) + "\"");
+	    }
+	    return buf.toString();
+	}
+
+	private void printString(String str) {
+	    printIndent();
+	    System.out.print(str);
+	}
+
+	private void printString(String prefix, char[] chars, String suffix) {
+	    String str = null;
+	    if (chars != null) {
+		str = new String(chars);
+	    }
+	    printString(prefix, str, suffix);
+	}
+	     
+	private void printString(String prefix, String str, String suffix) {
+	    printIndent();
+	    if (str != null) {
+		System.out.print(prefix + str + suffix);
+	    } else {
+		System.out.print(prefix + suffix);
+	    }
+	}
+
+	private void printAttributes(String prefix, Attributes attrs,
+				     String suffix) {
+	    printString(prefix, getAttributes(attrs), suffix);
+	}
+
+	private void dumpBody(Node n) throws JasperException {
+	    Node.Nodes page = n.getBody();
+	    if (page != null) {
+//		indent++;
+		page.visit(this);
+//		indent--;
+	    }
+        }
+
+        public void visit(Node.PageDirective n) throws JasperException {
+	    printAttributes("<%@ page", n.getAttributes(), "%>");
+        }
+
+        public void visit(Node.TaglibDirective n) throws JasperException {
+	    printAttributes("<%@ taglib", n.getAttributes(), "%>");
+        }
+
+        public void visit(Node.IncludeDirective n) throws JasperException {
+	    printAttributes("<%@ include", n.getAttributes(), "%>");
+	    dumpBody(n);
+        }
+
+        public void visit(Node.Comment n) throws JasperException {
+	    printString("<%--", n.getText(), "--%>");
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+	    printString("<%!", n.getText(), "%>");
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+	    printString("<%=", n.getText(), "%>");
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+	    printString("<%", n.getText(), "%>");
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+	    printAttributes("<jsp:include", n.getAttributes(), ">");
+	    dumpBody(n);
+            printString("</jsp:include>");
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+	    printAttributes("<jsp:forward", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:forward>");
+        }
+
+        public void visit(Node.GetProperty n) throws JasperException {
+	    printAttributes("<jsp:getProperty", n.getAttributes(), "/>");
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+	    printAttributes("<jsp:setProperty", n.getAttributes(), ">");
+            dumpBody(n);
+            printString("</jsp:setProperty>");
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+	    printAttributes("<jsp:useBean", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:useBean>");
+        }
+	
+        public void visit(Node.PlugIn n) throws JasperException {
+	    printAttributes("<jsp:plugin", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:plugin>");
+	}
+        
+        public void visit(Node.ParamsAction n) throws JasperException {
+	    printAttributes("<jsp:params", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:params>");
+        }
+        
+        public void visit(Node.ParamAction n) throws JasperException {
+	    printAttributes("<jsp:param", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:param>");
+        }
+        
+        public void visit(Node.NamedAttribute n) throws JasperException {
+	    printAttributes("<jsp:attribute", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:attribute>");
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+	    printAttributes("<jsp:body", n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</jsp:body>");
+        }
+        
+        public void visit(Node.ELExpression n) throws JasperException {
+	    printString( "${" + new String( n.getText() ) + "}" );
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+	    printAttributes("<" + n.getQName(), n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</" + n.getQName() + ">");
+        }
+
+	public void visit(Node.UninterpretedTag n) throws JasperException {
+	    String tag = n.getQName();
+	    printAttributes("<"+tag, n.getAttributes(), ">");
+	    dumpBody(n);
+	    printString("</" + tag + ">");
+        }
+
+	public void visit(Node.TemplateText n) throws JasperException {
+	    printString(new String(n.getText()));
+	}
+
+	private void printIndent() {
+	    for (int i=0; i < indent; i++) {
+		System.out.print("  ");
+	    }
+	}
+    }
+
+    public static void dump(Node n) {
+	try {
+	    n.accept(new DumpVisitor());	
+	} catch (JasperException e) {
+	    e.printStackTrace();
+	}
+    }
+
+    public static void dump(Node.Nodes page) {
+	try {
+	    page.visit(new DumpVisitor());
+	} catch (JasperException e) {
+	    e.printStackTrace();
+	}
+    }
+}
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import org.apache.jasper.JasperException;
+
+/**
+ * This class generates functions mappers for the EL expressions in the page.
+ * Instead of a global mapper, a mapper is used for ecah call to EL
+ * evaluator, thus avoiding the prefix overlapping and redefinition
+ * issues.
+ *
+ * @author Kin-man Chung
+ */
+
+public class ELFunctionMapper {
+    private int currFunc = 0;
+    StringBuffer ds;  // Contains codes to initialize the functions mappers.
+    StringBuffer ss;  // Contains declarations of the functions mappers.
+
+    /**
+     * Creates the functions mappers for all EL expressions in the JSP page.
+     *
+     * @param compiler Current compiler, mainly for accessing error dispatcher.
+     * @param page The current compilation unit.
+     */
+    public static void map(Compiler compiler, Node.Nodes page) 
+                throws JasperException {
+
+        ELFunctionMapper map = new ELFunctionMapper();
+        map.ds = new StringBuffer();
+        map.ss = new StringBuffer();
+
+        page.visit(map.new ELFunctionVisitor());
+
+        // Append the declarations to the root node
+        String ds = map.ds.toString();
+        if (ds.length() > 0) {
+            Node root = page.getRoot();
+            new Node.Declaration(map.ss.toString(), null, root);
+            new Node.Declaration("static {\n" + ds + "}\n", null, root);
+        }
+    }
+
+    /**
+     * A visitor for the page.  The places where EL is allowed are scanned
+     * for functions, and if found functions mappers are created.
+     */
+    class ELFunctionVisitor extends Node.Visitor {
+        
+        /**
+         * Use a global name map to facilitate reuse of function maps.
+         * The key used is prefix:function:uri.
+         */
+        private HashMap<String, String> gMap = new HashMap<String, String>();
+
+        public void visit(Node.ParamAction n) throws JasperException {
+            doMap(n.getValue());
+            visitBody(n);
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            doMap(n.getPage());
+            visitBody(n);
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            doMap(n.getPage());
+            visitBody(n);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            doMap(n.getValue());
+            visitBody(n);
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            doMap(n.getBeanName());
+            visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            doMap(n.getHeight());
+            doMap(n.getWidth());
+            visitBody(n);
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            doMap(n.getNameAttribute());
+            visitBody(n);
+        }
+
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            Node.JspAttribute[] attrs = n.getJspAttributes();
+            for (int i = 0; attrs != null && i < attrs.length; i++) {
+                doMap(attrs[i]);
+            }
+            visitBody(n);
+        }
+
+        public void visit(Node.ELExpression n) throws JasperException {
+            doMap(n.getEL());
+        }
+
+        private void doMap(Node.JspAttribute attr) 
+                throws JasperException {
+            if (attr != null) {
+                doMap(attr.getEL());
+            }
+        }
+
+        /**
+         * Creates function mappers, if needed, from ELNodes
+         */
+        private void doMap(ELNode.Nodes el) 
+                throws JasperException {
+
+            // Only care about functions in ELNode's
+            class Fvisitor extends ELNode.Visitor {
+                ArrayList<ELNode.Function> funcs =
+                    new ArrayList<ELNode.Function>();
+                HashMap<String, String> keyMap = new HashMap<String, String>();
+                public void visit(ELNode.Function n) throws JasperException {
+                    String key = n.getPrefix() + ":" + n.getName();
+                    if (! keyMap.containsKey(key)) {
+                        keyMap.put(key,"");
+                        funcs.add(n);
+                    }
+                }
+            }
+
+            if (el == null) {
+                return;
+            }
+
+            // First locate all unique functions in this EL
+            Fvisitor fv = new Fvisitor();
+            el.visit(fv);
+            ArrayList functions = fv.funcs;
+
+            if (functions.size() == 0) {
+                return;
+            }
+
+            // Reuse a previous map if possible
+            String decName = matchMap(functions);
+            if (decName != null) {
+                el.setMapName(decName);
+                return;
+            }
+        
+            // Generate declaration for the map statically
+            decName = getMapName();
+            ss.append("static private org.apache.jasper.runtime.ProtectedFunctionMapper " + decName + ";\n");
+
+            ds.append("  " + decName + "= ");
+            ds.append("org.apache.jasper.runtime.ProtectedFunctionMapper");
+
+            // Special case if there is only one function in the map
+            String funcMethod = null;
+            if (functions.size() == 1) {
+                funcMethod = ".getMapForFunction";
+            } else {
+                ds.append(".getInstance();\n");
+                funcMethod = "  " + decName + ".mapFunction";
+            }
+
+            // Setup arguments for either getMapForFunction or mapFunction
+            for (int i = 0; i < functions.size(); i++) {
+                ELNode.Function f = (ELNode.Function)functions.get(i);
+                FunctionInfo funcInfo = f.getFunctionInfo();
+                String key = f.getPrefix()+ ":" + f.getName();
+                ds.append(funcMethod + "(\"" + key + "\", " +
+                        funcInfo.getFunctionClass() + ".class, " +
+                        '\"' + f.getMethodName() + "\", " +
+                        "new Class[] {");
+                String params[] = f.getParameters();
+                for (int k = 0; k < params.length; k++) {
+                    if (k != 0) {
+                        ds.append(", ");
+                    }
+                    int iArray = params[k].indexOf('[');
+                    if (iArray < 0) {
+                        ds.append(params[k] + ".class");
+                    }
+                    else {
+                        String baseType = params[k].substring(0, iArray);
+                        ds.append("java.lang.reflect.Array.newInstance(");
+                        ds.append(baseType);
+                        ds.append(".class,");
+
+                        // Count the number of array dimension
+                        int aCount = 0;
+                        for (int jj = iArray; jj < params[k].length(); jj++ ) {
+                            if (params[k].charAt(jj) == '[') {
+                                aCount++;
+                            }
+                        }
+                        if (aCount == 1) {
+                            ds.append("0).getClass()");
+                        } else {
+                            ds.append("new int[" + aCount + "]).getClass()");
+                        }
+                    }
+                }
+                ds.append("});\n");
+                // Put the current name in the global function map
+                gMap.put(f.getPrefix() + ':' + f.getName() + ':' + f.getUri(),
+                         decName);
+            }
+            el.setMapName(decName);
+        }
+
+        /**
+         * Find the name of the function mapper for an EL.  Reuse a
+         * previously generated one if possible.
+         * @param functions An ArrayList of ELNode.Function instances that
+         *                  represents the functions in an EL
+         * @return A previous generated function mapper name that can be used
+         *         by this EL; null if none found.
+         */
+        private String matchMap(ArrayList functions) {
+
+            String mapName = null;
+            for (int i = 0; i < functions.size(); i++) {
+                ELNode.Function f = (ELNode.Function)functions.get(i);
+                String temName = (String) gMap.get(f.getPrefix() + ':' +
+                                        f.getName() + ':' + f.getUri());
+                if (temName == null) {
+                    return null;
+                }
+                if (mapName == null) {
+                    mapName = temName;
+                } else if (!temName.equals(mapName)) {
+                    // If not all in the previous match, then no match.
+                    return null;
+                }
+            }
+            return mapName;
+        }
+
+        /*
+         * @return An unique name for a function mapper.
+         */
+        private String getMapName() {
+            return "_jspx_fnmap_" + currFunc++;
+        }
+    }
+}
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import org.apache.jasper.JasperException;
+
+/**
+ * This class defines internal representation for an EL Expression
+ *
+ * It currently only defines functions.  It can be expanded to define
+ * all the components of an EL expression, if need to.
+ *
+ * @author Kin-man Chung
+ */
+
+abstract class ELNode {
+
+    abstract public void accept(Visitor v) throws JasperException;
+
+    /**
+     * Child classes
+     */
+
+
+    /**
+     * Represents an EL expression: anything in ${ and }.
+     */
+    public static class Root extends ELNode {
+
+	private ELNode.Nodes expr;
+    private char type;
+
+	Root(ELNode.Nodes expr, char type) {
+	    this.expr = expr;
+        this.type = type;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public ELNode.Nodes getExpression() {
+	    return expr;
+	}
+
+    public char getType() {
+        return type;
+    }
+    }
+
+    /**
+     * Represents text outside of EL expression.
+     */
+    public static class Text extends ELNode {
+
+	private String text;
+
+	Text(String text) {
+	    this.text = text;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public String getText() {
+	    return text;
+	}
+    }
+
+    /**
+     * Represents anything in EL expression, other than functions, including
+     * function arguments etc
+     */
+    public static class ELText extends ELNode {
+
+	private String text;
+
+	ELText(String text) {
+	    this.text = text;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public String getText() {
+	    return text;
+	}
+    }
+
+    /**
+     * Represents a function
+     * Currently only include the prefix and function name, but not its
+     * arguments.
+     */
+    public static class Function extends ELNode {
+
+	private String prefix;
+	private String name;
+	private String uri;
+	private FunctionInfo functionInfo;
+	private String methodName;
+	private String[] parameters;
+
+	Function(String prefix, String name) {
+	    this.prefix = prefix;
+	    this.name = name;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public String getPrefix() {
+	    return prefix;
+	}
+
+	public String getName() {
+	    return name;
+	}
+
+	public void setUri(String uri) {
+	    this.uri = uri;
+	}
+
+	public String getUri() {
+	    return uri;
+	}
+
+	public void setFunctionInfo(FunctionInfo f) {
+	    this.functionInfo = f;
+	}
+
+	public FunctionInfo getFunctionInfo() {
+	    return functionInfo;
+	}
+
+	public void setMethodName(String methodName) {
+	    this.methodName = methodName;
+	}
+
+	public String getMethodName() {
+	    return methodName;
+	}
+
+	public void setParameters(String[] parameters) {
+	    this.parameters = parameters;
+	}
+
+	public String[] getParameters() {
+	    return parameters;
+	}
+    }
+
+    /**
+     * An ordered list of ELNode.
+     */
+    public static class Nodes {
+
+	/* Name used for creating a map for the functions in this
+	   EL expression, for communication to Generator.
+	 */
+	String mapName = null;	// The function map associated this EL
+	private List<ELNode> list;
+
+	public Nodes() {
+	    list = new ArrayList<ELNode>();
+	}
+
+	public void add(ELNode en) {
+	    list.add(en);
+	}
+
+	/**
+	 * Visit the nodes in the list with the supplied visitor
+	 * @param v The visitor used
+	 */
+	public void visit(Visitor v) throws JasperException {
+	    Iterator<ELNode> iter = list.iterator();
+	    while (iter.hasNext()) {
+	        ELNode n = iter.next();
+	        n.accept(v);
+	    }
+	}
+
+	public Iterator<ELNode> iterator() {
+	    return list.iterator();
+	}
+
+	public boolean isEmpty() {
+	    return list.size() == 0;
+	}
+
+	/**
+	 * @return true if the expression contains a ${...}
+	 */
+	public boolean containsEL() {
+	    Iterator<ELNode> iter = list.iterator();
+	    while (iter.hasNext()) {
+	        ELNode n = iter.next();
+	        if (n instanceof Root) {
+	            return true;
+	        }
+	    }
+	    return false;
+	}
+
+	public void setMapName(String name) {
+	    this.mapName = name;
+	}
+
+	public String getMapName() {
+	    return mapName;
+	}
+    
+    }
+
+    /*
+     * A visitor class for traversing ELNodes
+     */
+    public static class Visitor {
+
+	public void visit(Root n) throws JasperException {
+	    n.getExpression().visit(this);
+	}
+
+	public void visit(Function n) throws JasperException {
+	}
+
+	public void visit(Text n) throws JasperException {
+	}
+
+	public void visit(ELText n) throws JasperException {
+	}
+    }
+}
+

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java?rev=819435&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java Mon Sep 28 00:43:34 2009
@@ -0,0 +1,382 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jasper.compiler;
+
+/**
+ * This class implements a parser for EL expressions.
+ * 
+ * It takes strings of the form xxx${..}yyy${..}zzz etc, and turn it into a
+ * ELNode.Nodes.
+ * 
+ * Currently, it only handles text outside ${..} and functions in ${ ..}.
+ * 
+ * @author Kin-man Chung
+ */
+
+public class ELParser {
+
+    private Token curToken; // current token
+
+    private ELNode.Nodes expr;
+
+    private ELNode.Nodes ELexpr;
+
+    private int index; // Current index of the expression
+
+    private String expression; // The EL expression
+    
+    private char type;
+
+    private boolean escapeBS; // is '\' an escape char in text outside EL?
+
+    private static final String reservedWords[] = { "and", "div", "empty",
+            "eq", "false", "ge", "gt", "instanceof", "le", "lt", "mod", "ne",
+            "not", "null", "or", "true" };
+
+    public ELParser(String expression) {
+        index = 0;
+        this.expression = expression;
+        expr = new ELNode.Nodes();
+    }
+
+    /**
+     * Parse an EL expression
+     * 
+     * @param expression
+     *            The input expression string of the form Char* ('${' Char*
+     *            '}')* Char*
+     * @return Parsed EL expression in ELNode.Nodes
+     */
+    public static ELNode.Nodes parse(String expression) {
+        ELParser parser = new ELParser(expression);
+        while (parser.hasNextChar()) {
+            String text = parser.skipUntilEL();
+            if (text.length() > 0) {
+                parser.expr.add(new ELNode.Text(text));
+            }
+            ELNode.Nodes elexpr = parser.parseEL();
+            if (!elexpr.isEmpty()) {
+                parser.expr.add(new ELNode.Root(elexpr, parser.type));
+            }
+        }
+        return parser.expr;
+    }
+
+    /**
+     * Parse an EL expression string '${...}'
+     * 
+     * @return An ELNode.Nodes representing the EL expression TODO: Currently
+     *         only parsed into functions and text strings. This should be
+     *         rewritten for a full parser.
+     */
+    private ELNode.Nodes parseEL() {
+
+        StringBuffer buf = new StringBuffer();
+        ELexpr = new ELNode.Nodes();
+        while (hasNext()) {
+            curToken = nextToken();
+            if (curToken instanceof Char) {
+                if (curToken.toChar() == '}') {
+                    break;
+                }
+                buf.append(curToken.toChar());
+            } else {
+                // Output whatever is in buffer
+                if (buf.length() > 0) {
+                    ELexpr.add(new ELNode.ELText(buf.toString()));
+                }
+                if (!parseFunction()) {
+                    ELexpr.add(new ELNode.ELText(curToken.toString()));
+                }
+            }
+        }
+        if (buf.length() > 0) {
+            ELexpr.add(new ELNode.ELText(buf.toString()));
+        }
+
+        return ELexpr;
+    }
+
+    /**
+     * Parse for a function FunctionInvokation ::= (identifier ':')? identifier
+     * '(' (Expression (,Expression)*)? ')' Note: currently we don't parse
+     * arguments
+     */
+    private boolean parseFunction() {
+        if (!(curToken instanceof Id) || isELReserved(curToken.toString())) {
+            return false;
+        }
+        String s1 = null; // Function prefix
+        String s2 = curToken.toString(); // Function name
+        int mark = getIndex();
+        if (hasNext()) {
+            Token t = nextToken();
+            if (t.toChar() == ':') {
+                if (hasNext()) {
+                    Token t2 = nextToken();
+                    if (t2 instanceof Id) {
+                        s1 = s2;
+                        s2 = t2.toString();
+                        if (hasNext()) {
+                            t = nextToken();
+                        }
+                    }
+                }
+            }
+            if (t.toChar() == '(') {
+                ELexpr.add(new ELNode.Function(s1, s2));
+                return true;
+            }
+        }
+        setIndex(mark);
+        return false;
+    }
+
+    /**
+     * Test if an id is a reserved word in EL
+     */
+    private boolean isELReserved(String id) {
+        int i = 0;
+        int j = reservedWords.length;
+        while (i < j) {
+            int k = (i + j) / 2;
+            int result = reservedWords[k].compareTo(id);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k + 1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Skip until an EL expression ('${' || '#{') is reached, allowing escape
+     * sequences '\\' and '\$' and '\#'.
+     * 
+     * @return The text string up to the EL expression
+     */
+    private String skipUntilEL() {
+        char prev = 0;
+        StringBuffer buf = new StringBuffer();
+        while (hasNextChar()) {
+            char ch = nextChar();
+            if (prev == '\\') {
+                prev = 0;
+                if (ch == '\\') {
+                    buf.append('\\');
+                    if (!escapeBS)
+                        prev = '\\';
+                } else if (ch == '$' || ch == '#') {
+                    buf.append(ch);
+                }
+                // else error!
+            } else if (prev == '$' || prev == '#') {
+                if (ch == '{') {
+                    this.type = prev;
+                    prev = 0;
+                    break;
+                }
+                buf.append(prev);
+                prev = 0;
+            }
+            if (ch == '\\' || ch == '$' || ch == '#') {
+                prev = ch;
+            } else {
+                buf.append(ch);
+            }
+        }
+        if (prev != 0) {
+            buf.append(prev);
+        }
+        return buf.toString();
+    }
+
+    /*
+     * @return true if there is something left in EL expression buffer other
+     * than white spaces.
+     */
+    private boolean hasNext() {
+        skipSpaces();
+        return hasNextChar();
+    }
+
+    /*
+     * @return The next token in the EL expression buffer.
+     */
+    private Token nextToken() {
+        skipSpaces();
+        if (hasNextChar()) {
+            char ch = nextChar();
+            if (Character.isJavaIdentifierStart(ch)) {
+                StringBuffer buf = new StringBuffer();
+                buf.append(ch);
+                while ((ch = peekChar()) != -1
+                        && Character.isJavaIdentifierPart(ch)) {
+                    buf.append(ch);
+                    nextChar();
+                }
+                return new Id(buf.toString());
+            }
+
+            if (ch == '\'' || ch == '"') {
+                return parseQuotedChars(ch);
+            } else {
+                // For now...
+                return new Char(ch);
+            }
+        }
+        return null;
+    }
+
+    /*
+     * Parse a string in single or double quotes, allowing for escape sequences
+     * '\\', and ('\"', or "\'")
+     */
+    private Token parseQuotedChars(char quote) {
+        StringBuffer buf = new StringBuffer();
+        buf.append(quote);
+        while (hasNextChar()) {
+            char ch = nextChar();
+            if (ch == '\\') {
+                ch = nextChar();
+                if (ch == '\\' || ch == quote) {
+                    buf.append(ch);
+                }
+                // else error!
+            } else if (ch == quote) {
+                buf.append(ch);
+                break;
+            } else {
+                buf.append(ch);
+            }
+        }
+        return new QuotedString(buf.toString());
+    }
+
+    /*
+     * A collection of low level parse methods dealing with character in the EL
+     * expression buffer.
+     */
+
+    private void skipSpaces() {
+        while (hasNextChar()) {
+            if (expression.charAt(index) > ' ')
+                break;
+            index++;
+        }
+    }
+
+    private boolean hasNextChar() {
+        return index < expression.length();
+    }
+
+    private char nextChar() {
+        if (index >= expression.length()) {
+            return (char) -1;
+        }
+        return expression.charAt(index++);
+    }
+
+    private char peekChar() {
+        if (index >= expression.length()) {
+            return (char) -1;
+        }
+        return expression.charAt(index);
+    }
+
+    private int getIndex() {
+        return index;
+    }
+
+    private void setIndex(int i) {
+        index = i;
+    }
+
+    /*
+     * Represents a token in EL expression string
+     */
+    private static class Token {
+
+        char toChar() {
+            return 0;
+        }
+
+        public String toString() {
+            return "";
+        }
+    }
+
+    /*
+     * Represents an ID token in EL
+     */
+    private static class Id extends Token {
+        String id;
+
+        Id(String id) {
+            this.id = id;
+        }
+
+        public String toString() {
+            return id;
+        }
+    }
+
+    /*
+     * Represents a character token in EL
+     */
+    private static class Char extends Token {
+
+        private char ch;
+
+        Char(char ch) {
+            this.ch = ch;
+        }
+
+        char toChar() {
+            return ch;
+        }
+
+        public String toString() {
+            return (new Character(ch)).toString();
+        }
+    }
+
+    /*
+     * Represents a quoted (single or double) string token in EL
+     */
+    private static class QuotedString extends Token {
+
+        private String value;
+
+        QuotedString(String v) {
+            this.value = v;
+        }
+
+        public String toString() {
+            return value;
+        }
+    }
+
+    public char getType() {
+        return type;
+    }
+}