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/07/31 20:12:51 UTC

svn commit: r799681 [3/24] - in /struts/sandbox/trunk/struts2-jsp-plugin: ./ src/main/java/org/apache/struts/ src/main/java/org/apache/struts2/ src/main/java/org/apache/struts2/compiler/ src/main/java/org/apache/struts2/jasper/ src/main/java/org/apache...

Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Compiler.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,498 @@
+/*
+ * 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.struts2.jasper.compiler;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.Options;
+import org.apache.struts2.jasper.servlet.JspServletWrapper;
+import org.apache.struts2.jasper.JspCompilationContext;
+
+/**
+ * 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.commons.logging.Log log=
+        org.apache.commons.logging.LogFactory.getLog( Compiler.class );
+
+    // ----------------------------------------------------------------- Static
+
+
+    // Some javac are not thread safe; use a lock to serialize compilation, 
+    static Object javacLock = new Object();
+
+
+    // ----------------------------------------------------- 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.
+         */
+        pageInfo.setELIgnored(JspUtil.booleanValue(
+                                            jspProperty.isELIgnored()));
+        pageInfo.setScriptingInvalid(JspUtil.booleanValue(
+                                            jspProperty.isScriptingInvalid()));
+        if (jspProperty.getIncludePrelude() != null) {
+            pageInfo.setIncludePrelude(jspProperty.getIncludePrelude());
+        }
+        if (jspProperty.getIncludeCoda() != null) {
+            pageInfo.setIncludeCoda(jspProperty.getIncludeCoda());
+        }
+
+        String javaFileName = ctxt.getServletJavaFileName();
+        ServletWriter writer = null;
+
+        try {
+            // Setup the ServletWriter
+            String javaEncoding = ctxt.getOptions().getJavaEncoding();
+            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
+            OutputStreamWriter osw = null; 
+
+            try {
+                osw = new OutputStreamWriter(
+                            byteArrayOutputStream, javaEncoding);
+            } catch (UnsupportedEncodingException ex) {
+                errDispatcher.jspError("jsp.error.needAlternateJavaEncoding",
+                                       javaEncoding);
+            }
+
+            writer = new ServletWriter(new PrintWriter(osw));
+            ctxt.setWriter(writer);
+
+            // Reset the temporary variable counter for the generator.
+            JspUtil.resetTemporaryVariableName();
+
+            // Parse the file
+            ParserController parserCtl = new ParserController(ctxt, this);
+            pageNodes = parserCtl.parse(ctxt.getJspFile());
+
+            if (ctxt.isPrototypeMode()) {
+                // generate prototype .java file for the tag file
+                Generator.generate(writer, this, pageNodes);
+                writer.close();
+                writer = null;
+                return null;
+            }
+
+            // Validate and process attributes
+            Validator.validate(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
+            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);
+            ctxt.setSourceCode(byteArrayOutputStream.toString());
+            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;
+    }
+
+    /** 
+     * 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);
+            }
+        } finally {
+            if (tfp != null) {
+                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();
+            jspRealLastModified = uc.getLastModified();
+            uc.getInputStream().close();
+        } catch (Exception e) {
+            e.printStackTrace();
+            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 includeUconn = includeUrl.openConnection();
+                long includeLastModified = includeUconn.getLastModified();
+                includeUconn.getInputStream().close();
+
+                if (includeLastModified > targetLastModified) {
+                    return true;
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                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/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/DefaultErrorHandler.java Fri Jul 31 18:12:48 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.struts2.jasper.compiler;
+
+import org.apache.struts2.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++) {
+            buf.append("\n");
+            if (details[i].getJspBeginLineNumber() >= 0) {
+                args = new Object[] {
+                        new Integer(details[i].getJspBeginLineNumber()), 
+                        details[i].getJspFileName() };
+                buf.append("\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/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Dumper.java Fri Jul 31 18:12:48 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.struts2.jasper.compiler;
+
+import org.xml.sax.Attributes;
+import org.apache.struts2.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/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELFunctionMapper.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,280 @@
+/*
+ * 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.struts2.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import org.apache.struts2.jasper.JasperException;
+
+/**
+ * This class generates functions mappers for the EL expressions in the page.
+ * Instead of a global mapper, a mapper is used for each 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 gMap = new HashMap();
+
+        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 funcs = new ArrayList();
+                HashMap keyMap = new HashMap();
+                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.struts2.jasper.runtime.ProtectedFunctionMapper " + decName + ";\n");
+
+            ds.append("  " + decName + "= ");
+            ds.append("org.apache.struts2.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/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELNode.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,248 @@
+/*
+ * 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.struts2.jasper.compiler;
+
+import java.util.*;
+import javax.servlet.jsp.tagext.FunctionInfo;
+import org.apache.struts2.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;
+
+	Root(ELNode.Nodes expr) {
+	    this.expr = expr;
+	}
+
+	public void accept(Visitor v) throws JasperException {
+	    v.visit(this);
+	}
+
+	public ELNode.Nodes getExpression() {
+	    return expr;
+	}
+    }
+
+    /**
+     * 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 list;
+
+	public Nodes() {
+	    list = new ArrayList();
+	}
+
+	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 iter = list.iterator();
+	    while (iter.hasNext()) {
+		ELNode n = (ELNode) iter.next();
+		n.accept(v);
+	    }
+	}
+
+	public Iterator iterator() {
+	    return list.iterator();
+	}
+
+	public boolean isEmpty() {
+	    return list.size() == 0;
+	}
+
+	/**
+	 * @return true if the expression contains a ${...}
+	 */
+	public boolean containsEL() {
+	    Iterator iter = list.iterator();
+	    while (iter.hasNext()) {
+		ELNode n = (ELNode) 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/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ELParser.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,369 @@
+/*
+ * 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.struts2.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 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));
+            }
+        }
+        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 '\$'.
+     * @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 == '$') {
+                    buf.append('$');
+                }
+                // else error!
+            } else if (prev == '$') {
+                if (ch == '{') {
+                    prev = 0;
+                    break;
+                } 
+                buf.append('$');
+                buf.append(ch);
+                prev = 0;
+            } else if (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;
+        }
+    }
+}
+

Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorDispatcher.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,614 @@
+/*
+ * 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.struts2.jasper.compiler;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Vector;
+import java.net.MalformedURLException;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.xml.sax.SAXException;
+
+/**
+ * Class responsible for dispatching JSP parse and javac compilation errors
+ * to the configured error handler.
+ *
+ * This class is also responsible for localizing any error codes before they
+ * are passed on to the configured error handler.
+ * 
+ * In the case of a Java compilation error, the compiler error message is
+ * parsed into an array of JavacErrorDetail instances, which is passed on to 
+ * the configured error handler.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public class ErrorDispatcher {
+
+    // Custom error handler
+    private ErrorHandler errHandler;
+
+    // Indicates whether the compilation was initiated by JspServlet or JspC
+    private boolean jspcMode = false;
+
+
+    /*
+     * Constructor.
+     *
+     * @param jspcMode true if compilation has been initiated by JspC, false
+     * otherwise
+     */
+    public ErrorDispatcher(boolean jspcMode) {
+	// XXX check web.xml for custom error handler
+	errHandler = new DefaultErrorHandler();
+        this.jspcMode = jspcMode;
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     */
+    public void jspError(String errCode) throws JasperException {
+	dispatch(null, errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     */
+    public void jspError(Mark where, String errCode) throws JasperException {
+	dispatch(where, errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     */
+    public void jspError(Node n, String errCode) throws JasperException {
+	dispatch(n.getStart(), errCode, null, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg) throws JasperException {
+	dispatch(null, errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(Mark where, String errCode, String arg)
+	        throws JasperException {
+	dispatch(where, errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     */
+    public void jspError(Node n, String errCode, String arg)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg1, String arg2)
+	        throws JasperException {
+	dispatch(null, errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+    public void jspError(String errCode, String arg1, String arg2, String arg3)
+	        throws JasperException {
+	dispatch(null, errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+    public void jspError(Mark where, String errCode, String arg1, String arg2)
+	        throws JasperException {
+	dispatch(where, errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+
+    public void jspError(Mark where, String errCode, String arg1, String arg2,
+                         String arg3)
+                throws JasperException {
+        dispatch(where, errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     */
+
+    public void jspError(Node n, String errCode, String arg1, String arg2)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg1, arg2}, null);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     */
+
+    public void jspError(Node n, String errCode, String arg1, String arg2,
+                         String arg3)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg1, arg2, arg3}, null);
+    }
+
+    /*
+     * Dispatches the given parsing exception to the configured error handler.
+     *
+     * @param e Parsing exception
+     */
+    public void jspError(Exception e) throws JasperException {
+	dispatch(null, null, null, e);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     * @param e Parsing exception
+     */
+    public void jspError(String errCode, String arg, Exception e)
+	        throws JasperException {
+	dispatch(null, errCode, new Object[] {arg}, e);
+    }
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param n Node that caused the error
+     * @param errCode Error code
+     * @param arg Argument for parametric replacement
+     * @param e Parsing exception
+     */
+    public void jspError(Node n, String errCode, String arg, Exception e)
+	        throws JasperException {
+	dispatch(n.getStart(), errCode, new Object[] {arg}, e);
+    }
+
+    /**
+     * Parses the given error message into an array of javac compilation error
+     * messages (one per javac compilation error line number).
+     *
+     * @param errMsg Error message
+     * @param fname Name of Java source file whose compilation failed
+     * @param page Node representation of JSP page from which the Java source
+     * file was generated
+     *
+     * @return Array of javac compilation errors, or null if the given error
+     * message does not contain any compilation error line numbers
+     */
+    public static JavacErrorDetail[] parseJavacErrors(String errMsg,
+                                                      String fname,
+                                                      Node.Nodes page)
+            throws JasperException, IOException {
+
+	return parseJavacMessage(errMsg, fname, page);
+    }
+
+    /*
+     * Dispatches the given javac compilation errors to the configured error
+     * handler.
+     *
+     * @param javacErrors Array of javac compilation errors
+     */
+    public void javacError(JavacErrorDetail[] javacErrors)
+            throws JasperException {
+
+        errHandler.javacError(javacErrors);
+    }
+
+
+    /*
+     * Dispatches the given compilation error report and exception to the
+     * configured error handler.
+     *
+     * @param errorReport Compilation error report
+     * @param e Compilation exception
+     */
+    public void javacError(String errorReport, Exception e)
+                throws JasperException {
+
+        errHandler.javacError(errorReport, e);
+    }
+
+
+    //*********************************************************************
+    // Private utility methods
+
+    /*
+     * Dispatches the given JSP parse error to the configured error handler.
+     *
+     * The given error code is localized. If it is not found in the
+     * resource bundle for localized error messages, it is used as the error
+     * message.
+     *
+     * @param where Error location
+     * @param errCode Error code
+     * @param args Arguments for parametric replacement
+     * @param e Parsing exception
+     */
+    private void dispatch(Mark where, String errCode, Object[] args,
+			  Exception e) throws JasperException {
+	String file = null;
+	String errMsg = null;
+	int line = -1;
+	int column = -1;
+	boolean hasLocation = false;
+
+	// Localize
+	if (errCode != null) {
+	    errMsg = Localizer.getMessage(errCode, args);
+	} else if (e != null) {
+	    // give a hint about what's wrong
+	    errMsg = e.getMessage();
+	}
+
+	// Get error location
+	if (where != null) {
+            if (jspcMode) {
+                // Get the full URL of the resource that caused the error
+                try {
+                    file = where.getURL().toString();
+                } catch (MalformedURLException me) {
+                    // Fallback to using context-relative path
+                    file = where.getFile();
+                }
+            } else {
+                // Get the context-relative resource path, so as to not
+                // disclose any local filesystem details
+                file = where.getFile();
+            }
+	    line = where.getLineNumber();
+	    column = where.getColumnNumber();
+	    hasLocation = true;
+	}
+
+	// Get nested exception
+	Exception nestedEx = e;
+	if ((e instanceof SAXException)
+	        && (((SAXException) e).getException() != null)) {
+	    nestedEx = ((SAXException) e).getException();
+	}
+
+	if (hasLocation) {
+	    errHandler.jspError(file, line, column, errMsg, nestedEx);
+	} else {
+	    errHandler.jspError(errMsg, nestedEx);
+	}
+    }
+
+    /*
+     * Parses the given Java compilation error message, which may contain one
+     * or more compilation errors, into an array of JavacErrorDetail instances.
+     *
+     * Each JavacErrorDetail instance contains the information about a single
+     * compilation error.
+     *
+     * @param errMsg Compilation error message that was generated by the
+     * javac compiler
+     * @param fname Name of Java source file whose compilation failed
+     * @param page Node representation of JSP page from which the Java source
+     * file was generated
+     *
+     * @return Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    private static JavacErrorDetail[] parseJavacMessage(
+                                String errMsg, String fname, Node.Nodes page)
+	        throws IOException, JasperException {
+
+        Vector errVec = new Vector();
+        StringBuffer errMsgBuf = null;
+        int lineNum = -1;
+        JavacErrorDetail javacError = null;
+        
+        BufferedReader reader = new BufferedReader(new StringReader(errMsg));
+        
+        /*
+         * Parse compilation errors. Each compilation error consists of a file
+         * path and error line number, followed by a number of lines describing
+         * the error.
+         */
+        String line = null;
+        while ((line = reader.readLine()) != null) {
+            
+            /*
+             * Error line number is delimited by set of colons.
+             * Ignore colon following drive letter on Windows (fromIndex = 2).
+             * XXX Handle deprecation warnings that don't have line info
+             */
+            int beginColon = line.indexOf(':', 2); 
+            int endColon = line.indexOf(':', beginColon + 1);
+            if ((beginColon >= 0) && (endColon >= 0)) {
+                if (javacError != null) {
+                    // add previous error to error vector
+                    errVec.add(javacError);
+                }
+                
+                String lineNumStr = line.substring(beginColon + 1, endColon);
+                try {
+                    lineNum = Integer.parseInt(lineNumStr);
+                } catch (NumberFormatException e) {
+                    // XXX
+                }
+                
+                errMsgBuf = new StringBuffer();
+                
+                javacError = createJavacError(fname, page, errMsgBuf, lineNum);
+            }
+            
+            // Ignore messages preceding first error
+            if (errMsgBuf != null) {
+                errMsgBuf.append(line);
+                errMsgBuf.append("\n");
+            }
+        }
+        
+        // Add last error to error vector
+        if (javacError != null) {
+            errVec.add(javacError);
+        } 
+        
+        reader.close();
+        
+        JavacErrorDetail[] errDetails = null;
+        if (errVec.size() > 0) {
+            errDetails = new JavacErrorDetail[errVec.size()];
+            errVec.copyInto(errDetails);
+        }
+        
+        return errDetails;
+    }
+
+
+    /**
+     * @param fname
+     * @param page
+     * @param errMsgBuf
+     * @param lineNum
+     * @return JavacErrorDetail The error details
+     * @throws JasperException
+     */
+    public static JavacErrorDetail createJavacError(String fname,
+            Node.Nodes page, 
+            StringBuffer errMsgBuf, int lineNum) throws JasperException {
+        return createJavacError(fname, page, errMsgBuf, lineNum, null);
+    }
+    /**
+     * @param fname
+     * @param page
+     * @param errMsgBuf
+     * @param lineNum
+     * @param ctxt
+     * @return JavacErrorDetail The error details
+     * @throws JasperException
+     */
+    public static JavacErrorDetail createJavacError(String fname,
+            Node.Nodes page, 
+            StringBuffer errMsgBuf, int lineNum, JspCompilationContext ctxt)
+    throws JasperException {
+        JavacErrorDetail javacError;
+        // Attempt to map javac error line number to line in JSP page
+        ErrorVisitor errVisitor = new ErrorVisitor(lineNum);
+        page.visit(errVisitor);
+        Node errNode = errVisitor.getJspSourceNode();
+        if ((errNode != null) && (errNode.getStart() != null)) {
+            // If this is a scriplet node then there is a one to one mapping
+            // between JSP lines and Java lines
+            if (errVisitor.getJspSourceNode() instanceof Node.Scriptlet) {
+                javacError = new JavacErrorDetail(
+                        fname,
+                        lineNum,
+                        errNode.getStart().getFile(),
+                        errNode.getStart().getLineNumber() + lineNum -
+                            errVisitor.getJspSourceNode().getBeginJavaLine(),
+                        errMsgBuf,
+                        ctxt);
+            } else {
+                javacError = new JavacErrorDetail(
+                        fname,
+                        lineNum,
+                        errNode.getStart().getFile(),
+                        errNode.getStart().getLineNumber(),
+                        errMsgBuf,
+                        ctxt);
+            }
+        } else {
+            /*
+             * javac error line number cannot be mapped to JSP page
+             * line number. For example, this is the case if a 
+             * scriptlet is missing a closing brace, which causes
+             * havoc with the try-catch-finally block that the code
+             * generator places around all generated code: As a result
+             * of this, the javac error line numbers will be outside
+             * the range of begin and end java line numbers that were
+             * generated for the scriptlet, and therefore cannot be
+             * mapped to the start line number of the scriptlet in the
+             * JSP page.
+             * Include just the javac error info in the error detail.
+             */
+            javacError = new JavacErrorDetail(
+                    fname,
+                    lineNum,
+                    errMsgBuf);
+        }
+        return javacError;
+    }
+
+
+    /*
+     * Visitor responsible for mapping a line number in the generated servlet
+     * source code to the corresponding JSP node.
+     */
+    static class ErrorVisitor extends Node.Visitor {
+
+	// Java source line number to be mapped
+	private int lineNum;
+
+	/*
+	 * JSP node whose Java source code range in the generated servlet
+	 * contains the Java source line number to be mapped
+	 */
+	Node found;
+
+	/*
+	 * Constructor.
+	 *
+	 * @param lineNum Source line number in the generated servlet code
+	 */
+	public ErrorVisitor(int lineNum) {
+	    this.lineNum = lineNum;
+	}
+
+	public void doVisit(Node n) throws JasperException {
+	    if ((lineNum >= n.getBeginJavaLine())
+		    && (lineNum < n.getEndJavaLine())) {
+		found = n;
+	    }
+        }
+
+	/*
+	 * Gets the JSP node to which the source line number in the generated
+	 * servlet code was mapped.
+	 *
+	 * @return JSP node to which the source line number in the generated
+	 * servlet code was mapped
+	 */
+	public Node getJspSourceNode() {
+	    return found;
+	}
+    }
+}

Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/ErrorHandler.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,73 @@
+/*
+ * 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.struts2.jasper.compiler;
+
+import org.apache.struts2.jasper.JasperException;
+
+/**
+ * Interface for handling JSP parse and javac compilation errors.
+ * 
+ * An implementation of this interface may be registered with the
+ * ErrorDispatcher by setting the XXX initialization parameter in the JSP
+ * page compiler and execution servlet in Catalina's web.xml file to the
+ * implementation's fully qualified class name.
+ *
+ * @author Jan Luehe
+ * @author Kin-man Chung
+ */
+public interface 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 msg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String fname, int line, int column, String msg,
+			 Exception exception) throws JasperException;
+
+    /**
+     * Processes the given JSP parse error.
+     *
+     * @param msg Parse error message
+     * @param exception Parse exception
+     */
+    public void jspError(String msg, Exception exception)
+	throws JasperException;
+
+    /**
+     * Processes the given javac compilation errors.
+     *
+     * @param details Array of JavacErrorDetail instances corresponding to the
+     * compilation errors
+     */
+    public void javacError(JavacErrorDetail[] details)
+	throws JasperException;
+
+    /**
+     * 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;
+}