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 [6/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/JspReader.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspReader.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,657 @@
+/*
+ * 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.CharArrayWriter;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+
+/**
+ * JspReader is an input buffer for the JSP parser. It should allow
+ * unlimited lookahead and pushback. It also has a bunch of parsing
+ * utility methods for understanding htmlesque thingies.
+ *
+ * @author Anil K. Vijendran
+ * @author Anselm Baird-Smith
+ * @author Harish Prabandham
+ * @author Rajiv Mordani
+ * @author Mandar Raje
+ * @author Danno Ferrin
+ * @author Kin-man Chung
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+
+class JspReader {
+
+    /**
+     * Logger.
+     */
+    private Log log = LogFactory.getLog(JspReader.class);
+
+    /**
+     * The current spot in the file.
+     */
+    private Mark current;
+
+    /**
+     * What is this?
+     */
+    private String master;
+
+    /**
+     * The list of source files.
+     */
+    private List sourceFiles;
+
+    /**
+     * The current file ID (-1 indicates an error or no file).
+     */
+    private int currFileId;
+
+    /**
+     * Seems redundant.
+     */
+    private int size;
+
+    /**
+     * The compilation context.
+     */
+    private JspCompilationContext context;
+
+    /**
+     * The Jasper error dispatcher.
+     */
+    private ErrorDispatcher err;
+
+    /**
+     * Set to true when using the JspReader on a single file where we read up
+     * to the end and reset to the beginning many times.
+     * (as in ParserController.figureOutJspDocument()).
+     */
+    private boolean singleFile;
+
+    /**
+     * Constructor.
+     *
+     * @param ctxt The compilation context
+     * @param fname The file name
+     * @param encoding The file encoding
+     * @param jarFile ?
+     * @param err The error dispatcher
+     * @throws JasperException If a Jasper-internal error occurs
+     * @throws FileNotFoundException If the JSP file is not found (or is unreadable)
+     * @throws IOException If an IO-level error occurs, e.g. reading the file
+     */
+    public JspReader(JspCompilationContext ctxt,
+                     String fname,
+                     String encoding,
+                     JarFile jarFile,
+                     ErrorDispatcher err)
+            throws JasperException, FileNotFoundException, IOException {
+
+        this(ctxt, fname, encoding,
+             JspUtil.getReader(fname, encoding, jarFile, ctxt, err),
+             err);
+    }
+
+    /**
+     * Constructor: same as above constructor but with initialized reader
+     * to the file given.
+     */
+    public JspReader(JspCompilationContext ctxt,
+                     String fname,
+                     String encoding,
+                     InputStreamReader reader,
+                     ErrorDispatcher err)
+            throws JasperException, FileNotFoundException {
+
+        this.context = ctxt;
+        this.err = err;
+        sourceFiles = new Vector();
+        currFileId = 0;
+        size = 0;
+        singleFile = false;
+        pushFile(fname, encoding, reader);
+    }
+
+    /**
+     * @return JSP compilation context with which this JspReader is 
+     * associated
+     */
+    JspCompilationContext getJspCompilationContext() {
+        return context;
+    }
+    
+    /**
+     * Returns the file at the given position in the list.
+     *
+     * @param fileid The file position in the list
+     * @return The file at that position, if found, null otherwise
+     */
+    String getFile(final int fileid) {
+        return (String) sourceFiles.get(fileid);
+    }
+       
+    /**
+     * Checks if the current file has more input.
+     *
+     * @return True if more reading is possible
+     * @throws JasperException if an error occurs
+     */ 
+    boolean hasMoreInput() throws JasperException {
+        if (current.cursor >= current.stream.length) {
+            if (singleFile) return false; 
+            while (popFile()) {
+                if (current.cursor < current.stream.length) return true;
+            }
+            return false;
+        }
+        return true;
+    }
+    
+    int nextChar() throws JasperException {
+        if (!hasMoreInput())
+            return -1;
+        
+        int ch = current.stream[current.cursor];
+
+        current.cursor++;
+        
+        if (ch == '\n') {
+            current.line++;
+            current.col = 0;
+        } else {
+            current.col++;
+        }
+        return ch;
+    }
+
+    /**
+     * Back up the current cursor by one char, assumes current.cursor > 0,
+     * and that the char to be pushed back is not '\n'.
+     */
+    void pushChar() {
+        current.cursor--;
+        current.col--;
+    }
+
+    String getText(Mark start, Mark stop) throws JasperException {
+        Mark oldstart = mark();
+        reset(start);
+        CharArrayWriter caw = new CharArrayWriter();
+        while (!stop.equals(mark()))
+            caw.write(nextChar());
+        caw.close();
+        reset(oldstart);
+        return caw.toString();
+    }
+
+    int peekChar() throws JasperException {
+        if (!hasMoreInput())
+            return -1;
+        return current.stream[current.cursor];
+    }
+
+    Mark mark() {
+        return new Mark(current);
+    }
+
+    void reset(Mark mark) {
+        current = new Mark(mark);
+    }
+
+    boolean matchesIgnoreCase(String string) throws JasperException {
+        Mark mark = mark();
+        int ch = 0;
+        int i = 0;
+        do {
+            ch = nextChar();
+            if (Character.toLowerCase((char) ch) != string.charAt(i++)) {
+                reset(mark);
+                return false;
+            }
+        } while (i < string.length());
+        reset(mark);
+        return true;
+    }
+
+    /**
+     * search the stream for a match to a string
+     * @param string The string to match
+     * @return <strong>true</strong> is one is found, the current position
+     *         in stream is positioned after the search string, <strong>
+     *               false</strong> otherwise, position in stream unchanged.
+     */
+    boolean matches(String string) throws JasperException {
+        Mark mark = mark();
+        int ch = 0;
+        int i = 0;
+        do {
+            ch = nextChar();
+            if (((char) ch) != string.charAt(i++)) {
+                reset(mark);
+                return false;
+            }
+        } while (i < string.length());
+        return true;
+    }
+
+    boolean matchesETag(String tagName) throws JasperException {
+        Mark mark = mark();
+
+        if (!matches("</" + tagName))
+            return false;
+        skipSpaces();
+        if (nextChar() == '>')
+            return true;
+
+        reset(mark);
+        return false;
+    }
+
+    boolean matchesETagWithoutLessThan(String tagName)
+        throws JasperException
+    {
+       Mark mark = mark();
+
+       if (!matches("/" + tagName))
+           return false;
+       skipSpaces();
+       if (nextChar() == '>')
+           return true;
+
+       reset(mark);
+       return false;
+    }
+
+
+    /**
+     * Looks ahead to see if there are optional spaces followed by
+     * the given String.  If so, true is returned and those spaces and
+     * characters are skipped.  If not, false is returned and the
+     * position is restored to where we were before.
+     */
+    boolean matchesOptionalSpacesFollowedBy( String s )
+        throws JasperException
+    {
+        Mark mark = mark();
+
+        skipSpaces();
+        boolean result = matches( s );
+        if( !result ) {
+            reset( mark );
+        }
+
+        return result;
+    }
+
+    int skipSpaces() throws JasperException {
+        int i = 0;
+        while (hasMoreInput() && isSpace()) {
+            i++;
+            nextChar();
+        }
+        return i;
+    }
+
+    /**
+     * Skip until the given string is matched in the stream.
+     * When returned, the context is positioned past the end of the match.
+     *
+     * @param s The String to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         otherwise.
+     */
+    Mark skipUntil(String limit) throws JasperException {
+        Mark ret = null;
+        int limlen = limit.length();
+        int ch;
+
+    skip:
+        for (ret = mark(), ch = nextChar() ; ch != -1 ;
+                 ret = mark(), ch = nextChar()) {
+            if (ch == limit.charAt(0)) {
+                Mark restart = mark();
+                for (int i = 1 ; i < limlen ; i++) {
+                    if (peekChar() == limit.charAt(i))
+                        nextChar();
+                    else {
+                        reset(restart);
+                        continue skip;
+                    }
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Skip until the given string is matched in the stream, but ignoring
+     * chars initially escaped by a '\'.
+     * When returned, the context is positioned past the end of the match.
+     *
+     * @param s The String to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *         before the search string) if found, <strong>null</strong>
+     *         otherwise.
+     */
+    Mark skipUntilIgnoreEsc(String limit) throws JasperException {
+        Mark ret = null;
+        int limlen = limit.length();
+        int ch;
+        int prev = 'x';        // Doesn't matter
+        
+    skip:
+        for (ret = mark(), ch = nextChar() ; ch != -1 ;
+                 ret = mark(), prev = ch, ch = nextChar()) {            
+            if (ch == '\\' && prev == '\\') {
+                ch = 0;                // Double \ is not an escape char anymore
+            }
+            else if (ch == limit.charAt(0) && prev != '\\') {
+                for (int i = 1 ; i < limlen ; i++) {
+                    if (peekChar() == limit.charAt(i))
+                        nextChar();
+                    else
+                        continue skip;
+                }
+                return ret;
+            }
+        }
+        return null;
+    }
+    
+    /**
+     * Skip until the given end tag is matched in the stream.
+     * When returned, the context is positioned past the end of the tag.
+     *
+     * @param tag The name of the tag whose ETag (</tag>) to match.
+     * @return A non-null <code>Mark</code> instance (positioned immediately
+     *               before the ETag) if found, <strong>null</strong> otherwise.
+     */
+    Mark skipUntilETag(String tag) throws JasperException {
+        Mark ret = skipUntil("</" + tag);
+        if (ret != null) {
+            skipSpaces();
+            if (nextChar() != '>')
+                ret = null;
+        }
+        return ret;
+    }
+
+    final boolean isSpace() throws JasperException {
+        // Note: If this logic changes, also update Node.TemplateText.rtrim()
+        return peekChar() <= ' ';
+    }
+
+    /**
+     * Parse a space delimited token.
+     * If quoted the token will consume all characters up to a matching quote,
+     * otherwise, it consumes up to the first delimiter character.
+     *
+     * @param quoted If <strong>true</strong> accept quoted strings.
+     */
+    String parseToken(boolean quoted) throws JasperException {
+        StringBuffer stringBuffer = new StringBuffer();
+        skipSpaces();
+        stringBuffer.setLength(0);
+        
+        if (!hasMoreInput()) {
+            return "";
+        }
+
+        int ch = peekChar();
+        
+        if (quoted) {
+            if (ch == '"' || ch == '\'') {
+
+                char endQuote = ch == '"' ? '"' : '\'';
+                // Consume the open quote: 
+                ch = nextChar();
+                for (ch = nextChar(); ch != -1 && ch != endQuote;
+                         ch = nextChar()) {
+                    if (ch == '\\') 
+                        ch = nextChar();
+                    stringBuffer.append((char) ch);
+                }
+                // Check end of quote, skip closing quote:
+                if (ch == -1) {
+                    err.jspError(mark(), "jsp.error.quotes.unterminated");
+                }
+            } else {
+                err.jspError(mark(), "jsp.error.attr.quoted");
+            }
+        } else {
+            if (!isDelimiter()) {
+                // Read value until delimiter is found:
+                do {
+                    ch = nextChar();
+                    // Take care of the quoting here.
+                    if (ch == '\\') {
+                        if (peekChar() == '"' || peekChar() == '\'' ||
+                               peekChar() == '>' || peekChar() == '%')
+                            ch = nextChar();
+                    }
+                    stringBuffer.append((char) ch);
+                } while (!isDelimiter());
+            }
+        }
+
+        return stringBuffer.toString();
+    }
+
+    void setSingleFile(boolean val) {
+        singleFile = val;
+    }
+
+
+    /**
+     * Gets the URL for the given path name.
+     *
+     * @param path Path name
+     *
+     * @return URL for the given path name.
+     *
+     * @exception MalformedURLException if the path name is not given in 
+     * the correct form
+     */
+    URL getResource(String path) throws MalformedURLException {
+        return context.getResource(path);
+    }
+
+
+    /**
+     * Parse utils - Is current character a token delimiter ?
+     * Delimiters are currently defined to be =, &gt;, &lt;, ", and ' or any
+     * any space character as defined by <code>isSpace</code>.
+     *
+     * @return A boolean.
+     */
+    private boolean isDelimiter() throws JasperException {
+        if (! isSpace()) {
+            int ch = peekChar();
+            // Look for a single-char work delimiter:
+            if (ch == '=' || ch == '>' || ch == '"' || ch == '\''
+                    || ch == '/') {
+                return true;
+            }
+            // Look for an end-of-comment or end-of-tag:                
+            if (ch == '-') {
+                Mark mark = mark();
+                if (((ch = nextChar()) == '>')
+                        || ((ch == '-') && (nextChar() == '>'))) {
+                    reset(mark);
+                    return true;
+                } else {
+                    reset(mark);
+                    return false;
+                }
+            }
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Register a new source file.
+     * This method is used to implement file inclusion. Each included file
+     * gets a unique identifier (which is the index in the array of source
+     * files).
+     *
+     * @return The index of the now registered file.
+     */
+    private int registerSourceFile(final String file) {
+        if (sourceFiles.contains(file)) {
+            return -1;
+        }
+
+        sourceFiles.add(file);
+        this.size++;
+
+        return sourceFiles.size() - 1;
+    }
+    
+
+    /**
+     * Unregister the source file.
+     * This method is used to implement file inclusion. Each included file
+     * gets a uniq identifier (which is the index in the array of source
+     * files).
+     *
+     * @return The index of the now registered file.
+     */
+    private int unregisterSourceFile(final String file) {
+        if (!sourceFiles.contains(file)) {
+            return -1;
+        }
+
+        sourceFiles.remove(file);
+        this.size--;
+        return sourceFiles.size() - 1;
+    }
+
+    /**
+     * Push a file (and its associated Stream) on the file stack.  THe
+     * current position in the current file is remembered.
+     */
+    private void pushFile(String file, String encoding, 
+                           InputStreamReader reader) 
+                throws JasperException, FileNotFoundException {
+
+        // Register the file
+        String longName = file;
+
+        int fileid = registerSourceFile(longName);
+
+        if (fileid == -1) {
+            // Bugzilla 37407: http://issues.apache.org/bugzilla/show_bug.cgi?id=37407
+            if(reader != null) {
+                try {
+                    reader.close();
+                } catch (Exception any) {
+                    if(log.isDebugEnabled()) {
+                        log.debug("Exception closing reader: ", any);
+                    }
+                }
+            }
+
+            err.jspError("jsp.error.file.already.registered", file);
+        }
+
+        currFileId = fileid;
+
+        try {
+            CharArrayWriter caw = new CharArrayWriter();
+            char buf[] = new char[1024];
+            for (int i = 0 ; (i = reader.read(buf)) != -1 ;)
+                caw.write(buf, 0, i);
+            caw.close();
+            if (current == null) {
+                current = new Mark(this, caw.toCharArray(), fileid, 
+                                   getFile(fileid), master, encoding);
+            } else {
+                current.pushStream(caw.toCharArray(), fileid, getFile(fileid),
+                                   longName, encoding);
+            }
+        } catch (Throwable ex) {
+            log.error("Exception parsing file ", ex);
+            // Pop state being constructed:
+            popFile();
+            err.jspError("jsp.error.file.cannot.read", file);
+        } finally {
+            if (reader != null) {
+                try {
+                    reader.close();
+                } catch (Exception any) {
+                    if(log.isDebugEnabled()) {
+                        log.debug("Exception closing reader: ", any);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Pop a file from the file stack.  The field "current" is retored
+     * to the value to point to the previous files, if any, and is set
+     * to null otherwise.
+     * @return true is there is a previous file on the stack.
+     *         false otherwise.
+     */
+    private boolean popFile() throws JasperException {
+
+        // Is stack created ? (will happen if the Jsp file we're looking at is
+        // missing.
+        if (current == null || currFileId < 0) {
+            return false;
+        }
+
+        // Restore parser state:
+        String fName = getFile(currFileId);
+        currFileId = unregisterSourceFile(fName);
+        if (currFileId < -1) {
+            err.jspError("jsp.error.file.not.registered", fName);
+        }
+
+        Mark previous = current.popStream();
+        if (previous != null) {
+            master = current.baseDir;
+            current = previous;
+            return true;
+        }
+        // Note that although the current file is undefined here, "current"
+        // is not set to null just for convience, for it maybe used to
+        // set the current (undefined) position.
+        return false;
+    }
+}
+

Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspRuntimeContext.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,525 @@
+/*
+ * 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.FileNotFoundException;
+import java.io.FilePermission;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.PermissionCollection;
+import java.security.Policy;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.jsp.JspFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.apache.struts2.jasper.Options;
+import org.apache.struts2.jasper.runtime.JspFactoryImpl;
+import org.apache.struts2.jasper.security.SecurityClassLoad;
+import org.apache.struts2.jasper.servlet.JspServletWrapper;
+
+/**
+ * Class for tracking JSP compile time file dependencies when the
+ * &060;%@include file="..."%&062; directive is used.
+ *
+ * A background thread periodically checks the files a JSP page
+ * is dependent upon.  If a dpendent file changes the JSP page
+ * which included it is recompiled.
+ *
+ * Only used if a web application context is a directory.
+ *
+ * @author Glenn L. Nielsen
+ * @version $Revision: 466606 $
+ */
+public final class JspRuntimeContext implements Runnable {
+
+    // Logger
+    private Log log = LogFactory.getLog(JspRuntimeContext.class);
+
+    /*
+     * Counts how many times the webapp's JSPs have been reloaded.
+     */
+    private int jspReloadCount;
+
+    /**
+     * Preload classes required at runtime by a JSP servlet so that
+     * we don't get a defineClassInPackage security exception.
+     */
+    static {
+        JspFactoryImpl factory = new JspFactoryImpl();
+        SecurityClassLoad.securityClassLoad(factory.getClass().getClassLoader());
+        JspFactory.setDefaultFactory(factory);
+    }
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Create a JspRuntimeContext for a web application context.
+     *
+     * Loads in any previously generated dependencies from file.
+     *
+     * @param context ServletContext for web application
+     */
+    public JspRuntimeContext(ServletContext context, Options options) {
+
+        this.context = context;
+        this.options = options;
+
+        // Get the parent class loader
+        parentClassLoader =
+            (URLClassLoader) Thread.currentThread().getContextClassLoader();
+        if (parentClassLoader == null) {
+            parentClassLoader =
+                (URLClassLoader)this.getClass().getClassLoader();
+        }
+
+	if (log.isDebugEnabled()) {
+	    if (parentClassLoader != null) {
+		log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
+					       parentClassLoader.toString()));
+	    } else {
+		log.debug(Localizer.getMessage("jsp.message.parent_class_loader_is",
+					       "<none>"));
+	    }
+        }
+
+        initClassPath();
+
+	if (context instanceof org.apache.struts2.jasper.servlet.JspCServletContext) {
+	    return;
+	}
+
+        if (System.getSecurityManager() != null) {
+            initSecurity();
+        }
+
+        // If this web application context is running from a
+        // directory, start the background compilation thread
+        String appBase = context.getRealPath("/");         
+        if (!options.getDevelopment()
+                && appBase != null
+                && options.getCheckInterval() > 0) {
+            if (appBase.endsWith(File.separator) ) {
+                appBase = appBase.substring(0,appBase.length()-1);
+            }
+            String directory =
+                appBase.substring(appBase.lastIndexOf(File.separator));
+            threadName = threadName + "[" + directory + "]";
+            threadStart();
+        }                                            
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * This web applications ServletContext
+     */
+    private ServletContext context;
+    private Options options;
+    private URLClassLoader parentClassLoader;
+    private PermissionCollection permissionCollection;
+    private CodeSource codeSource;                    
+    private String classpath;
+
+    /**
+     * Maps JSP pages to their JspServletWrapper's
+     */
+    private Map jsps = Collections.synchronizedMap( new HashMap());
+ 
+
+    /**
+     * The background thread.
+     */
+    private Thread thread = null;
+
+
+    /**
+     * The background thread completion semaphore.
+     */
+    private boolean threadDone = false;
+
+
+    /**
+     * Name to register for the background thread.
+     */
+    private String threadName = "JspRuntimeContext";
+
+    // ------------------------------------------------------ Public Methods
+
+    /**
+     * Add a new JspServletWrapper.
+     *
+     * @param jspUri JSP URI
+     * @param jsw Servlet wrapper for JSP
+     */
+    public void addWrapper(String jspUri, JspServletWrapper jsw) {
+        jsps.remove(jspUri);
+        jsps.put(jspUri,jsw);
+    }
+
+    /**
+     * Get an already existing JspServletWrapper.
+     *
+     * @param jspUri JSP URI
+     * @return JspServletWrapper for JSP
+     */
+    public JspServletWrapper getWrapper(String jspUri) {
+        return (JspServletWrapper) jsps.get(jspUri);
+    }
+
+    /**
+     * Remove a  JspServletWrapper.
+     *
+     * @param jspUri JSP URI of JspServletWrapper to remove
+     */
+    public void removeWrapper(String jspUri) {
+        jsps.remove(jspUri);
+    }
+
+    /**
+     * Returns the number of JSPs for which JspServletWrappers exist, i.e.,
+     * the number of JSPs that have been loaded into the webapp.
+     *
+     * @return The number of JSPs that have been loaded into the webapp
+     */
+    public int getJspCount() {
+        return jsps.size();
+    }
+
+    /**
+     * Get the SecurityManager Policy CodeSource for this web
+     * applicaiton context.
+     *
+     * @return CodeSource for JSP
+     */
+    public CodeSource getCodeSource() {
+        return codeSource;
+    }
+
+    /**
+     * Get the parent URLClassLoader.
+     *
+     * @return URLClassLoader parent
+     */
+    public URLClassLoader getParentClassLoader() {
+        return parentClassLoader;
+    }
+
+    /**
+     * Get the SecurityManager PermissionCollection for this
+     * web application context.
+     *
+     * @return PermissionCollection permissions
+     */
+    public PermissionCollection getPermissionCollection() {
+        return permissionCollection;
+    }
+
+    /**
+     * Process a "destory" event for this web application context.
+     */                                                        
+    public void destroy() {
+        threadStop();
+
+        Iterator servlets = jsps.values().iterator();
+        while (servlets.hasNext()) {
+            ((JspServletWrapper) servlets.next()).destroy();
+        }
+    }
+
+    /**
+     * Increments the JSP reload counter.
+     */
+    public synchronized void incrementJspReloadCount() {
+        jspReloadCount++;
+    }
+
+    /**
+     * Resets the JSP reload counter.
+     *
+     * @param count Value to which to reset the JSP reload counter
+     */
+    public synchronized void setJspReloadCount(int count) {
+        this.jspReloadCount = count;
+    }
+
+    /**
+     * Gets the current value of the JSP reload counter.
+     *
+     * @return The current value of the JSP reload counter
+     */
+    public int getJspReloadCount() {
+        return jspReloadCount;
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+    /**
+     * Method used by background thread to check the JSP dependencies
+     * registered with this class for JSP's.
+     */
+    private void checkCompile() {
+        Object [] wrappers = jsps.values().toArray();
+        for (int i = 0; i < wrappers.length; i++ ) {
+            JspServletWrapper jsw = (JspServletWrapper)wrappers[i];
+            JspCompilationContext ctxt = jsw.getJspEngineContext();
+            // JspServletWrapper also synchronizes on this when
+            // it detects it has to do a reload
+            synchronized(jsw) {
+                try {
+                    ctxt.compile();
+                } catch (FileNotFoundException ex) {
+                    ctxt.incrementRemoved();
+                } catch (Throwable t) {
+                    jsw.getServletContext().log("Background compile failed",
+						t);
+                }
+            }
+        }
+    }
+
+    /**
+     * The classpath that is passed off to the Java compiler.
+     */
+    public String getClassPath() {
+        return classpath;
+    }
+
+    /**
+     * Method used to initialize classpath for compiles.
+     */
+    private void initClassPath() {
+
+        URL [] urls = parentClassLoader.getURLs();
+        StringBuffer cpath = new StringBuffer();
+        String sep = System.getProperty("path.separator");
+
+        for(int i = 0; i < urls.length; i++) {
+            // Tomcat 4 can use URL's other than file URL's,
+            // a protocol other than file: will generate a
+            // bad file system path, so only add file:
+            // protocol URL's to the classpath.
+            
+            if( urls[i].getProtocol().equals("file") ) {
+                cpath.append((String)urls[i].getFile()+sep);
+            }
+        }    
+
+	cpath.append(options.getScratchDir() + sep);
+
+        String cp = (String) context.getAttribute(Constants.SERVLET_CLASSPATH);
+        if (cp == null || cp.equals("")) {
+            cp = options.getClassPath();
+        }
+
+        classpath = cpath.toString() + cp;
+
+        if(log.isDebugEnabled()) {
+            log.debug("Compilation classpath initialized: " + getClassPath());
+        }
+    }
+
+    /**
+     * Method used to initialize SecurityManager data.
+     */
+    private void initSecurity() {
+
+        // Setup the PermissionCollection for this web app context
+        // based on the permissions configured for the root of the
+        // web app context directory, then add a file read permission
+        // for that directory.
+        Policy policy = Policy.getPolicy();
+        if( policy != null ) {
+            try {          
+                // Get the permissions for the web app context
+                String docBase = context.getRealPath("/");
+                if( docBase == null ) {
+                    docBase = options.getScratchDir().toString();
+                }
+                String codeBase = docBase;
+                if (!codeBase.endsWith(File.separator)){
+                    codeBase = codeBase + File.separator;
+                }
+                File contextDir = new File(codeBase);
+                URL url = contextDir.getCanonicalFile().toURL();
+                codeSource = new CodeSource(url,(Certificate[])null);
+                permissionCollection = policy.getPermissions(codeSource);
+
+                // Create a file read permission for web app context directory
+                if (!docBase.endsWith(File.separator)){
+                    permissionCollection.add
+                        (new FilePermission(docBase,"read"));
+                    docBase = docBase + File.separator;
+                } else {
+                    permissionCollection.add
+                        (new FilePermission
+                            (docBase.substring(0,docBase.length() - 1),"read"));
+                }
+                docBase = docBase + "-";
+                permissionCollection.add(new FilePermission(docBase,"read"));
+
+                // Create a file read permission for web app tempdir (work)
+                // directory
+                String workDir = options.getScratchDir().toString();
+                if (!workDir.endsWith(File.separator)){
+                    permissionCollection.add
+                        (new FilePermission(workDir,"read"));
+                    workDir = workDir + File.separator;
+                }
+                workDir = workDir + "-";
+                permissionCollection.add(new FilePermission(workDir,"read"));
+
+                // Allow the JSP to access org.apache.struts2.jasper.runtime.HttpJspBase
+                permissionCollection.add( new RuntimePermission(
+                    "accessClassInPackage.org.apache.struts2.jasper.runtime") );
+
+                if (parentClassLoader instanceof URLClassLoader) {
+                    URL [] urls = parentClassLoader.getURLs();
+                    String jarUrl = null;
+                    String jndiUrl = null;
+                    for (int i=0; i<urls.length; i++) {
+                        if (jndiUrl == null
+                                && urls[i].toString().startsWith("jndi:") ) {
+                            jndiUrl = urls[i].toString() + "-";
+                        }
+                        if (jarUrl == null
+                                && urls[i].toString().startsWith("jar:jndi:")
+                                ) {
+                            jarUrl = urls[i].toString();
+                            jarUrl = jarUrl.substring(0,jarUrl.length() - 2);
+                            jarUrl = jarUrl.substring(0,
+                                     jarUrl.lastIndexOf('/')) + "/-";
+                        }
+                    }
+                    if (jarUrl != null) {
+                        permissionCollection.add(
+                                new FilePermission(jarUrl,"read"));
+                        permissionCollection.add(
+                                new FilePermission(jarUrl.substring(4),"read"));
+                    }
+                    if (jndiUrl != null)
+                        permissionCollection.add(
+                                new FilePermission(jndiUrl,"read") );
+                }
+            } catch(Exception e) {
+                context.log("Security Init for context failed",e);
+            }
+        }
+    }
+
+
+    // -------------------------------------------------------- Thread Support
+
+    /**
+     * Start the background thread that will periodically check for
+     * changes to compile time included files in a JSP.
+     *
+     * @exception IllegalStateException if we should not be starting
+     *  a background thread now
+     */
+    protected void threadStart() {
+
+        // Has the background thread already been started?
+        if (thread != null) {
+            return;
+        }
+
+        // Start the background thread
+        threadDone = false;
+        thread = new Thread(this, threadName);
+        thread.setDaemon(true);
+        thread.start();
+
+    }
+
+
+    /**
+     * Stop the background thread that is periodically checking for
+     * changes to compile time included files in a JSP.
+     */ 
+    protected void threadStop() {
+
+        if (thread == null) {
+            return;
+        }
+
+        threadDone = true;
+        thread.interrupt();
+        try {
+            thread.join();
+        } catch (InterruptedException e) {
+            ;
+        }
+        
+        thread = null;
+        
+    }
+
+    /**
+     * Sleep for the duration specified by the <code>checkInterval</code>
+     * property.
+     */ 
+    protected void threadSleep() {
+        
+        try {
+            Thread.sleep(options.getCheckInterval() * 1000L);
+        } catch (InterruptedException e) {
+            ;
+        }
+        
+    }   
+    
+    
+    // ------------------------------------------------------ Background Thread
+        
+        
+    /**
+     * The background thread that checks for changes to files
+     * included by a JSP and flags that a recompile is required.
+     */ 
+    public void run() {
+        
+        // Loop until the termination semaphore is set
+        while (!threadDone) {
+
+            // Wait for our check interval
+            threadSleep();
+
+            // Check for included files which are newer than the
+            // JSP which uses them.
+            try {
+                checkCompile();
+            } catch (Throwable t) {
+                log.error("Exception checking if recompile needed: ", t);
+            }
+        }
+        
+    }
+
+}

Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/JspUtil.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,1109 @@
+/*
+ * 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.CharArrayWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.util.Vector;
+import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
+
+import javax.servlet.jsp.el.ELException;
+import javax.servlet.jsp.el.ELParseException;
+import javax.servlet.jsp.el.FunctionMapper;
+
+import org.apache.commons.el.ExpressionEvaluatorImpl;
+import org.apache.struts2.jasper.Constants;
+import org.apache.struts2.jasper.JasperException;
+import org.apache.struts2.jasper.JspCompilationContext;
+import org.xml.sax.Attributes;
+
+/** 
+ * This class has all the utility method(s).
+ * Ideally should move all the bean containers here.
+ *
+ * @author Mandar Raje.
+ * @author Rajiv Mordani.
+ * @author Danno Ferrin
+ * @author Pierre Delisle
+ * @author Shawn Bayern
+ * @author Mark Roth
+ */
+public class JspUtil {
+
+    private static final String WEB_INF_TAGS = "/WEB-INF/tags/";
+    private static final String META_INF_TAGS = "/META-INF/tags/";
+
+    // Delimiters for request-time expressions (JSP and XML syntax)
+    private static final String OPEN_EXPR  = "<%=";
+    private static final String CLOSE_EXPR = "%>";
+    private static final String OPEN_EXPR_XML  = "%=";
+    private static final String CLOSE_EXPR_XML = "%";
+
+    private static int tempSequenceNumber = 0;
+    private static ExpressionEvaluatorImpl expressionEvaluator
+	= new ExpressionEvaluatorImpl();
+
+    private static final String javaKeywords[] = {
+        "abstract", "assert", "boolean", "break", "byte", "case",
+        "catch", "char", "class", "const", "continue",
+        "default", "do", "double", "else", "enum", "extends",
+        "final", "finally", "float", "for", "goto",
+        "if", "implements", "import", "instanceof", "int",
+        "interface", "long", "native", "new", "package",
+        "private", "protected", "public", "return", "short",
+        "static", "strictfp", "super", "switch", "synchronized",
+        "this", "throws", "transient", "try", "void",
+        "volatile", "while" };
+
+    public static final int CHUNKSIZE = 1024;
+        
+    public static char[] removeQuotes(char []chars) {
+        CharArrayWriter caw = new CharArrayWriter();
+        for (int i = 0; i < chars.length; i++) {
+            if (chars[i] == '%' && chars[i+1] == '\\' &&
+                chars[i+2] == '>') {
+                caw.write('%');
+                caw.write('>');
+                i = i + 2;
+            } else {
+                caw.write(chars[i]);
+            }
+        }
+        return caw.toCharArray();
+    }
+
+    public static char[] escapeQuotes (char []chars) {
+        // Prescan to convert %\> to %>
+        String s = new String(chars);
+        while (true) {
+            int n = s.indexOf("%\\>");
+            if (n < 0)
+                break;
+            StringBuffer sb = new StringBuffer(s.substring(0, n));
+            sb.append("%>");
+            sb.append(s.substring(n + 3));
+            s = sb.toString();
+        }
+        chars = s.toCharArray();
+        return (chars);
+
+
+        // Escape all backslashes not inside a Java string literal
+        /*
+        CharArrayWriter caw = new CharArrayWriter();
+        boolean inJavaString = false;
+        for (int i = 0; i < chars.length; i++) {
+            if (chars[i] == '"') inJavaString = !inJavaString;
+            // escape out the escape character
+            if (!inJavaString && (chars[i] == '\\')) caw.write('\\');
+            caw.write(chars[i]);
+        }
+        return caw.toCharArray();
+        */
+    }
+
+    /**
+     * Checks if the token is a runtime expression.
+     * In standard JSP syntax, a runtime expression starts with '<%' and
+     * ends with '%>'. When the JSP document is in XML syntax, a runtime
+     * expression starts with '%=' and ends with '%'.
+     *
+     * @param token The token to be checked
+     * return whether the token is a runtime expression or not.
+     */
+    public static boolean isExpression(String token, boolean isXml) {
+	String openExpr;
+	String closeExpr;
+	if (isXml) {
+	    openExpr = OPEN_EXPR_XML;
+	    closeExpr = CLOSE_EXPR_XML;
+	} else {
+	    openExpr = OPEN_EXPR;
+	    closeExpr = CLOSE_EXPR;
+	}
+	if (token.startsWith(openExpr) && token.endsWith(closeExpr)) {
+	    return true;
+	} else {
+	    return false;
+	}
+    }
+
+    /**
+     * @return the "expression" part of a runtime expression, 
+     * taking the delimiters out.
+     */
+    public static String getExpr (String expression, boolean isXml) {
+	String returnString;
+	String openExpr;
+	String closeExpr;
+	if (isXml) {
+	    openExpr = OPEN_EXPR_XML;
+	    closeExpr = CLOSE_EXPR_XML;
+	} else {
+	    openExpr = OPEN_EXPR;
+	    closeExpr = CLOSE_EXPR;
+	}
+	int length = expression.length();
+	if (expression.startsWith(openExpr) && 
+                expression.endsWith(closeExpr)) {
+	    returnString = expression.substring(
+                               openExpr.length(), length - closeExpr.length());
+	} else {
+	    returnString = "";
+	}
+	return returnString;
+    }
+
+    /**
+     * Takes a potential expression and converts it into XML form
+     */
+    public static String getExprInXml(String expression) {
+        String returnString;
+        int length = expression.length();
+
+        if (expression.startsWith(OPEN_EXPR) 
+                && expression.endsWith(CLOSE_EXPR)) {
+            returnString = expression.substring (1, length - 1);
+        } else {
+            returnString = expression;
+        }
+
+        return escapeXml(returnString.replace(Constants.HACK_CHAR, '$'));
+    }
+
+    /**
+     * Checks to see if the given scope is valid.
+     *
+     * @param scope The scope to be checked
+     * @param n The Node containing the 'scope' attribute whose value is to be
+     * checked
+     * @param err error dispatcher
+     *
+     * @throws JasperException if scope is not null and different from
+     * &quot;page&quot;, &quot;request&quot;, &quot;session&quot;, and
+     * &quot;application&quot;
+     */
+    public static void checkScope(String scope, Node n, ErrorDispatcher err)
+            throws JasperException {
+	if (scope != null && !scope.equals("page") && !scope.equals("request")
+		&& !scope.equals("session") && !scope.equals("application")) {
+	    err.jspError(n, "jsp.error.invalid.scope", scope);
+	}
+    }
+
+    /**
+     * Checks if all mandatory attributes are present and if all attributes
+     * present have valid names.  Checks attributes specified as XML-style
+     * attributes as well as attributes specified using the jsp:attribute
+     * standard action. 
+     */
+    public static void checkAttributes(String typeOfTag,
+				       Node n,
+				       ValidAttribute[] validAttributes,
+				       ErrorDispatcher err)
+				throws JasperException {
+        Attributes attrs = n.getAttributes();
+        Mark start = n.getStart();
+	boolean valid = true;
+
+        // AttributesImpl.removeAttribute is broken, so we do this...
+        int tempLength = (attrs == null) ? 0 : attrs.getLength();
+	Vector temp = new Vector(tempLength, 1);
+        for (int i = 0; i < tempLength; i++) {
+            String qName = attrs.getQName(i);
+            if ((!qName.equals("xmlns")) && (!qName.startsWith("xmlns:")))
+                temp.addElement(qName);
+        }
+
+        // Add names of attributes specified using jsp:attribute
+        Node.Nodes tagBody = n.getBody();
+        if( tagBody != null ) {
+            int numSubElements = tagBody.size();
+            for( int i = 0; i < numSubElements; i++ ) {
+                Node node = tagBody.getNode( i );
+                if( node instanceof Node.NamedAttribute ) {
+                    String attrName = node.getAttributeValue( "name" );
+                    temp.addElement( attrName );
+		    // Check if this value appear in the attribute of the node
+		    if (n.getAttributeValue(attrName) != null) {
+			err.jspError(n, "jsp.error.duplicate.name.jspattribute",
+					attrName);
+		    }
+                }
+                else {
+                    // Nothing can come before jsp:attribute, and only
+                    // jsp:body can come after it.
+                    break;
+                }
+            }
+        }
+
+	/*
+	 * First check to see if all the mandatory attributes are present.
+	 * If so only then proceed to see if the other attributes are valid
+	 * for the particular tag.
+	 */
+	String missingAttribute = null;
+
+	for (int i = 0; i < validAttributes.length; i++) {
+	    int attrPos;    
+	    if (validAttributes[i].mandatory) {
+                attrPos = temp.indexOf(validAttributes[i].name);
+		if (attrPos != -1) {
+		    temp.remove(attrPos);
+		    valid = true;
+		} else {
+		    valid = false;
+		    missingAttribute = validAttributes[i].name;
+		    break;
+		}
+	    }
+	}
+
+	// If mandatory attribute is missing then the exception is thrown
+	if (!valid)
+	    err.jspError(start, "jsp.error.mandatory.attribute", typeOfTag,
+			 missingAttribute);
+
+	// Check to see if there are any more attributes for the specified tag.
+        int attrLeftLength = temp.size();
+	if (attrLeftLength == 0)
+	    return;
+
+	// Now check to see if the rest of the attributes are valid too.
+	String attribute = null;
+
+	for (int j = 0; j < attrLeftLength; j++) {
+	    valid = false;
+	    attribute = (String) temp.elementAt(j);
+	    for (int i = 0; i < validAttributes.length; i++) {
+		if (attribute.equals(validAttributes[i].name)) {
+		    valid = true;
+		    break;
+		}
+	    }
+	    if (!valid)
+		err.jspError(start, "jsp.error.invalid.attribute", typeOfTag,
+			     attribute);
+	}
+	// XXX *could* move EL-syntax validation here... (sb)
+    }
+    
+    public static String escapeQueryString(String unescString) {
+	if ( unescString == null )
+	    return null;
+	
+	String escString    = "";
+	String shellSpChars = "\\\"";
+	
+	for(int index=0; index<unescString.length(); index++) {
+	    char nextChar = unescString.charAt(index);
+	    
+	    if( shellSpChars.indexOf(nextChar) != -1 )
+		escString += "\\";
+	    
+	    escString += nextChar;
+	}
+	return escString;
+    }
+ 
+    /**
+     *  Escape the 5 entities defined by XML.
+     */
+    public static String escapeXml(String s) {
+        if (s == null) return null;
+        StringBuffer sb = new StringBuffer();
+        for(int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '<') {
+                sb.append("&lt;");
+            } else if (c == '>') {
+                sb.append("&gt;");
+            } else if (c == '\'') {
+                sb.append("&apos;");
+            } else if (c == '&') {
+                sb.append("&amp;");
+            } else if (c == '"') {
+                sb.append("&quot;");
+            } else {
+                sb.append(c);
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Replaces any occurrences of the character <tt>replace</tt> with the
+     * string <tt>with</tt>.
+     */
+    public static String replace(String name, char replace, String with) {
+	StringBuffer buf = new StringBuffer();
+	int begin = 0;
+	int end;
+	int last = name.length();
+
+	while (true) {
+	    end = name.indexOf(replace, begin);
+	    if (end < 0) {
+		end = last;
+	    }
+	    buf.append(name.substring(begin, end));
+	    if (end == last) {
+		break;
+	    }
+	    buf.append(with);
+	    begin = end + 1;
+	}
+	
+	return buf.toString();
+    }
+
+    public static class ValidAttribute {
+	String name;
+	boolean mandatory;
+	boolean rtexprvalue;	// not used now
+
+	public ValidAttribute (String name, boolean mandatory,
+            boolean rtexprvalue )
+        {
+	    this.name = name;
+	    this.mandatory = mandatory;
+            this.rtexprvalue = rtexprvalue;
+        }
+
+       public ValidAttribute (String name, boolean mandatory) {
+            this( name, mandatory, false );
+	}
+
+	public ValidAttribute (String name) {
+	    this (name, false);
+	}
+    }
+    
+    /**
+     * Convert a String value to 'boolean'.
+     * Besides the standard conversions done by
+     * Boolean.valueOf(s).booleanValue(), the value "yes"
+     * (ignore case) is also converted to 'true'. 
+     * If 's' is null, then 'false' is returned.
+     *
+     * @param s the string to be converted
+     * @return the boolean value associated with the string s
+     */
+    public static boolean booleanValue(String s) {
+	boolean b = false;
+	if (s != null) {
+	    if (s.equalsIgnoreCase("yes")) {
+		b = true;
+	    } else {
+		b = Boolean.valueOf(s).booleanValue();
+	    }
+	}
+	return b;
+    }
+
+    /**
+     * Returns the <tt>Class</tt> object associated with the class or
+     * interface with the given string name.
+     *
+     * <p> The <tt>Class</tt> object is determined by passing the given string
+     * name to the <tt>Class.forName()</tt> method, unless the given string
+     * name represents a primitive type, in which case it is converted to a
+     * <tt>Class</tt> object by appending ".class" to it (e.g., "int.class").
+     */
+    public static Class toClass(String type, ClassLoader loader)
+	    throws ClassNotFoundException {
+
+	Class c = null;
+	int i0 = type.indexOf('[');
+	int dims = 0;
+	if (i0 > 0) {
+	    // This is an array.  Count the dimensions
+	    for (int i = 0; i < type.length(); i++) {
+		if (type.charAt(i) == '[')
+		    dims++;
+	    }
+	    type = type.substring(0, i0);
+	}
+
+	if ("boolean".equals(type))
+	    c = boolean.class;
+	else if ("char".equals(type))
+	    c = char.class;
+	else if ("byte".equals(type))
+	    c =  byte.class;
+	else if ("short".equals(type))
+	    c = short.class;
+	else if ("int".equals(type))
+	    c = int.class;
+	else if ("long".equals(type))
+	    c = long.class;
+	else if ("float".equals(type))
+	    c = float.class;
+	else if ("double".equals(type))
+	    c = double.class;
+	else if (type.indexOf('[') < 0)
+	    c = loader.loadClass(type);
+
+	if (dims == 0)
+	    return c;
+
+	if (dims == 1)
+	    return java.lang.reflect.Array.newInstance(c, 1).getClass();
+
+	// Array of more than i dimension
+	return java.lang.reflect.Array.newInstance(c, new int[dims]).getClass();
+    }
+
+    /**
+     * Produces a String representing a call to the EL interpreter.
+     * @param expression a String containing zero or more "${}" expressions
+     * @param expectedType the expected type of the interpreted result
+     * @param fnmapvar Variable pointing to a function map.
+     * @param XmlEscape True if the result should do XML escaping
+     * @return a String representing a call to the EL interpreter.
+     */
+    public static String interpreterCall(boolean isTagFile,
+					 String expression,
+                                         Class expectedType,
+                                         String fnmapvar,
+                                         boolean XmlEscape ) 
+    {
+        /*
+         * Determine which context object to use.
+         */
+	String jspCtxt = null;
+	if (isTagFile)
+	    jspCtxt = "this.getJspContext()";
+	else
+	    jspCtxt = "_jspx_page_context";
+
+	/*
+         * Determine whether to use the expected type's textual name
+	 * or, if it's a primitive, the name of its correspondent boxed
+	 * type.
+         */
+	String targetType = expectedType.getName();
+	String primitiveConverterMethod = null;
+	if (expectedType.isPrimitive()) {
+	    if (expectedType.equals(Boolean.TYPE)) {
+		targetType = Boolean.class.getName();
+		primitiveConverterMethod = "booleanValue";
+	    } else if (expectedType.equals(Byte.TYPE)) {
+		targetType = Byte.class.getName();
+		primitiveConverterMethod = "byteValue";
+	    } else if (expectedType.equals(Character.TYPE)) {
+		targetType = Character.class.getName();
+		primitiveConverterMethod = "charValue";
+	    } else if (expectedType.equals(Short.TYPE)) {
+		targetType = Short.class.getName();
+		primitiveConverterMethod = "shortValue";
+	    } else if (expectedType.equals(Integer.TYPE)) {
+		targetType = Integer.class.getName();
+		primitiveConverterMethod = "intValue";
+	    } else if (expectedType.equals(Long.TYPE)) {
+		targetType = Long.class.getName();
+		primitiveConverterMethod = "longValue";
+	    } else if (expectedType.equals(Float.TYPE)) {
+		targetType = Float.class.getName();
+		primitiveConverterMethod = "floatValue";
+	    } else if (expectedType.equals(Double.TYPE)) { 
+		targetType = Double.class.getName();
+		primitiveConverterMethod = "doubleValue";
+	    }
+	}
+ 
+	if (primitiveConverterMethod != null) {
+	    XmlEscape = false;
+	}
+
+	/*
+         * Build up the base call to the interpreter.
+         */
+        // XXX - We use a proprietary call to the interpreter for now
+        // as the current standard machinery is inefficient and requires
+        // lots of wrappers and adapters.  This should all clear up once
+        // the EL interpreter moves out of JSTL and into its own project.
+        // In the future, this should be replaced by code that calls
+        // ExpressionEvaluator.parseExpression() and then cache the resulting
+        // expression objects.  The interpreterCall would simply select
+        // one of the pre-cached expressions and evaluate it.
+        // Note that PageContextImpl implements VariableResolver and
+        // the generated Servlet/SimpleTag implements FunctionMapper, so
+        // that machinery is already in place (mroth).
+	targetType = toJavaSourceType(targetType);
+	StringBuffer call = new StringBuffer(
+             "(" + targetType + ") "
+               + "org.apache.struts2.jasper.runtime.PageContextImpl.proprietaryEvaluate"
+               + "(" + Generator.quote(expression) + ", "
+               +       targetType + ".class, "
+	       +       "(PageContext)" + jspCtxt 
+               +       ", " + fnmapvar
+	       + ", " + XmlEscape
+               + ")");
+ 
+	/*
+         * Add the primitive converter method if we need to.
+         */
+	if (primitiveConverterMethod != null) {
+	    call.insert(0, "(");
+	    call.append(")." + primitiveConverterMethod + "()");
+	}
+ 
+	return call.toString();
+    }
+
+    /**
+     * Validates the syntax of all ${} expressions within the given string.
+     * @param where the approximate location of the expressions in the JSP page
+     * @param expressions a string containing zero or more "${}" expressions
+     * @param err an error dispatcher to use
+     */
+    public static void validateExpressions(Mark where,
+                                           String expressions,
+                                           Class expectedType,
+                                           FunctionMapper functionMapper,
+                                           ErrorDispatcher err)
+            throws JasperException {
+
+        try {
+            JspUtil.expressionEvaluator.parseExpression( expressions, 
+                expectedType, null );
+        }
+        catch( ELParseException e ) {
+            err.jspError(where, "jsp.error.invalid.expression", expressions,
+                e.toString() );
+        }
+        catch( ELException e ) {
+            err.jspError(where, "jsp.error.invalid.expression", expressions,
+                e.toString() );
+        }
+    }
+
+    /**
+     * Resets the temporary variable name.
+     * (not thread-safe)
+     */
+    public static void resetTemporaryVariableName() {
+        tempSequenceNumber = 0;
+    }
+
+    /**
+     * Generates a new temporary variable name.
+     * (not thread-safe)
+     */
+    public static String nextTemporaryVariableName() {
+        return Constants.TEMP_VARIABLE_NAME_PREFIX + (tempSequenceNumber++);
+    }
+
+    public static String coerceToPrimitiveBoolean(String s,
+						  boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToBoolean(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "false";
+	    else
+		return Boolean.valueOf(s).toString();
+	}
+    }
+
+    public static String coerceToBoolean(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Boolean) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Boolean.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Boolean(false)";
+	    } else {
+		// Detect format error at translation time
+		return "new Boolean(" + Boolean.valueOf(s).toString() + ")";
+	    }
+	}
+    }
+
+    public static String coerceToPrimitiveByte(String s,
+					       boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToByte(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "(byte) 0";
+	    else
+		return "((byte)" + Byte.valueOf(s).toString() + ")";
+	}
+    }
+
+    public static String coerceToByte(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Byte) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Byte.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Byte((byte) 0)";
+	    } else {
+		// Detect format error at translation time
+		return "new Byte((byte)" + Byte.valueOf(s).toString() + ")";
+	    }
+	}
+    }
+
+    public static String coerceToChar(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToChar(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "(char) 0";
+	    } else {
+		char ch = s.charAt(0);
+		// this trick avoids escaping issues
+		return "((char) " + (int) ch + ")";
+	    }
+	}
+    }
+
+    public static String coerceToCharacter(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Character) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Character.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Character((char) 0)";
+	    } else {
+		char ch = s.charAt(0);
+		// this trick avoids escaping issues
+		return "new Character((char) " + (int) ch + ")";
+	    }
+	}
+    }
+
+    public static String coerceToPrimitiveDouble(String s,
+						 boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToDouble(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "(double) 0";
+	    else
+		return Double.valueOf(s).toString();
+	}
+    }
+
+    public static String coerceToDouble(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Double) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Double.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Double(0)";
+	    } else {
+		// Detect format error at translation time
+		return "new Double(" + Double.valueOf(s).toString() + ")";
+	    }
+	}
+    }
+
+    public static String coerceToPrimitiveFloat(String s,
+						boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToFloat(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "(float) 0";
+	    else
+		return Float.valueOf(s).toString() + "f";
+	}
+    }
+
+    public static String coerceToFloat(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Float) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Float.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Float(0)";
+	    } else {
+		// Detect format error at translation time
+		return "new Float(" + Float.valueOf(s).toString() + "f)";
+	    }
+	}
+    }
+
+    public static String coerceToInt(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToInt(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "0";
+	    else
+		return Integer.valueOf(s).toString();
+	}
+    }
+
+    public static String coerceToInteger(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Integer) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Integer.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Integer(0)";
+	    } else {
+		// Detect format error at translation time
+		return "new Integer(" + Integer.valueOf(s).toString() + ")";
+	    }
+	}
+    }
+
+    public static String coerceToPrimitiveShort(String s,
+						boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToShort(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "(short) 0";
+	    else
+		return "((short) " + Short.valueOf(s).toString() + ")";
+	}
+    }
+    
+    public static String coerceToShort(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Short) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Short.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Short((short) 0)";
+	    } else {
+		// Detect format error at translation time
+		return "new Short(\"" + Short.valueOf(s).toString() + "\")";
+	    }
+	}
+    }
+    
+    public static String coerceToPrimitiveLong(String s,
+					       boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerceToLong(" + s + ")";
+	} else {
+	    if (s == null || s.length() == 0)
+		return "(long) 0";
+	    else
+		return Long.valueOf(s).toString() + "l";
+	}
+    }
+
+    public static String coerceToLong(String s, boolean isNamedAttribute) {
+	if (isNamedAttribute) {
+	    return "(Long) org.apache.struts2.jasper.runtime.JspRuntimeLibrary.coerce(" + s + ", Long.class)";
+	} else {
+	    if (s == null || s.length() == 0) {
+		return "new Long(0)";
+	    } else {
+		// Detect format error at translation time
+		return "new Long(" + Long.valueOf(s).toString() + "l)";
+	    }
+	}
+    }
+
+    public static InputStream getInputStream(String fname, JarFile jarFile,
+					     JspCompilationContext ctxt,
+					     ErrorDispatcher err)
+		throws JasperException, IOException {
+
+        InputStream in = null;
+
+	if (jarFile != null) {
+	    String jarEntryName = fname.substring(1, fname.length());
+	    ZipEntry jarEntry = jarFile.getEntry(jarEntryName);
+	    if (jarEntry == null) {
+		err.jspError("jsp.error.file.not.found", fname);
+	    }
+	    in = jarFile.getInputStream(jarEntry);
+	} else {
+	    in = ctxt.getResourceAsStream(fname);
+	}
+
+	if (in == null) {
+	    err.jspError("jsp.error.file.not.found", fname);
+	}
+
+	return in;
+    }
+
+    /**
+     * Gets the fully-qualified class name of the tag handler corresponding to
+     * the given tag file path.
+     *
+     * @param path Tag file path
+     * @param err Error dispatcher
+     *
+     * @return Fully-qualified class name of the tag handler corresponding to 
+     * the given tag file path
+     */
+    public static String getTagHandlerClassName(String path,
+						ErrorDispatcher err)
+                throws JasperException {
+
+        String className = null;
+        int begin = 0;
+        int index;
+        
+        index = path.lastIndexOf(".tag");
+        if (index == -1) {
+            err.jspError("jsp.error.tagfile.badSuffix", path);
+        }
+
+        //It's tempting to remove the ".tag" suffix here, but we can't.
+        //If we remove it, the fully-qualified class name of this tag
+        //could conflict with the package name of other tags.
+        //For instance, the tag file
+        //    /WEB-INF/tags/foo.tag
+        //would have fully-qualified class name
+        //    org.apache.jsp.tag.web.foo
+        //which would conflict with the package name of the tag file
+        //    /WEB-INF/tags/foo/bar.tag
+
+        index = path.indexOf(WEB_INF_TAGS);
+        if (index != -1) {
+            className = "org.apache.jsp.tag.web.";
+            begin = index + WEB_INF_TAGS.length();
+        } else {
+	    index = path.indexOf(META_INF_TAGS);
+	    if (index != -1) {
+		className = "org.apache.jsp.tag.meta.";
+		begin = index + META_INF_TAGS.length();
+	    } else {
+		err.jspError("jsp.error.tagfile.illegalPath", path);
+	    }
+	}
+
+        className += makeJavaPackage(path.substring(begin));
+  
+       return className;
+    }
+
+    /**
+     * Converts the given path to a Java package or fully-qualified class name
+     *
+     * @param path Path to convert
+     *
+     * @return Java package corresponding to the given path
+     */
+    public static final String makeJavaPackage(String path) {
+        String classNameComponents[] = split(path,"/");
+        StringBuffer legalClassNames = new StringBuffer();
+        for (int i = 0; i < classNameComponents.length; i++) {
+            legalClassNames.append(makeJavaIdentifier(classNameComponents[i]));
+            if (i < classNameComponents.length - 1) {
+                legalClassNames.append('.');
+            }
+        }
+        return legalClassNames.toString();
+    }
+
+    /**
+     * Splits a string into it's components.
+     * @param path String to split
+     * @param pat Pattern to split at
+     * @return the components of the path
+     */
+    private static final String [] split(String path, String pat) {
+        Vector comps = new Vector();
+        int pos = path.indexOf(pat);
+        int start = 0;
+        while( pos >= 0 ) {
+            if(pos > start ) {
+                String comp = path.substring(start,pos);
+                comps.add(comp);
+            }
+            start = pos + pat.length();
+            pos = path.indexOf(pat,start);
+        }
+        if( start < path.length()) {
+            comps.add(path.substring(start));
+        }
+        String [] result = new String[comps.size()];
+        for(int i=0; i < comps.size(); i++) {
+            result[i] = (String)comps.elementAt(i);
+        }
+        return result;
+    }
+            
+    /**
+     * Converts the given identifier to a legal Java identifier
+     *
+     * @param identifier Identifier to convert
+     *
+     * @return Legal Java identifier corresponding to the given identifier
+     */
+    public static final String makeJavaIdentifier(String identifier) {
+        StringBuffer modifiedIdentifier = 
+            new StringBuffer(identifier.length());
+        if (!Character.isJavaIdentifierStart(identifier.charAt(0))) {
+            modifiedIdentifier.append('_');
+        }
+        for (int i = 0; i < identifier.length(); i++) {
+            char ch = identifier.charAt(i);
+            if (Character.isJavaIdentifierPart(ch) && ch != '_') {
+                modifiedIdentifier.append(ch);
+            } else if (ch == '.') {
+                modifiedIdentifier.append('_');
+            } else {
+                modifiedIdentifier.append(mangleChar(ch));
+            }
+        }
+        if (isJavaKeyword(modifiedIdentifier.toString())) {
+            modifiedIdentifier.append('_');
+        }
+        return modifiedIdentifier.toString();
+    }
+    
+    /**
+     * Mangle the specified character to create a legal Java class name.
+     */
+    public static final String mangleChar(char ch) {
+        char[] result = new char[5];
+        result[0] = '_';
+        result[1] = Character.forDigit((ch >> 12) & 0xf, 16);
+        result[2] = Character.forDigit((ch >> 8) & 0xf, 16);
+        result[3] = Character.forDigit((ch >> 4) & 0xf, 16);
+        result[4] = Character.forDigit(ch & 0xf, 16);
+        return new String(result);
+    }
+
+    /**
+     * Test whether the argument is a Java keyword
+     */
+    public static boolean isJavaKeyword(String key) {
+        int i = 0;
+        int j = javaKeywords.length;
+        while (i < j) {
+            int k = (i+j)/2;
+            int result = javaKeywords[k].compareTo(key);
+            if (result == 0) {
+                return true;
+            }
+            if (result < 0) {
+                i = k+1;
+            } else {
+                j = k;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Converts the given Xml name to a legal Java identifier.  This is
+     * slightly more efficient than makeJavaIdentifier in that we only need
+     * to worry about '.', '-', and ':' in the string.  We also assume that
+     * the resultant string is further concatenated with some prefix string
+     * so that we don't have to worry about it being a Java key word.
+     *
+     * @param name Identifier to convert
+     *
+     * @return Legal Java identifier corresponding to the given identifier
+     */
+    public static final String makeXmlJavaIdentifier(String name) {
+        if (name.indexOf('-') >= 0)
+            name = replace(name, '-', "$1");
+        if (name.indexOf('.') >= 0)
+            name = replace(name, '.', "$2");
+        if (name.indexOf(':') >= 0)
+            name = replace(name, ':', "$3");
+        return name;
+    }
+
+    static InputStreamReader getReader(String fname, String encoding,
+				       JarFile jarFile,
+				       JspCompilationContext ctxt,
+				       ErrorDispatcher err)
+		throws JasperException, IOException {
+
+        InputStreamReader reader = null;
+	InputStream in = getInputStream(fname, jarFile, ctxt, err);
+
+	try {
+            reader = new InputStreamReader(in, encoding);
+	} catch (UnsupportedEncodingException ex) {
+	    err.jspError("jsp.error.unsupported.encoding", encoding);
+	}
+
+	return reader;
+    }
+
+    /**
+     * Class.getName() return arrays in the form "[[[<et>", where et,
+     * the element type can be one of ZBCDFIJS or L<classname>;
+     * It is converted into forms that can be understood by javac.
+     */
+    public static String toJavaSourceType(String type) {
+
+	if (type.charAt(0) != '[') {
+	    return type;
+ 	}
+
+	int dims = 1;
+	String t = null;
+	for (int i = 1; i < type.length(); i++) {
+	    if (type.charAt(i) == '[') {
+		dims++;
+	    } else {
+		switch (type.charAt(i)) {
+		case 'Z': t = "boolean"; break;
+		case 'B': t = "byte"; break;
+		case 'C': t = "char"; break;
+		case 'D': t = "double"; break;
+		case 'F': t = "float"; break;
+		case 'I': t = "int"; break;
+		case 'J': t = "long"; break;
+		case 'S': t = "short"; break;
+		case 'L': t = type.substring(i+1, type.indexOf(';')); break;
+		}
+		break;
+	    }
+	}
+	StringBuffer resultType = new StringBuffer(t);
+	for (; dims > 0; dims--) {
+	    resultType.append("[]");
+	}
+	return resultType.toString();
+    }
+
+    /**
+     * Compute the canonical name from a Class instance.  Note that a
+     * simple replacment of '$' with '.' of a binary name would not work,
+     * as '$' is a legal Java Identifier character.
+     * @param c A instance of java.lang.Class
+     * @return  The canonical name of c.
+     */
+    public static String getCanonicalName(Class c) {
+
+        String binaryName = c.getName();
+        c = c.getDeclaringClass();
+
+        if (c == null) {
+            return binaryName;
+        }
+
+        StringBuffer buf = new StringBuffer(binaryName);
+        do {
+            buf.setCharAt(c.getName().length(), '.');
+            c = c.getDeclaringClass();
+        } while ( c != null);
+
+        return buf.toString();
+    }
+}
+

Added: struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java
URL: http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java?rev=799681&view=auto
==============================================================================
--- struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java (added)
+++ struts/sandbox/trunk/struts2-jsp-plugin/src/main/java/org/apache/struts2/jasper/compiler/Localizer.java Fri Jul 31 18:12:48 2009
@@ -0,0 +1,160 @@
+/*
+ * 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.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Class responsible for converting error codes to corresponding localized
+ * error messages.
+ *
+ * @author Jan Luehe
+ */
+public class Localizer {
+
+    private static ResourceBundle bundle = null;
+    
+    static {
+        try {
+        bundle = ResourceBundle.getBundle(
+            "org.apache.struts2.jasper.resources.LocalStrings");
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    /*
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * 
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode) {
+	String errMsg = errCode;
+	try {
+	    errMsg = bundle.getString(errCode);
+	} catch (MissingResourceException e) {
+	}
+	return errMsg;
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg Argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg) {
+	return getMessage(errCode, new Object[] {arg});
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2) {
+	return getMessage(errCode, new Object[] {arg1, arg2});
+    }
+    
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2,
+				    String arg3) {
+	return getMessage(errCode, new Object[] {arg1, arg2, arg3});
+    }
+
+    /* 
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param arg1 First argument for parametric replacement
+     * @param arg2 Second argument for parametric replacement
+     * @param arg3 Third argument for parametric replacement
+     * @param arg4 Fourth argument for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, String arg1, String arg2,
+				    String arg3, String arg4) {
+	return getMessage(errCode, new Object[] {arg1, arg2, arg3, arg4});
+    }
+
+    /*
+     * Returns the localized error message corresponding to the given error
+     * code.
+     *
+     * If the given error code is not defined in the resource bundle for
+     * localized error messages, it is used as the error message.
+     *
+     * @param errCode Error code to localize
+     * @param args Arguments for parametric replacement
+     *
+     * @return Localized error message
+     */
+    public static String getMessage(String errCode, Object[] args) {
+	String errMsg = errCode;
+	try {
+	    errMsg = bundle.getString(errCode);
+	    if (args != null) {
+		MessageFormat formatter = new MessageFormat(errMsg);
+		errMsg = formatter.format(args);
+	    }
+	} catch (MissingResourceException e) {
+	}
+	
+	return errMsg;
+    }
+}