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 03:55:35 UTC

svn commit: r819444 [16/27] - in /struts/struts2/trunk/plugins/embeddedjsp: ./ src/main/java/org/apache/struts2/el/ src/main/java/org/apache/struts2/el/lang/ src/main/java/org/apache/struts2/el/parser/ src/main/java/org/apache/struts2/el/util/ src/main...

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ParserController.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,635 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.JarURLConnection;
+import java.net.URL;
+import java.util.Stack;
+import java.util.jar.JarFile;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.xmlparser.XMLEncodingDetector;
+import org.xml.sax.Attributes;
+
+/**
+ * Controller for the parsing of a JSP page.
+ * <p>
+ * The same ParserController instance is used for a JSP page and any JSP
+ * segments included by it (via an include directive), where each segment may
+ * be provided in standard or XML syntax. This class selects and invokes the
+ * appropriate parser for the JSP page and its included segments.
+ *
+ * @author Pierre Delisle
+ * @author Jan Luehe
+ */
+class ParserController implements TagConstants {
+
+    private static final String CHARSET = "charset=";
+
+    private JspCompilationContext ctxt;
+    private Compiler compiler;
+    private ErrorDispatcher err;
+
+    /*
+     * Indicates the syntax (XML or standard) of the file being processed
+     */
+    private boolean isXml;
+
+    /*
+     * A stack to keep track of the 'current base directory'
+     * for include directives that refer to relative paths.
+     */
+    private Stack baseDirStack = new Stack();
+
+    private boolean isEncodingSpecifiedInProlog;
+    private boolean isBomPresent;
+    private int skip;
+
+    private String sourceEnc;
+
+    private boolean isDefaultPageEncoding;
+    private boolean isTagFile;
+    private boolean directiveOnly;
+
+    /*
+     * Constructor
+     */
+    public ParserController(JspCompilationContext ctxt, Compiler compiler) {
+        this.ctxt = ctxt; 
+        this.compiler = compiler;
+        this.err = compiler.getErrorDispatcher();
+    }
+
+    public JspCompilationContext getJspCompilationContext () {
+        return ctxt;
+    }
+
+    public Compiler getCompiler () {
+        return compiler;
+    }
+
+    /**
+     * Parses a JSP page or tag file. This is invoked by the compiler.
+     *
+     * @param inFileName The path to the JSP page or tag file to be parsed.
+     */
+    public Node.Nodes parse(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        // If we're parsing a packaged tag file or a resource included by it
+        // (using an include directive), ctxt.getTagFileJar() returns the 
+        // JAR file from which to read the tag file or included resource,
+        // respectively.
+        isTagFile = ctxt.isTagFile();
+        directiveOnly = false;
+        return doParse(inFileName, null, ctxt.getTagFileJarUrl());
+    }
+
+    /**
+     * Parses the directives of a JSP page or tag file. This is invoked by the
+     * compiler.
+     *
+     * @param inFileName The path to the JSP page or tag file to be parsed.
+     */
+    public Node.Nodes parseDirectives(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        // If we're parsing a packaged tag file or a resource included by it
+        // (using an include directive), ctxt.getTagFileJar() returns the 
+        // JAR file from which to read the tag file or included resource,
+        // respectively.
+        isTagFile = ctxt.isTagFile();
+        directiveOnly = true;
+        return doParse(inFileName, null, ctxt.getTagFileJarUrl());
+    }
+
+
+    /**
+     * Processes an include directive with the given path.
+     *
+     * @param inFileName The path to the resource to be included.
+     * @param parent The parent node of the include directive.
+     * @param jarFile The JAR file from which to read the included resource,
+     * or null of the included resource is to be read from the filesystem
+     */
+    public Node.Nodes parse(String inFileName, Node parent,
+            URL jarFileUrl)
+    throws FileNotFoundException, JasperException, IOException {
+        // For files that are statically included, isTagfile and directiveOnly
+        // remain unchanged.
+        return doParse(inFileName, parent, jarFileUrl);
+    }
+
+    /**
+     * Extracts tag file directive information from the tag file with the
+     * given name.
+     *
+     * This is invoked by the compiler 
+     *
+     * @param inFileName The name of the tag file to be parsed.
+     * @deprecated Use {@link #parseTagFileDirectives(String, URL)}
+     *             See https://issues.apache.org/bugzilla/show_bug.cgi?id=46471
+     */
+    public Node.Nodes parseTagFileDirectives(String inFileName)
+    throws FileNotFoundException, JasperException, IOException {
+        return parseTagFileDirectives(
+                inFileName, ctxt.getTagFileJarUrl(inFileName));
+    }
+
+    /**
+     * Extracts tag file directive information from the given tag file.
+     *
+     * This is invoked by the compiler 
+     *
+     * @param inFileName    The name of the tag file to be parsed.
+     * @param tagFileJarUrl The location of the tag file.
+     */
+    public Node.Nodes parseTagFileDirectives(String inFileName,
+            URL tagFileJarUrl)
+            throws FileNotFoundException, JasperException, IOException {
+        boolean isTagFileSave = isTagFile;
+        boolean directiveOnlySave = directiveOnly;
+        isTagFile = true;
+        directiveOnly = true;
+        Node.Nodes page = doParse(inFileName, null, tagFileJarUrl);
+        directiveOnly = directiveOnlySave;
+        isTagFile = isTagFileSave;
+        return page;
+    }
+
+    /**
+     * Parses the JSP page or tag file with the given path name.
+     *
+     * @param inFileName The name of the JSP page or tag file to be parsed.
+     * @param parent The parent node (non-null when processing an include
+     * directive)
+     * @param isTagFile true if file to be parsed is tag file, and false if it
+     * is a regular JSP page
+     * @param directivesOnly true if the file to be parsed is a tag file and
+     * we are only interested in the directives needed for constructing a
+     * TagFileInfo.
+     * @param jarFile The JAR file from which to read the JSP page or tag file,
+     * or null if the JSP page or tag file is to be read from the filesystem
+     */
+    private Node.Nodes doParse(String inFileName,
+            Node parent,
+            URL jarFileUrl)
+    throws FileNotFoundException, JasperException, IOException {
+
+        Node.Nodes parsedPage = null;
+        isEncodingSpecifiedInProlog = false;
+        isBomPresent = false;
+        isDefaultPageEncoding = false;
+
+        JarFile jarFile = getJarFile(jarFileUrl);
+        String absFileName = resolveFileName(inFileName);
+        String jspConfigPageEnc = getJspConfigPageEncoding(absFileName);
+
+        // Figure out what type of JSP document and encoding type we are
+        // dealing with
+        determineSyntaxAndEncoding(absFileName, jarFile, jspConfigPageEnc);
+
+        if (parent != null) {
+            // Included resource, add to dependent list
+            if (jarFile == null) {
+                compiler.getPageInfo().addDependant(absFileName);
+            } else {
+                compiler.getPageInfo().addDependant(
+                        jarFileUrl.toExternalForm() + absFileName.substring(1));
+            }
+        }
+
+        if ((isXml && isEncodingSpecifiedInProlog) || isBomPresent) {
+            /*
+             * Make sure the encoding explicitly specified in the XML
+             * prolog (if any) matches that in the JSP config element
+             * (if any), treating "UTF-16", "UTF-16BE", and "UTF-16LE" as
+             * identical.
+             */
+            if (jspConfigPageEnc != null && !jspConfigPageEnc.equals(sourceEnc)
+                    && (!jspConfigPageEnc.startsWith("UTF-16")
+                            || !sourceEnc.startsWith("UTF-16"))) {
+                err.jspError("jsp.error.prolog_config_encoding_mismatch",
+                        sourceEnc, jspConfigPageEnc);
+            }
+        }
+
+        // Dispatch to the appropriate parser
+        if (isXml) {
+            // JSP document (XML syntax)
+            // InputStream for jspx page is created and properly closed in
+            // JspDocumentParser.
+            parsedPage = JspDocumentParser.parse(this, absFileName,
+                    jarFile, parent,
+                    isTagFile, directiveOnly,
+                    sourceEnc,
+                    jspConfigPageEnc,
+                    isEncodingSpecifiedInProlog,
+                    isBomPresent);
+        } else {
+            // Standard syntax
+            InputStreamReader inStreamReader = null;
+            try {
+                inStreamReader = JspUtil.getReader(absFileName, sourceEnc,
+                        jarFile, ctxt, err, skip);
+                JspReader jspReader = new JspReader(ctxt, absFileName,
+                        sourceEnc, inStreamReader,
+                        err);
+                parsedPage = Parser.parse(this, jspReader, parent, isTagFile,
+                        directiveOnly, jarFileUrl,
+                        sourceEnc, jspConfigPageEnc,
+                        isDefaultPageEncoding, isBomPresent);
+            } finally {
+                if (inStreamReader != null) {
+                    try {
+                        inStreamReader.close();
+                    } catch (Exception any) {
+                    }
+                }
+            }
+        }
+
+        if (jarFile != null) {
+            try {
+                jarFile.close();
+            } catch (Throwable t) {}
+        }
+
+        baseDirStack.pop();
+
+        return parsedPage;
+    }
+
+    /*
+     * Checks to see if the given URI is matched by a URL pattern specified in
+     * a jsp-property-group in web.xml, and if so, returns the value of the
+     * <page-encoding> element.
+     *
+     * @param absFileName The URI to match
+     *
+     * @return The value of the <page-encoding> attribute of the 
+     * jsp-property-group with matching URL pattern
+     */
+    private String getJspConfigPageEncoding(String absFileName)
+    throws JasperException {
+
+        JspConfig jspConfig = ctxt.getOptions().getJspConfig();
+        JspConfig.JspProperty jspProperty
+            = jspConfig.findJspProperty(absFileName);
+        return jspProperty.getPageEncoding();
+    }
+
+    /**
+     * Determines the syntax (standard or XML) and page encoding properties
+     * for the given file, and stores them in the 'isXml' and 'sourceEnc'
+     * instance variables, respectively.
+     */
+    private void determineSyntaxAndEncoding(String absFileName,
+            JarFile jarFile,
+            String jspConfigPageEnc)
+    throws JasperException, IOException {
+
+        isXml = false;
+
+        /*
+         * 'true' if the syntax (XML or standard) of the file is given
+         * from external information: either via a JSP configuration element,
+         * the ".jspx" suffix, or the enclosing file (for included resources)
+         */
+        boolean isExternal = false;
+
+        /*
+         * Indicates whether we need to revert from temporary usage of
+         * "ISO-8859-1" back to "UTF-8"
+         */
+        boolean revert = false;
+
+        JspConfig jspConfig = ctxt.getOptions().getJspConfig();
+        JspConfig.JspProperty jspProperty = jspConfig.findJspProperty(
+                absFileName);
+        if (jspProperty.isXml() != null) {
+            // If <is-xml> is specified in a <jsp-property-group>, it is used.
+            isXml = JspUtil.booleanValue(jspProperty.isXml());
+            isExternal = true;
+        } else if (absFileName.endsWith(".jspx")
+                || absFileName.endsWith(".tagx")) {
+            isXml = true;
+            isExternal = true;
+        }
+
+        if (isExternal && !isXml) {
+            // JSP (standard) syntax. Use encoding specified in jsp-config
+            // if provided.
+            sourceEnc = jspConfigPageEnc;
+            if (sourceEnc != null) {
+                return;
+            }
+            // We don't know the encoding, so use BOM to determine it
+            sourceEnc = "ISO-8859-1";
+        } else {
+            // XML syntax or unknown, (auto)detect encoding ...
+            Object[] ret = XMLEncodingDetector.getEncoding(absFileName,
+                    jarFile, ctxt, err);
+            sourceEnc = (String) ret[0];
+            if (((Boolean) ret[1]).booleanValue()) {
+                isEncodingSpecifiedInProlog = true;
+            }
+            if (((Boolean) ret[2]).booleanValue()) {
+                isBomPresent = true;
+            }
+            skip = ((Integer) ret[3]).intValue();
+
+            if (!isXml && sourceEnc.equals("UTF-8")) {
+                /*
+                 * We don't know if we're dealing with XML or standard syntax.
+                 * Therefore, we need to check to see if the page contains
+                 * a <jsp:root> element.
+                 *
+                 * We need to be careful, because the page may be encoded in
+                 * ISO-8859-1 (or something entirely different), and may
+                 * contain byte sequences that will cause a UTF-8 converter to
+                 * throw exceptions. 
+                 *
+                 * It is safe to use a source encoding of ISO-8859-1 in this
+                 * case, as there are no invalid byte sequences in ISO-8859-1,
+                 * and the byte/character sequences we're looking for (i.e.,
+                 * <jsp:root>) are identical in either encoding (both UTF-8
+                 * and ISO-8859-1 are extensions of ASCII).
+                 */
+                sourceEnc = "ISO-8859-1";
+                revert = true;
+            }
+        }
+
+        if (isXml) {
+            // (This implies 'isExternal' is TRUE.)
+            // We know we're dealing with a JSP document (via JSP config or
+            // ".jspx" suffix), so we're done.
+            return;
+        }
+
+        /*
+         * At this point, 'isExternal' or 'isXml' is FALSE.
+         * Search for jsp:root action, in order to determine if we're dealing 
+         * with XML or standard syntax (unless we already know what we're 
+         * dealing with, i.e., when 'isExternal' is TRUE and 'isXml' is FALSE).
+         * No check for XML prolog, since nothing prevents a page from
+         * outputting XML and still using JSP syntax (in this case, the 
+         * XML prolog is treated as template text).
+         */
+        JspReader jspReader = null;
+        try {
+            jspReader = new JspReader(ctxt, absFileName, sourceEnc, jarFile,
+                    err);
+        } catch (FileNotFoundException ex) {
+            throw new JasperException(ex);
+        }
+        jspReader.setSingleFile(true);
+        Mark startMark = jspReader.mark();
+        if (!isExternal) {
+            jspReader.reset(startMark);
+            if (hasJspRoot(jspReader)) {
+                if (revert) {
+                    sourceEnc = "UTF-8";
+                }
+                isXml = true;
+                return;
+            } else {
+                if (revert && isBomPresent) {
+                    sourceEnc = "UTF-8";
+                }
+                isXml = false;
+            }
+        }
+
+        /*
+         * At this point, we know we're dealing with JSP syntax.
+         * If an XML prolog is provided, it's treated as template text.
+         * Determine the page encoding from the page directive, unless it's
+         * specified via JSP config.
+         */
+        if (!isBomPresent) {
+            sourceEnc = jspConfigPageEnc;
+            if (sourceEnc == null) {
+                sourceEnc = getPageEncodingForJspSyntax(jspReader, startMark);
+                if (sourceEnc == null) {
+                    // Default to "ISO-8859-1" per JSP spec
+                    sourceEnc = "ISO-8859-1";
+                    isDefaultPageEncoding = true;
+                }
+            }
+        }
+        
+    }
+
+    /*
+     * Determines page source encoding for page or tag file in JSP syntax,
+     * by reading (in this order) the value of the 'pageEncoding' page
+     * directive attribute, or the charset value of the 'contentType' page
+     * directive attribute.
+     *
+     * @return The page encoding, or null if not found
+     */
+    private String getPageEncodingForJspSyntax(JspReader jspReader,
+            Mark startMark)
+    throws JasperException {
+
+        String encoding = null;
+        String saveEncoding = null;
+
+        jspReader.reset(startMark);
+
+        /*
+         * Determine page encoding from directive of the form <%@ page %>,
+         * <%@ tag %>, <jsp:directive.page > or <jsp:directive.tag >.
+         */
+        while (true) {
+            if (jspReader.skipUntil("<") == null) {
+                break;
+            }
+            // If this is a comment, skip until its end
+            if (jspReader.matches("%--")) {
+                if (jspReader.skipUntil("--%>") == null) {
+                    // error will be caught in Parser
+                    break;
+                }
+                continue;
+            }
+            boolean isDirective = jspReader.matches("%@");
+            if (isDirective) {
+                jspReader.skipSpaces();
+            }
+            else {
+                isDirective = jspReader.matches("jsp:directive.");
+            }
+            if (!isDirective) {
+                continue;
+            }
+
+            // compare for "tag ", so we don't match "taglib"
+            if (jspReader.matches("tag ") || jspReader.matches("page")) {
+
+                jspReader.skipSpaces();
+                Attributes attrs = Parser.parseAttributes(this, jspReader);
+                encoding = getPageEncodingFromDirective(attrs, "pageEncoding");
+                if (encoding != null) {
+                    break;
+                }
+                encoding = getPageEncodingFromDirective(attrs, "contentType");
+                if (encoding != null) {
+                    saveEncoding = encoding;
+                }
+            }
+        }
+
+        if (encoding == null) {
+            encoding = saveEncoding;
+        }
+
+        return encoding;
+    }
+
+    /*
+     * Scans the given attributes for the attribute with the given name,
+     * which is either 'pageEncoding' or 'contentType', and returns the
+     * specified page encoding.
+     *
+     * In the case of 'contentType', the page encoding is taken from the
+     * content type's 'charset' component.
+     *
+     * @param attrs The page directive attributes
+     * @param attrName The name of the attribute to search for (either
+     * 'pageEncoding' or 'contentType')
+     *
+     * @return The page encoding, or null
+     */
+    private String getPageEncodingFromDirective(Attributes attrs,
+            String attrName) {
+        String value = attrs.getValue(attrName);
+        if (attrName.equals("pageEncoding")) {
+            return value;
+        }
+
+        // attrName = contentType
+        String contentType = value;
+        String encoding = null;
+        if (contentType != null) {
+            int loc = contentType.indexOf(CHARSET);
+            if (loc != -1) {
+                encoding = contentType.substring(loc + CHARSET.length());
+            }
+        }
+
+        return encoding;
+    }
+
+    /*
+     * Resolve the name of the file and update baseDirStack() to keep track of
+     * the current base directory for each included file.
+     * The 'root' file is always an 'absolute' path, so no need to put an
+     * initial value in the baseDirStack.
+     */
+    private String resolveFileName(String inFileName) {
+        String fileName = inFileName.replace('\\', '/');
+        String baseDir =
+                fileName.substring(0, fileName.lastIndexOf("/") + 1);
+        baseDirStack.push(baseDir);
+        return fileName;
+    }
+
+    /*
+     * Checks to see if the given page contains, as its first element, a <root>
+     * element whose prefix is bound to the JSP namespace, as in:
+     *
+     * <wombat:root xmlns:wombat="http://java.sun.com/JSP/Page" version="1.2">
+     *   ...
+     * </wombat:root>
+     *
+     * @param reader The reader for this page
+     *
+     * @return true if this page contains a root element whose prefix is bound
+     * to the JSP namespace, and false otherwise
+     */
+    private boolean hasJspRoot(JspReader reader) throws JasperException {
+
+        // <prefix>:root must be the first element
+        Mark start = null;
+        while ((start = reader.skipUntil("<")) != null) {
+            int c = reader.nextChar();
+            if (c != '!' && c != '?') break;
+        }
+        if (start == null) {
+            return false;
+        }
+        Mark stop = reader.skipUntil(":root");
+        if (stop == null) {
+            return false;
+        }
+        // call substring to get rid of leading '<'
+        String prefix = reader.getText(start, stop).substring(1);
+
+        start = stop;
+        stop = reader.skipUntil(">");
+        if (stop == null) {
+            return false;
+        }
+
+        // Determine namespace associated with <root> element's prefix
+        String root = reader.getText(start, stop);
+        String xmlnsDecl = "xmlns:" + prefix;
+        int index = root.indexOf(xmlnsDecl);
+        if (index == -1) {
+            return false;
+        }
+        index += xmlnsDecl.length();
+        while (index < root.length()
+                && Character.isWhitespace(root.charAt(index))) {
+            index++;
+        }
+        if (index < root.length() && root.charAt(index) == '=') {
+            index++;
+            while (index < root.length()
+                    && Character.isWhitespace(root.charAt(index))) {
+                index++;
+            }
+            if (index < root.length() && root.charAt(index++) == '"'
+                && root.regionMatches(index, JSP_URI, 0,
+                        JSP_URI.length())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private JarFile getJarFile(URL jarFileUrl) throws IOException {
+        JarFile jarFile = null;
+
+        if (jarFileUrl != null) {
+            JarURLConnection conn = (JarURLConnection) jarFileUrl.openConnection();
+            conn.setUseCaches(false);
+            conn.connect();
+            jarFile = conn.getJarFile();
+        }
+
+        return jarFile;
+    }
+
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ScriptingVariabler.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,148 @@
+/*
+ * 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.*;
+import org.apache.struts2.jasper.JasperException;
+
+/**
+ * Class responsible for determining the scripting variables that every
+ * custom action needs to declare.
+ *
+ * @author Jan Luehe
+ */
+class ScriptingVariabler {
+
+    private static final Integer MAX_SCOPE = new Integer(Integer.MAX_VALUE);
+
+    /*
+     * Assigns an identifier (of type integer) to every custom tag, in order
+     * to help identify, for every custom tag, the scripting variables that it
+     * needs to declare.
+     */
+    static class CustomTagCounter extends Node.Visitor {
+
+	private int count;
+	private Node.CustomTag parent;
+
+	public void visit(Node.CustomTag n) throws JasperException {
+	    n.setCustomTagParent(parent);
+	    Node.CustomTag tmpParent = parent;
+	    parent = n;
+	    visitBody(n);
+	    parent = tmpParent;
+	    n.setNumCount(new Integer(count++));
+	}
+    }
+
+    /*
+     * For every custom tag, determines the scripting variables it needs to
+     * declare. 
+     */
+    static class ScriptingVariableVisitor extends Node.Visitor {
+
+	private ErrorDispatcher err;
+	private Hashtable scriptVars;
+	
+	public ScriptingVariableVisitor(ErrorDispatcher err) {
+	    this.err = err;
+	    scriptVars = new Hashtable();
+	}
+
+	public void visit(Node.CustomTag n) throws JasperException {
+	    setScriptingVars(n, VariableInfo.AT_BEGIN);
+	    setScriptingVars(n, VariableInfo.NESTED);
+	    visitBody(n);
+	    setScriptingVars(n, VariableInfo.AT_END);
+	}
+
+	private void setScriptingVars(Node.CustomTag n, int scope)
+	        throws JasperException {
+
+	    TagVariableInfo[] tagVarInfos = n.getTagVariableInfos();
+	    VariableInfo[] varInfos = n.getVariableInfos();
+	    if (tagVarInfos.length == 0 && varInfos.length == 0) {
+		return;
+	    }
+
+	    Vector vec = new Vector();
+
+	    Integer ownRange = null;
+	    if (scope == VariableInfo.AT_BEGIN
+		    || scope == VariableInfo.AT_END) {
+		Node.CustomTag parent = n.getCustomTagParent();
+		if (parent == null)
+		    ownRange = MAX_SCOPE;
+		else
+		    ownRange = parent.getNumCount();
+	    } else {
+		// NESTED
+		ownRange = n.getNumCount();
+	    }
+
+	    if (varInfos.length > 0) {
+		for (int i=0; i<varInfos.length; i++) {
+		    if (varInfos[i].getScope() != scope
+			    || !varInfos[i].getDeclare()) {
+			continue;
+		    }
+		    String varName = varInfos[i].getVarName();
+		    
+		    Integer currentRange = (Integer) scriptVars.get(varName);
+		    if (currentRange == null
+			    || ownRange.compareTo(currentRange) > 0) {
+			scriptVars.put(varName, ownRange);
+			vec.add(varInfos[i]);
+		    }
+		}
+	    } else {
+		for (int i=0; i<tagVarInfos.length; i++) {
+		    if (tagVarInfos[i].getScope() != scope
+			    || !tagVarInfos[i].getDeclare()) {
+			continue;
+		    }
+		    String varName = tagVarInfos[i].getNameGiven();
+		    if (varName == null) {
+			varName = n.getTagData().getAttributeString(
+		                        tagVarInfos[i].getNameFromAttribute());
+			if (varName == null) {
+			    err.jspError(n, "jsp.error.scripting.variable.missing_name",
+					 tagVarInfos[i].getNameFromAttribute());
+			}
+		    }
+
+		    Integer currentRange = (Integer) scriptVars.get(varName);
+		    if (currentRange == null
+			    || ownRange.compareTo(currentRange) > 0) {
+			scriptVars.put(varName, ownRange);
+			vec.add(tagVarInfos[i]);
+		    }
+		}
+	    }
+
+	    n.setScriptingVars(vec, scope);
+	}
+    }
+
+    public static void set(Node.Nodes page, ErrorDispatcher err)
+	    throws JasperException {
+	page.visit(new CustomTagCounter());
+	page.visit(new ScriptingVariableVisitor(err));
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/ServletWriter.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,176 @@
+/*
+ * 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.IOException;
+import java.io.PrintWriter;
+
+/**
+ * This is what is used to generate servlets. 
+ *
+ * @author Anil K. Vijendran
+ * @author Kin-man Chung
+ */
+public class ServletWriter {
+    public static int TAB_WIDTH = 2;
+    public static String SPACES = "                              ";
+
+    // Current indent level:
+    private int indent = 0;
+    private int virtual_indent = 0;
+
+    // The sink writer:
+    PrintWriter writer;
+    
+    // servlet line numbers start from 1
+    private int javaLine = 1;
+
+
+    public ServletWriter(PrintWriter writer) {
+	this.writer = writer;
+    }
+
+    public void close() throws IOException {
+	writer.close();
+    }
+
+    
+    // -------------------- Access informations --------------------
+
+    public int getJavaLine() {
+        return javaLine;
+    }
+
+
+    // -------------------- Formatting --------------------
+
+    public void pushIndent() {
+	virtual_indent += TAB_WIDTH;
+	if (virtual_indent >= 0 && virtual_indent <= SPACES.length())
+	    indent = virtual_indent;
+    }
+
+    public void popIndent() {
+	virtual_indent -= TAB_WIDTH;
+	if (virtual_indent >= 0 && virtual_indent <= SPACES.length())
+	    indent = virtual_indent;
+    }
+
+    /**
+     * Print a standard comment for echo outputed chunk.
+     * @param start The starting position of the JSP chunk being processed. 
+     * @param stop  The ending position of the JSP chunk being processed. 
+     */
+    public void printComment(Mark start, Mark stop, char[] chars) {
+        if (start != null && stop != null) {
+            println("// from="+start);
+            println("//   to="+stop);
+        }
+        
+        if (chars != null)
+            for(int i = 0; i < chars.length;) {
+                printin();
+                print("// ");
+                while (chars[i] != '\n' && i < chars.length)
+                    writer.print(chars[i++]);
+            }
+    }
+
+    /**
+     * Prints the given string followed by '\n'
+     */
+    public void println(String s) {
+        javaLine++;
+	writer.println(s);
+    }
+
+    /**
+     * Prints a '\n'
+     */
+    public void println() {
+        javaLine++;
+	writer.println("");
+    }
+
+    /**
+     * Prints the current indention
+     */
+    public void printin() {
+	writer.print(SPACES.substring(0, indent));
+    }
+
+    /**
+     * Prints the current indention, followed by the given string
+     */
+    public void printin(String s) {
+	writer.print(SPACES.substring(0, indent));
+	writer.print(s);
+    }
+
+    /**
+     * Prints the current indention, and then the string, and a '\n'.
+     */
+    public void printil(String s) {
+        javaLine++;
+	writer.print(SPACES.substring(0, indent));
+	writer.println(s);
+    }
+
+    /**
+     * Prints the given char.
+     *
+     * Use println() to print a '\n'.
+     */
+    public void print(char c) {
+	writer.print(c);
+    }
+
+    /**
+     * Prints the given int.
+     */
+    public void print(int i) {
+	writer.print(i);
+    }
+
+    /**
+     * Prints the given string.
+     *
+     * The string must not contain any '\n', otherwise the line count will be
+     * off.
+     */
+    public void print(String s) {
+	writer.print(s);
+    }
+
+    /**
+     * Prints the given string.
+     *
+     * If the string spans multiple lines, the line count will be adjusted
+     * accordingly.
+     */
+    public void printMultiLn(String s) {
+        int index = 0;
+
+        // look for hidden newlines inside strings
+        while ((index=s.indexOf('\n',index)) > -1 ) {
+            javaLine++;
+            index++;
+        }
+
+	writer.print(s);
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapGenerator.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,171 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+
+/**
+ * Represents a source map (SMAP), which serves to associate lines
+ * of the input JSP file(s) to lines in the generated servlet in the
+ * final .class file, according to the JSR-045 spec.
+ * 
+ * @author Shawn Bayern
+ */
+public class SmapGenerator {
+
+    //*********************************************************************
+    // Overview
+
+    /*
+     * The SMAP syntax is reasonably straightforward.  The purpose of this
+     * class is currently twofold:
+     *  - to provide a simple but low-level Java interface to build
+     *    a logical SMAP
+     *  - to serialize this logical SMAP for eventual inclusion directly
+     *    into a .class file.
+     */
+
+
+    //*********************************************************************
+    // Private state
+
+    private String outputFileName;
+    private String defaultStratum = "Java";
+    private List strata = new ArrayList();
+    private List embedded = new ArrayList();
+    private boolean doEmbedded = true;
+
+    //*********************************************************************
+    // Methods for adding mapping data
+
+    /**
+     * Sets the filename (without path information) for the generated
+     * source file.  E.g., "foo$jsp.java".
+     */
+    public synchronized void setOutputFileName(String x) {
+	outputFileName = x;
+    }
+
+    /**
+     * Adds the given SmapStratum object, representing a Stratum with
+     * logically associated FileSection and LineSection blocks, to
+     * the current SmapGenerator.  If <tt>default</tt> is true, this
+     * stratum is made the default stratum, overriding any previously
+     * set default.
+     *
+     * @param stratum the SmapStratum object to add
+     * @param defaultStratum if <tt>true</tt>, this SmapStratum is considered
+     *                to represent the default SMAP stratum unless
+     *                overwritten
+     */
+    public synchronized void addStratum(SmapStratum stratum,
+					boolean defaultStratum) {
+	strata.add(stratum);
+	if (defaultStratum)
+	    this.defaultStratum = stratum.getStratumName();
+    }
+
+    /**
+     * Adds the given string as an embedded SMAP with the given stratum name.
+     *
+     * @param smap the SMAP to embed
+     * @param stratumName the name of the stratum output by the compilation
+     *                    that produced the <tt>smap</tt> to be embedded
+     */
+    public synchronized void addSmap(String smap, String stratumName) {
+	embedded.add("*O " + stratumName + "\n"
+		   + smap
+		   + "*C " + stratumName + "\n");
+    }
+
+    /**
+     * Instructs the SmapGenerator whether to actually print any embedded
+     * SMAPs or not.  Intended for situations without an SMAP resolver.
+     *
+     * @param status If <tt>false</tt>, ignore any embedded SMAPs.
+     */
+    public void setDoEmbedded(boolean status) {
+	doEmbedded = status;
+    }
+
+    //*********************************************************************
+    // Methods for serializing the logical SMAP
+
+    public synchronized String getString() {
+	// check state and initialize buffer
+	if (outputFileName == null)
+	    throw new IllegalStateException();
+        StringBuffer out = new StringBuffer();
+
+	// start the SMAP
+	out.append("SMAP\n");
+	out.append(outputFileName + '\n');
+	out.append(defaultStratum + '\n');
+
+	// include embedded SMAPs
+	if (doEmbedded) {
+	    int nEmbedded = embedded.size();
+	    for (int i = 0; i < nEmbedded; i++) {
+	        out.append(embedded.get(i));
+	    }
+	}
+
+	// print our StratumSections, FileSections, and LineSections
+	int nStrata = strata.size();
+	for (int i = 0; i < nStrata; i++) {
+	    SmapStratum s = (SmapStratum) strata.get(i);
+	    out.append(s.getString());
+	}
+
+	// end the SMAP
+	out.append("*E\n");
+
+	return out.toString();
+    }
+
+    public String toString() { return getString(); }
+
+    //*********************************************************************
+    // For testing (and as an example of use)...
+
+    public static void main(String args[]) {
+	SmapGenerator g = new SmapGenerator();
+	g.setOutputFileName("foo.java");
+	SmapStratum s = new SmapStratum("JSP");
+	s.addFile("foo.jsp");
+	s.addFile("bar.jsp", "/foo/foo/bar.jsp");
+	s.addLineData(1, "foo.jsp", 1, 1, 1);
+	s.addLineData(2, "foo.jsp", 1, 6, 1);
+	s.addLineData(3, "foo.jsp", 2, 10, 5);
+	s.addLineData(20, "bar.jsp", 1, 30, 1);
+	g.addStratum(s, true);
+	System.out.print(g);
+
+	System.out.println("---");
+
+	SmapGenerator embedded = new SmapGenerator();
+	embedded.setOutputFileName("blargh.tier2");
+	s = new SmapStratum("Tier2");
+	s.addFile("1.tier2");
+	s.addLineData(1, "1.tier2", 1, 1, 1);
+	embedded.addStratum(s, true);
+	g.addSmap(embedded.toString(), "JSP");
+	System.out.println(g);
+    }
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapStratum.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,336 @@
+/*
+ * 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.List;
+import java.util.ArrayList;
+
+/**
+ * Represents the line and file mappings associated with a JSR-045
+ * "stratum".
+ *
+ * @author Jayson Falkner
+ * @author Shawn Bayern
+ */
+public class SmapStratum {
+
+    //*********************************************************************
+    // Class for storing LineInfo data
+
+    /**
+     * Represents a single LineSection in an SMAP, associated with
+     * a particular stratum.
+     */
+    public static class LineInfo {
+        private int inputStartLine = -1;
+        private int outputStartLine = -1;
+        private int lineFileID = 0;
+        private int inputLineCount = 1;
+        private int outputLineIncrement = 1;
+        private boolean lineFileIDSet = false;
+
+        /** Sets InputStartLine. */
+        public void setInputStartLine(int inputStartLine) {
+            if (inputStartLine < 0)
+                throw new IllegalArgumentException("" + inputStartLine);
+            this.inputStartLine = inputStartLine;
+        }
+
+        /** Sets OutputStartLine. */
+        public void setOutputStartLine(int outputStartLine) {
+            if (outputStartLine < 0)
+                throw new IllegalArgumentException("" + outputStartLine);
+            this.outputStartLine = outputStartLine;
+        }
+
+        /**
+             * Sets lineFileID.  Should be called only when different from
+             * that of prior LineInfo object (in any given context) or 0
+             * if the current LineInfo has no (logical) predecessor.
+             * <tt>LineInfo</tt> will print this file number no matter what.
+             */
+        public void setLineFileID(int lineFileID) {
+            if (lineFileID < 0)
+                throw new IllegalArgumentException("" + lineFileID);
+            this.lineFileID = lineFileID;
+            this.lineFileIDSet = true;
+        }
+
+        /** Sets InputLineCount. */
+        public void setInputLineCount(int inputLineCount) {
+            if (inputLineCount < 0)
+                throw new IllegalArgumentException("" + inputLineCount);
+            this.inputLineCount = inputLineCount;
+        }
+
+        /** Sets OutputLineIncrement. */
+        public void setOutputLineIncrement(int outputLineIncrement) {
+            if (outputLineIncrement < 0)
+                throw new IllegalArgumentException("" + outputLineIncrement);
+            this.outputLineIncrement = outputLineIncrement;
+        }
+
+        /**
+         * Retrieves the current LineInfo as a String, print all values
+         * only when appropriate (but LineInfoID if and only if it's been
+         * specified, as its necessity is sensitive to context).
+         */
+        public String getString() {
+            if (inputStartLine == -1 || outputStartLine == -1)
+                throw new IllegalStateException();
+            StringBuffer out = new StringBuffer();
+            out.append(inputStartLine);
+            if (lineFileIDSet)
+                out.append("#" + lineFileID);
+            if (inputLineCount != 1)
+                out.append("," + inputLineCount);
+            out.append(":" + outputStartLine);
+            if (outputLineIncrement != 1)
+                out.append("," + outputLineIncrement);
+            out.append('\n');
+            return out.toString();
+        }
+
+        public String toString() {
+            return getString();
+        }
+    }
+
+    //*********************************************************************
+    // Private state
+
+    private String stratumName;
+    private List fileNameList;
+    private List filePathList;
+    private List lineData;
+    private int lastFileID;
+
+    //*********************************************************************
+    // Constructor
+
+    /**
+     * Constructs a new SmapStratum object for the given stratum name
+     * (e.g., JSP).
+     *
+     * @param stratumName the name of the stratum (e.g., JSP)
+     */
+    public SmapStratum(String stratumName) {
+        this.stratumName = stratumName;
+        fileNameList = new ArrayList();
+        filePathList = new ArrayList();
+        lineData = new ArrayList();
+        lastFileID = 0;
+    }
+
+    //*********************************************************************
+    // Methods to add mapping information
+
+    /**
+     * Adds record of a new file, by filename.
+     *
+     * @param filename the filename to add, unqualified by path.
+     */
+    public void addFile(String filename) {
+        addFile(filename, filename);
+    }
+
+    /**
+     * Adds record of a new file, by filename and path.  The path
+     * may be relative to a source compilation path.
+     *
+     * @param filename the filename to add, unqualified by path
+     * @param filePath the path for the filename, potentially relative
+     *                 to a source compilation path
+     */
+    public void addFile(String filename, String filePath) {
+        int pathIndex = filePathList.indexOf(filePath);
+        if (pathIndex == -1) {
+            fileNameList.add(filename);
+            filePathList.add(filePath);
+        }
+    }
+
+    /**
+     * Combines consecutive LineInfos wherever possible
+     */
+    public void optimizeLineSection() {
+
+/* Some debugging code
+        for (int i = 0; i < lineData.size(); i++) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            System.out.print(li.toString());
+        }
+*/
+        //Incorporate each LineInfo into the previous LineInfo's 
+        //outputLineIncrement, if possible
+        int i = 0;
+        while (i < lineData.size() - 1) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            LineInfo liNext = (LineInfo)lineData.get(i + 1);
+            if (!liNext.lineFileIDSet
+                && liNext.inputStartLine == li.inputStartLine
+                && liNext.inputLineCount == 1
+                && li.inputLineCount == 1
+                && liNext.outputStartLine
+                    == li.outputStartLine
+                        + li.inputLineCount * li.outputLineIncrement) {
+                li.setOutputLineIncrement(
+                    liNext.outputStartLine
+                        - li.outputStartLine
+                        + liNext.outputLineIncrement);
+                lineData.remove(i + 1);
+            } else {
+                i++;
+            }
+        }
+
+        //Incorporate each LineInfo into the previous LineInfo's
+        //inputLineCount, if possible
+        i = 0;
+        while (i < lineData.size() - 1) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            LineInfo liNext = (LineInfo)lineData.get(i + 1);
+            if (!liNext.lineFileIDSet
+                && liNext.inputStartLine == li.inputStartLine + li.inputLineCount
+                && liNext.outputLineIncrement == li.outputLineIncrement
+                && liNext.outputStartLine
+                    == li.outputStartLine
+                        + li.inputLineCount * li.outputLineIncrement) {
+                li.setInputLineCount(li.inputLineCount + liNext.inputLineCount);
+                lineData.remove(i + 1);
+            } else {
+                i++;
+            }
+        }
+    }
+
+    /**
+     * Adds complete information about a simple line mapping.  Specify
+     * all the fields in this method; the back-end machinery takes care
+     * of printing only those that are necessary in the final SMAP.
+     * (My view is that fields are optional primarily for spatial efficiency,
+     * not for programmer convenience.  Could always add utility methods
+     * later.)
+     *
+     * @param inputStartLine starting line in the source file
+     *        (SMAP <tt>InputStartLine</tt>)
+     * @param inputFileName the filepath (or name) from which the input comes
+     *        (yields SMAP <tt>LineFileID</tt>)  Use unqualified names
+     *        carefully, and only when they uniquely identify a file.
+     * @param inputLineCount the number of lines in the input to map
+     *        (SMAP <tt>LineFileCount</tt>)
+     * @param outputStartLine starting line in the output file 
+     *        (SMAP <tt>OutputStartLine</tt>)
+     * @param outputLineIncrement number of output lines to map to each
+     *        input line (SMAP <tt>OutputLineIncrement</tt>).  <i>Given the
+     *        fact that the name starts with "output", I continuously have
+     *        the subconscious urge to call this field
+     *        <tt>OutputLineExcrement</tt>.</i>
+     */
+    public void addLineData(
+        int inputStartLine,
+        String inputFileName,
+        int inputLineCount,
+        int outputStartLine,
+        int outputLineIncrement) {
+        // check the input - what are you doing here??
+        int fileIndex = filePathList.indexOf(inputFileName);
+        if (fileIndex == -1) // still
+            throw new IllegalArgumentException(
+                "inputFileName: " + inputFileName);
+
+        //Jasper incorrectly SMAPs certain Nodes, giving them an 
+        //outputStartLine of 0.  This can cause a fatal error in
+        //optimizeLineSection, making it impossible for Jasper to
+        //compile the JSP.  Until we can fix the underlying
+        //SMAPping problem, we simply ignore the flawed SMAP entries.
+        if (outputStartLine == 0)
+            return;
+
+        // build the LineInfo
+        LineInfo li = new LineInfo();
+        li.setInputStartLine(inputStartLine);
+        li.setInputLineCount(inputLineCount);
+        li.setOutputStartLine(outputStartLine);
+        li.setOutputLineIncrement(outputLineIncrement);
+        if (fileIndex != lastFileID)
+            li.setLineFileID(fileIndex);
+        lastFileID = fileIndex;
+
+        // save it
+        lineData.add(li);
+    }
+
+    //*********************************************************************
+    // Methods to retrieve information
+
+    /**
+     * Returns the name of the stratum.
+     */
+    public String getStratumName() {
+        return stratumName;
+    }
+
+    /**
+     * Returns the given stratum as a String:  a StratumSection,
+     * followed by at least one FileSection and at least one LineSection.
+     */
+    public String getString() {
+        // check state and initialize buffer
+        if (fileNameList.size() == 0 || lineData.size() == 0)
+            return null;
+
+        StringBuffer out = new StringBuffer();
+
+        // print StratumSection
+        out.append("*S " + stratumName + "\n");
+
+        // print FileSection
+        out.append("*F\n");
+        int bound = fileNameList.size();
+        for (int i = 0; i < bound; i++) {
+            if (filePathList.get(i) != null) {
+                out.append("+ " + i + " " + fileNameList.get(i) + "\n");
+                // Source paths must be relative, not absolute, so we
+                // remove the leading "/", if one exists.
+                String filePath = (String)filePathList.get(i);
+                if (filePath.startsWith("/")) {
+                    filePath = filePath.substring(1);
+                }
+                out.append(filePath + "\n");
+            } else {
+                out.append(i + " " + fileNameList.get(i) + "\n");
+            }
+        }
+
+        // print LineSection
+        out.append("*L\n");
+        bound = lineData.size();
+        for (int i = 0; i < bound; i++) {
+            LineInfo li = (LineInfo)lineData.get(i);
+            out.append(li.getString());
+        }
+
+        return out.toString();
+    }
+
+    public String toString() {
+        return getString();
+    }
+
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/SmapUtil.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,729 @@
+/*
+ * 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.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+
+/**
+ * Contains static utilities for generating SMAP data based on the
+ * current version of Jasper.
+ * 
+ * @author Jayson Falkner
+ * @author Shawn Bayern
+ * @author Robert Field (inner SDEInstaller class)
+ * @author Mark Roth
+ * @author Kin-man Chung
+ */
+public class SmapUtil {
+
+    private org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SmapUtil.class );
+
+    //*********************************************************************
+    // Constants
+
+    public static final String SMAP_ENCODING = "UTF-8";
+
+    //*********************************************************************
+    // Public entry points
+
+    /**
+     * Generates an appropriate SMAP representing the current compilation
+     * context.  (JSR-045.)
+     *
+     * @param ctxt Current compilation context
+     * @param pageNodes The current JSP page
+     * @return a SMAP for the page
+     */
+    public static String[] generateSmap(
+        JspCompilationContext ctxt,
+        Node.Nodes pageNodes)
+        throws IOException {
+
+        // Scan the nodes for presence of Jasper generated inner classes
+        PreScanVisitor psVisitor = new PreScanVisitor();
+        try {
+            pageNodes.visit(psVisitor);
+        } catch (JasperException ex) {
+        }
+        HashMap map = psVisitor.getMap();
+
+        // set up our SMAP generator
+        SmapGenerator g = new SmapGenerator();
+        
+        /** Disable reading of input SMAP because:
+            1. There is a bug here: getRealPath() is null if .jsp is in a jar
+        	Bugzilla 14660.
+            2. Mappings from other sources into .jsp files are not supported.
+            TODO: fix 1. if 2. is not true.
+        // determine if we have an input SMAP
+        String smapPath = inputSmapPath(ctxt.getRealPath(ctxt.getJspFile()));
+            File inputSmap = new File(smapPath);
+            if (inputSmap.exists()) {
+                byte[] embeddedSmap = null;
+            byte[] subSmap = SDEInstaller.readWhole(inputSmap);
+            String subSmapString = new String(subSmap, SMAP_ENCODING);
+            g.addSmap(subSmapString, "JSP");
+        }
+        **/
+
+        // now, assemble info about our own stratum (JSP) using JspLineMap
+        SmapStratum s = new SmapStratum("JSP");
+
+        g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
+
+        // Map out Node.Nodes
+        evaluateNodes(pageNodes, s, map, ctxt.getOptions().getMappedFile());
+        s.optimizeLineSection();
+        g.addStratum(s, true);
+
+        if (ctxt.getOptions().isSmapDumped()) {
+            File outSmap = new File(ctxt.getClassFileName() + ".smap");
+            PrintWriter so =
+                new PrintWriter(
+                    new OutputStreamWriter(
+                        new FileOutputStream(outSmap),
+                        SMAP_ENCODING));
+            so.print(g.getString());
+            so.close();
+        }
+
+        String classFileName = ctxt.getClassFileName();
+        int innerClassCount = map.size();
+        String [] smapInfo = new String[2 + innerClassCount*2];
+        smapInfo[0] = classFileName;
+        smapInfo[1] = g.getString();
+
+        int count = 2;
+        Iterator iter = map.entrySet().iterator();
+        while (iter.hasNext()) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            String innerClass = (String) entry.getKey();
+            s = (SmapStratum) entry.getValue();
+            s.optimizeLineSection();
+            g = new SmapGenerator();
+            g.setOutputFileName(unqualify(ctxt.getServletJavaFileName()));
+            g.addStratum(s, true);
+
+            String innerClassFileName =
+                classFileName.substring(0, classFileName.indexOf(".class")) +
+                '$' + innerClass + ".class";
+            if (ctxt.getOptions().isSmapDumped()) {
+                File outSmap = new File(innerClassFileName + ".smap");
+                PrintWriter so =
+                    new PrintWriter(
+                        new OutputStreamWriter(
+                            new FileOutputStream(outSmap),
+                            SMAP_ENCODING));
+                so.print(g.getString());
+                so.close();
+            }
+            smapInfo[count] = innerClassFileName;
+            smapInfo[count+1] = g.getString();
+            count += 2;
+        }
+
+        return smapInfo;
+    }
+
+    public static void installSmap(String[] smap)
+        throws IOException {
+        if (smap == null) {
+            return;
+        }
+
+        for (int i = 0; i < smap.length; i += 2) {
+            File outServlet = new File(smap[i]);
+            SDEInstaller.install(outServlet, smap[i+1].getBytes());
+        }
+    }
+
+    //*********************************************************************
+    // Private utilities
+
+    /**
+     * Returns an unqualified version of the given file path.
+     */
+    private static String unqualify(String path) {
+        path = path.replace('\\', '/');
+        return path.substring(path.lastIndexOf('/') + 1);
+    }
+
+    /**
+     * Returns a file path corresponding to a potential SMAP input
+     * for the given compilation input (JSP file).
+     */
+    private static String inputSmapPath(String path) {
+        return path.substring(0, path.lastIndexOf('.') + 1) + "smap";
+    }
+
+    //*********************************************************************
+    // Installation logic (from Robert Field, JSR-045 spec lead)
+    private static class SDEInstaller {
+
+        private org.apache.juli.logging.Log log=
+            org.apache.juli.logging.LogFactory.getLog( SDEInstaller.class );
+
+        static final String nameSDE = "SourceDebugExtension";
+
+        byte[] orig;
+        byte[] sdeAttr;
+        byte[] gen;
+
+        int origPos = 0;
+        int genPos = 0;
+
+        int sdeIndex;
+
+        public static void main(String[] args) throws IOException {
+            if (args.length == 2) {
+                install(new File(args[0]), new File(args[1]));
+            } else if (args.length == 3) {
+                install(
+                    new File(args[0]),
+                    new File(args[1]),
+                    new File(args[2]));
+            } else {
+                System.err.println(
+                    "Usage: <command> <input class file> "
+                        + "<attribute file> <output class file name>\n"
+                        + "<command> <input/output class file> <attribute file>");
+            }
+        }
+
+        static void install(File inClassFile, File attrFile, File outClassFile)
+            throws IOException {
+            new SDEInstaller(inClassFile, attrFile, outClassFile);
+        }
+
+        static void install(File inOutClassFile, File attrFile)
+            throws IOException {
+            File tmpFile = new File(inOutClassFile.getPath() + "tmp");
+            new SDEInstaller(inOutClassFile, attrFile, tmpFile);
+            if (!inOutClassFile.delete()) {
+                throw new IOException("inOutClassFile.delete() failed");
+            }
+            if (!tmpFile.renameTo(inOutClassFile)) {
+                throw new IOException("tmpFile.renameTo(inOutClassFile) failed");
+            }
+        }
+
+        static void install(File classFile, byte[] smap) throws IOException {
+            File tmpFile = new File(classFile.getPath() + "tmp");
+            new SDEInstaller(classFile, smap, tmpFile);
+            if (!classFile.delete()) {
+                throw new IOException("classFile.delete() failed");
+            }
+            if (!tmpFile.renameTo(classFile)) {
+                throw new IOException("tmpFile.renameTo(classFile) failed");
+            }
+        }
+
+        SDEInstaller(File inClassFile, byte[] sdeAttr, File outClassFile)
+            throws IOException {
+            if (!inClassFile.exists()) {
+                throw new FileNotFoundException("no such file: " + inClassFile);
+            }
+
+            this.sdeAttr = sdeAttr;
+            // get the bytes
+            orig = readWhole(inClassFile);
+            gen = new byte[orig.length + sdeAttr.length + 100];
+
+            // do it
+            addSDE();
+
+            // write result
+            FileOutputStream outStream = new FileOutputStream(outClassFile);
+            outStream.write(gen, 0, genPos);
+            outStream.close();
+        }
+
+        SDEInstaller(File inClassFile, File attrFile, File outClassFile)
+            throws IOException {
+            this(inClassFile, readWhole(attrFile), outClassFile);
+        }
+
+        static byte[] readWhole(File input) throws IOException {
+            FileInputStream inStream = new FileInputStream(input);
+            int len = (int)input.length();
+            byte[] bytes = new byte[len];
+            if (inStream.read(bytes, 0, len) != len) {
+                throw new IOException("expected size: " + len);
+            }
+            inStream.close();
+            return bytes;
+        }
+
+        void addSDE() throws UnsupportedEncodingException, IOException {
+            int i;
+            copy(4 + 2 + 2); // magic min/maj version
+            int constantPoolCountPos = genPos;
+            int constantPoolCount = readU2();
+            if (log.isDebugEnabled())
+                log.debug("constant pool count: " + constantPoolCount);
+            writeU2(constantPoolCount);
+
+            // copy old constant pool return index of SDE symbol, if found
+            sdeIndex = copyConstantPool(constantPoolCount);
+            if (sdeIndex < 0) {
+                // if "SourceDebugExtension" symbol not there add it
+                writeUtf8ForSDE();
+
+                // increment the countantPoolCount
+                sdeIndex = constantPoolCount;
+                ++constantPoolCount;
+                randomAccessWriteU2(constantPoolCountPos, constantPoolCount);
+
+                if (log.isDebugEnabled())
+                    log.debug("SourceDebugExtension not found, installed at: " + sdeIndex);
+            } else {
+                if (log.isDebugEnabled())
+                    log.debug("SourceDebugExtension found at: " + sdeIndex);
+            }
+            copy(2 + 2 + 2); // access, this, super
+            int interfaceCount = readU2();
+            writeU2(interfaceCount);
+            if (log.isDebugEnabled())
+                log.debug("interfaceCount: " + interfaceCount);
+            copy(interfaceCount * 2);
+            copyMembers(); // fields
+            copyMembers(); // methods
+            int attrCountPos = genPos;
+            int attrCount = readU2();
+            writeU2(attrCount);
+            if (log.isDebugEnabled())
+                log.debug("class attrCount: " + attrCount);
+            // copy the class attributes, return true if SDE attr found (not copied)
+            if (!copyAttrs(attrCount)) {
+                // we will be adding SDE and it isn't already counted
+                ++attrCount;
+                randomAccessWriteU2(attrCountPos, attrCount);
+                if (log.isDebugEnabled())
+                    log.debug("class attrCount incremented");
+            }
+            writeAttrForSDE(sdeIndex);
+        }
+
+        void copyMembers() {
+            int count = readU2();
+            writeU2(count);
+            if (log.isDebugEnabled())
+                log.debug("members count: " + count);
+            for (int i = 0; i < count; ++i) {
+                copy(6); // access, name, descriptor
+                int attrCount = readU2();
+                writeU2(attrCount);
+                if (log.isDebugEnabled())
+                    log.debug("member attr count: " + attrCount);
+                copyAttrs(attrCount);
+            }
+        }
+
+        boolean copyAttrs(int attrCount) {
+            boolean sdeFound = false;
+            for (int i = 0; i < attrCount; ++i) {
+                int nameIndex = readU2();
+                // don't write old SDE
+                if (nameIndex == sdeIndex) {
+                    sdeFound = true;
+                    if (log.isDebugEnabled())
+                        log.debug("SDE attr found");
+                } else {
+                    writeU2(nameIndex); // name
+                    int len = readU4();
+                    writeU4(len);
+                    copy(len);
+                    if (log.isDebugEnabled())
+                        log.debug("attr len: " + len);
+                }
+            }
+            return sdeFound;
+        }
+
+        void writeAttrForSDE(int index) {
+            writeU2(index);
+            writeU4(sdeAttr.length);
+            for (int i = 0; i < sdeAttr.length; ++i) {
+                writeU1(sdeAttr[i]);
+            }
+        }
+
+        void randomAccessWriteU2(int pos, int val) {
+            int savePos = genPos;
+            genPos = pos;
+            writeU2(val);
+            genPos = savePos;
+        }
+
+        int readU1() {
+            return ((int)orig[origPos++]) & 0xFF;
+        }
+
+        int readU2() {
+            int res = readU1();
+            return (res << 8) + readU1();
+        }
+
+        int readU4() {
+            int res = readU2();
+            return (res << 16) + readU2();
+        }
+
+        void writeU1(int val) {
+            gen[genPos++] = (byte)val;
+        }
+
+        void writeU2(int val) {
+            writeU1(val >> 8);
+            writeU1(val & 0xFF);
+        }
+
+        void writeU4(int val) {
+            writeU2(val >> 16);
+            writeU2(val & 0xFFFF);
+        }
+
+        void copy(int count) {
+            for (int i = 0; i < count; ++i) {
+                gen[genPos++] = orig[origPos++];
+            }
+        }
+
+        byte[] readBytes(int count) {
+            byte[] bytes = new byte[count];
+            for (int i = 0; i < count; ++i) {
+                bytes[i] = orig[origPos++];
+            }
+            return bytes;
+        }
+
+        void writeBytes(byte[] bytes) {
+            for (int i = 0; i < bytes.length; ++i) {
+                gen[genPos++] = bytes[i];
+            }
+        }
+
+        int copyConstantPool(int constantPoolCount)
+            throws UnsupportedEncodingException, IOException {
+            int sdeIndex = -1;
+            // copy const pool index zero not in class file
+            for (int i = 1; i < constantPoolCount; ++i) {
+                int tag = readU1();
+                writeU1(tag);
+                switch (tag) {
+                    case 7 : // Class
+                    case 8 : // String
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 2 bytes");
+                        copy(2);
+                        break;
+                    case 9 : // Field
+                    case 10 : // Method
+                    case 11 : // InterfaceMethod
+                    case 3 : // Integer
+                    case 4 : // Float
+                    case 12 : // NameAndType
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 4 bytes");
+                        copy(4);
+                        break;
+                    case 5 : // Long
+                    case 6 : // Double
+                        if (log.isDebugEnabled())
+                            log.debug(i + " copying 8 bytes");
+                        copy(8);
+                        i++;
+                        break;
+                    case 1 : // Utf8
+                        int len = readU2();
+                        writeU2(len);
+                        byte[] utf8 = readBytes(len);
+                        String str = new String(utf8, "UTF-8");
+                        if (log.isDebugEnabled())
+                            log.debug(i + " read class attr -- '" + str + "'");
+                        if (str.equals(nameSDE)) {
+                            sdeIndex = i;
+                        }
+                        writeBytes(utf8);
+                        break;
+                    default :
+                        throw new IOException("unexpected tag: " + tag);
+                }
+            }
+            return sdeIndex;
+        }
+
+        void writeUtf8ForSDE() {
+            int len = nameSDE.length();
+            writeU1(1); // Utf8 tag
+            writeU2(len);
+            for (int i = 0; i < len; ++i) {
+                writeU1(nameSDE.charAt(i));
+            }
+        }
+    }
+
+    public static void evaluateNodes(
+        Node.Nodes nodes,
+        SmapStratum s,
+        HashMap innerClassMap,
+        boolean breakAtLF) {
+        try {
+            nodes.visit(new SmapGenVisitor(s, breakAtLF, innerClassMap));
+        } catch (JasperException ex) {
+        }
+    }
+
+    static class SmapGenVisitor extends Node.Visitor {
+
+        private SmapStratum smap;
+        private boolean breakAtLF;
+        private HashMap innerClassMap;
+
+        SmapGenVisitor(SmapStratum s, boolean breakAtLF, HashMap map) {
+            this.smap = s;
+            this.breakAtLF = breakAtLF;
+            this.innerClassMap = map;
+        }
+
+        public void visitBody(Node n) throws JasperException {
+            SmapStratum smapSave = smap;
+            String innerClass = n.getInnerClassName();
+            if (innerClass != null) {
+                this.smap = (SmapStratum) innerClassMap.get(innerClass);
+            }
+            super.visitBody(n);
+            smap = smapSave;
+        }
+
+        public void visit(Node.Declaration n) throws JasperException {
+            doSmapText(n);
+        }
+
+        public void visit(Node.Expression n) throws JasperException {
+            doSmapText(n);
+        }
+
+        public void visit(Node.Scriptlet n) throws JasperException {
+            doSmapText(n);
+        }
+
+        public void visit(Node.IncludeAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.ForwardAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.GetProperty n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.SetProperty n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.UseBean n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.PlugIn n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.CustomTag n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.UninterpretedTag n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.JspElement n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.JspText n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.NamedAttribute n) throws JasperException {
+            visitBody(n);
+        }
+
+        public void visit(Node.JspBody n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.InvokeAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.DoBodyAction n) throws JasperException {
+            doSmap(n);
+            visitBody(n);
+        }
+
+        public void visit(Node.ELExpression n) throws JasperException {
+            doSmap(n);
+        }
+
+        public void visit(Node.TemplateText n) throws JasperException {
+            Mark mark = n.getStart();
+            if (mark == null) {
+                return;
+            }
+
+            //Add the file information
+            String fileName = mark.getFile();
+            smap.addFile(unqualify(fileName), fileName);
+
+            //Add a LineInfo that corresponds to the beginning of this node
+            int iInputStartLine = mark.getLineNumber();
+            int iOutputStartLine = n.getBeginJavaLine();
+            int iOutputLineIncrement = breakAtLF? 1: 0;
+            smap.addLineData(iInputStartLine, fileName, 1, iOutputStartLine, 
+                             iOutputLineIncrement);
+
+            // Output additional mappings in the text
+            java.util.ArrayList extraSmap = n.getExtraSmap();
+
+            if (extraSmap != null) {
+                for (int i = 0; i < extraSmap.size(); i++) {
+                    iOutputStartLine += iOutputLineIncrement;
+                    smap.addLineData(
+                        iInputStartLine+((Integer)extraSmap.get(i)).intValue(),
+                        fileName,
+                        1,
+                        iOutputStartLine,
+                        iOutputLineIncrement);
+                }
+            }
+        }
+
+        private void doSmap(
+            Node n,
+            int inLineCount,
+            int outIncrement,
+            int skippedLines) {
+            Mark mark = n.getStart();
+            if (mark == null) {
+                return;
+            }
+
+            String unqualifiedName = unqualify(mark.getFile());
+            smap.addFile(unqualifiedName, mark.getFile());
+            smap.addLineData(
+                mark.getLineNumber() + skippedLines,
+                mark.getFile(),
+                inLineCount - skippedLines,
+                n.getBeginJavaLine() + skippedLines,
+                outIncrement);
+        }
+
+        private void doSmap(Node n) {
+            doSmap(n, 1, n.getEndJavaLine() - n.getBeginJavaLine(), 0);
+        }
+
+        private void doSmapText(Node n) {
+            String text = n.getText();
+            int index = 0;
+            int next = 0;
+            int lineCount = 1;
+            int skippedLines = 0;
+            boolean slashStarSeen = false;
+            boolean beginning = true;
+
+            // Count lines inside text, but skipping comment lines at the
+            // beginning of the text.
+            while ((next = text.indexOf('\n', index)) > -1) {
+                if (beginning) {
+                    String line = text.substring(index, next).trim();
+                    if (!slashStarSeen && line.startsWith("/*")) {
+                        slashStarSeen = true;
+                    }
+                    if (slashStarSeen) {
+                        skippedLines++;
+                        int endIndex = line.indexOf("*/");
+                        if (endIndex >= 0) {
+                            // End of /* */ comment
+                            slashStarSeen = false;
+                            if (endIndex < line.length() - 2) {
+                                // Some executable code after comment
+                                skippedLines--;
+                                beginning = false;
+                            }
+                        }
+                    } else if (line.length() == 0 || line.startsWith("//")) {
+                        skippedLines++;
+                    } else {
+                        beginning = false;
+                    }
+                }
+                lineCount++;
+                index = next + 1;
+            }
+
+            doSmap(n, lineCount, 1, skippedLines);
+        }
+    }
+
+    private static class PreScanVisitor extends Node.Visitor {
+
+        HashMap map = new HashMap();
+
+        public void doVisit(Node n) {
+            String inner = n.getInnerClassName();
+            if (inner != null && !map.containsKey(inner)) {
+                map.put(inner, new SmapStratum("JSP"));
+            }
+        }
+
+        HashMap getMap() {
+            return map;
+        }
+    }
+    
+}

Added: struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java?rev=819444&view=auto
==============================================================================
--- struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java (added)
+++ struts/struts2/trunk/plugins/embeddedjsp/src/main/java/org/apache/struts2/jasper/compiler/TagConstants.java Mon Sep 28 01:55:26 2009
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+public interface TagConstants {
+
+    public static final String JSP_URI = "http://java.sun.com/JSP/Page";
+
+    public static final String DIRECTIVE_ACTION = "directive.";
+
+    public static final String ROOT_ACTION = "root";
+    public static final String JSP_ROOT_ACTION = "jsp:root";
+
+    public static final String PAGE_DIRECTIVE_ACTION = "directive.page";
+    public static final String JSP_PAGE_DIRECTIVE_ACTION = "jsp:directive.page";
+
+    public static final String INCLUDE_DIRECTIVE_ACTION = "directive.include";
+    public static final String JSP_INCLUDE_DIRECTIVE_ACTION = "jsp:directive.include";
+
+    public static final String DECLARATION_ACTION = "declaration";
+    public static final String JSP_DECLARATION_ACTION = "jsp:declaration";
+
+    public static final String SCRIPTLET_ACTION = "scriptlet";
+    public static final String JSP_SCRIPTLET_ACTION = "jsp:scriptlet";
+
+    public static final String EXPRESSION_ACTION = "expression";
+    public static final String JSP_EXPRESSION_ACTION = "jsp:expression";
+
+    public static final String USE_BEAN_ACTION = "useBean";
+    public static final String JSP_USE_BEAN_ACTION = "jsp:useBean";
+
+    public static final String SET_PROPERTY_ACTION = "setProperty";
+    public static final String JSP_SET_PROPERTY_ACTION = "jsp:setProperty";
+
+    public static final String GET_PROPERTY_ACTION = "getProperty";
+    public static final String JSP_GET_PROPERTY_ACTION = "jsp:getProperty";
+
+    public static final String INCLUDE_ACTION = "include";
+    public static final String JSP_INCLUDE_ACTION = "jsp:include";
+
+    public static final String FORWARD_ACTION = "forward";
+    public static final String JSP_FORWARD_ACTION = "jsp:forward";
+
+    public static final String PARAM_ACTION = "param";
+    public static final String JSP_PARAM_ACTION = "jsp:param";
+
+    public static final String PARAMS_ACTION = "params";
+    public static final String JSP_PARAMS_ACTION = "jsp:params";
+
+    public static final String PLUGIN_ACTION = "plugin";
+    public static final String JSP_PLUGIN_ACTION = "jsp:plugin";
+
+    public static final String FALLBACK_ACTION = "fallback";
+    public static final String JSP_FALLBACK_ACTION = "jsp:fallback";
+
+    public static final String TEXT_ACTION = "text";
+    public static final String JSP_TEXT_ACTION = "jsp:text";
+    public static final String JSP_TEXT_ACTION_END = "</jsp:text>";
+
+    public static final String ATTRIBUTE_ACTION = "attribute";
+    public static final String JSP_ATTRIBUTE_ACTION = "jsp:attribute";
+
+    public static final String BODY_ACTION = "body";
+    public static final String JSP_BODY_ACTION = "jsp:body";
+
+    public static final String ELEMENT_ACTION = "element";
+    public static final String JSP_ELEMENT_ACTION = "jsp:element";
+
+    public static final String OUTPUT_ACTION = "output";
+    public static final String JSP_OUTPUT_ACTION = "jsp:output";
+
+    public static final String TAGLIB_DIRECTIVE_ACTION = "taglib";
+    public static final String JSP_TAGLIB_DIRECTIVE_ACTION = "jsp:taglib";
+
+    /*
+     * Tag Files
+     */
+    public static final String INVOKE_ACTION = "invoke";
+    public static final String JSP_INVOKE_ACTION = "jsp:invoke";
+
+    public static final String DOBODY_ACTION = "doBody";
+    public static final String JSP_DOBODY_ACTION = "jsp:doBody";
+
+    /*
+     * Tag File Directives
+     */
+    public static final String TAG_DIRECTIVE_ACTION = "directive.tag";
+    public static final String JSP_TAG_DIRECTIVE_ACTION = "jsp:directive.tag";
+
+    public static final String ATTRIBUTE_DIRECTIVE_ACTION = "directive.attribute";
+    public static final String JSP_ATTRIBUTE_DIRECTIVE_ACTION = "jsp:directive.attribute";
+
+    public static final String VARIABLE_DIRECTIVE_ACTION = "directive.variable";
+    public static final String JSP_VARIABLE_DIRECTIVE_ACTION = "jsp:directive.variable";
+
+    /*
+     * Directive attributes
+     */
+    public static final String URN_JSPTAGDIR = "urn:jsptagdir:";
+    public static final String URN_JSPTLD = "urn:jsptld:";
+}