alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *  Copyright 1999-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.apache.jasper.compiler;

import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.jasper.Constants;
import org.apache.jasper.JspCompilationContext;
import org.apache.tomcat.util.log.Log;

/**
 * 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
 */
public class JspReader {
    protected Mark current  = null;
    String master = null;

    Vector sourceFiles = new Vector();
    int currFileId = 0;
    int size = 0;
    
    private JspCompilationContext context;

    Log loghelper = Log.getLog("JASPER_LOG", "JspReader");
    //    LogHelper loghelper = new LogHelper("JASPER_LOG", "JspReader");
    
    /*
     * Default encoding used. The JspReader is created with the
     * "top file" encoding. This is then the encoding used for the
     * included files (same translation unit).
     */
    private String encoding = null;

    public String getFile(int fileid) {
	return (String) sourceFiles.elementAt(fileid);
    }

    /**
     * Register a new 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.
     */
    protected int registerSourceFile(String file) {
        if (sourceFiles.contains(file))
            return -1;
	sourceFiles.addElement(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.
     */
    protected int unregisterSourceFile(String file) {
        if (!sourceFiles.contains(file))
            return -1;
	sourceFiles.removeElement(file);
	this.size--;
	return sourceFiles.size() - 1;
    }

    /**
     * Push a new file onto the stack.
     * The name of the file is resolved to a fully qualified filename.
     * @param name The name of the file.
     */
    public void pushFile(String name) 
	throws ParseException, FileNotFoundException
    {
	pushFile(name, this.encoding);
    }

    /**
     * Push a new file onto the stack.
     * The name of the file is resolved to a fully qualified filename.
     * @param name The name of the file.
     * @param encoding The optional encoding for the file.
     */
    public void pushFile(String name, String encoding) 
	throws ParseException, FileNotFoundException
    {
        String parent = master == null ?
            null : master.substring(0, master.lastIndexOf("/") + 1);
        boolean isAbsolute = name.startsWith("/");

	if (parent == null || isAbsolute) {
	    master = name;
	    pushFile(new File(name), encoding);
	} else {
	    master = parent + name;
	    pushFile(new File(master), encoding);
	}
    }

    /**
     * Push a new file to be parsed onto the stack.
     * @param inputFile The fully qualified path of the file.
     * @param encoding Optional encoding to read the file.
     */
    private void pushFile(File file, String encoding) 
	throws ParseException, FileNotFoundException 
    {
        // Default encoding if needed:
	if (encoding == null) {
            encoding = this.encoding;
            // XXX - longer term, this should really be:
	    //   System.getProperty("file.encoding", "8859_1");
            // but this doesn't work right now, so we stick with ASCII
        }

	// Register the file, and read its content:
	String longName = (context == null)
	    ? file.getAbsolutePath()
	    : context.getRealPath(file.toString());

	if (longName == null)
	    throw new FileNotFoundException(file.toString());

	int fileid = registerSourceFile(longName);
	
        if (fileid == -1)
            throw new ParseException(Constants.getString("jsp.error.file.already.registered",
                                                         new Object[] { 
                                                             file 
                                                         }));
	currFileId = fileid;
                                     
	InputStreamReader reader = null;
	try {
            if (context == null)
                reader = new InputStreamReader(new FileInputStream(file),
                                               encoding);
            else {
	        String fileName = context.getRealPath(file.toString());
		InputStream in = context.getResourceAsStream(file.toString());
                if (in == null)
                    throw new FileNotFoundException(fileName);
                
                try {
                    reader = new InputStreamReader(in, encoding);
                } catch (Throwable ex) {
                    throw new FileNotFoundException(fileName + ": "+ ex.getMessage());
                }
            }
            
	    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),
				    master, encoding );
	    }

        } catch (FileNotFoundException fnfe) {
            throw fnfe;
	} catch (Throwable ex) {
	    loghelper.log("Exception parsing file " + file, ex);
	    // Pop state being constructed:
	    popFile();
	    throw new ParseException(Constants.getString("jsp.error.file.cannot.read",
							new Object[] { file }));
	} finally {
	    if ( reader != null ) {
		try { reader.close(); } catch (Exception any) {}
	    }
	}
    }

    public boolean popFile() throws ParseException {
	// Is stack created ? (will happen if the Jsp file we'r looking at is
	// missing.
	if (current == null) 
		return false;

	// Restore parser state:
	//size--;
	if (currFileId < 0) {
	    throw new ParseException(
		          Constants.getString("jsp.error.no.more.content"));
	}

	String fName = getFile(currFileId);
	currFileId = unregisterSourceFile(fName);
	if (currFileId < -1)
	    throw new ParseException
		(Constants.getString("jsp.error.file.not.registered",
				     new Object[] {fName}));

	boolean r = current.popStream();
	if (r)
		master = current.baseDir;
	return r;
    }
	
    protected JspReader(String file, JspCompilationContext ctx, String encoding) 
	throws ParseException, FileNotFoundException
    {
        this.context = ctx;
	this.encoding = encoding;
	if (this.encoding == null) this.encoding = "8859_1";
	pushFile(file, encoding);
    }

    public static JspReader createJspReader(String file, JspCompilationContext ctx, String encoding) 
	throws ParseException, FileNotFoundException
    {
	return new JspReader(file, ctx, encoding);
    }

    public boolean hasMoreInput() throws ParseException {
	if (current.cursor >= current.stream.length) {
	    while (popFile()) {
		if (current.cursor < current.stream.length) return true;
	    }
	    return false;
	}
	return true;
    }
    
    public int nextChar() throws ParseException {
	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;
    }

    /**
     * Gets Content until the next potential JSP element.  Because all elements
     * begin with a '<' we can just move until we see the next one.
     */
    String nextContent() {
        int cur_cursor = current.cursor;
	int len = current.stream.length;
 	char ch;

	if (peekChar() == '\n') {
	    current.line++;
	    current.col = 0;
	}
	else current.col++;
	
	// pure obsfuscated genius!
        while ((++current.cursor < len) && 
	    ((ch = current.stream[current.cursor]) != '<')) {

	    if (ch == '\n') {
		current.line++;
		current.col = 0;
	    } else {
  		current.col++;
	    }
	}

	return new String(current.stream, cur_cursor, current.cursor-cur_cursor);
    }

    char[] getChars(Mark start, Mark stop) throws ParseException {
	Mark oldstart = mark();
	reset(start);
	try {
	    CharArrayWriter caw = new CharArrayWriter();
	    while (!stop.equals(mark()))
		caw.write(nextChar());
	    caw.close();
	    reset(oldstart);
	    return caw.toCharArray();
	} catch( Exception ex ) {
	    ex.printStackTrace();
	    throw new ParseException( ex.toString() );
	}
    }

    public int peekChar() {
	return current.stream[current.cursor];
    }

    public Mark mark() {
	return new Mark(current);
    }

    public void reset(Mark mark) {
	current = new Mark(mark);
    }

    public boolean matchesIgnoreCase(String string) throws ParseException {
	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;
    }

    public boolean matches(String string) throws ParseException {
	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());
	reset(mark);
	return true;
    }
    
    public void advance(int n) throws ParseException {
	while (--n >= 0)
	    nextChar();
    }

    public int skipSpaces() throws ParseException {
	int i = 0;
	while (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 Mark instance if found,
     * null otherwise.
     */
    public Mark skipUntil(String limit)
    throws ParseException {
	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) ) {
		for (int i = 1 ; i < limlen ; i++) {
		    if (Character.toLowerCase((char) nextChar()) != limit.charAt(i)){
			reset(ret);
			advance(1);
			continue skip;
		    }
		}
		return ret;
	    }
	}
	return null;
    }
    
    final boolean isSpace() {
	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 true accept quoted strings.
     */

    public String parseToken(boolean quoted) 
	throws ParseException
    {
	StringBuffer stringBuffer = new StringBuffer();
	skipSpaces();
	stringBuffer.setLength(0);
	
	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 ) 
		    throw new ParseException(mark(), 
				Constants.getString("jsp.error.quotes.unterminated"));
	    }
	    else throw new ParseException(mark(),
				Constants.getString("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();
    }

    /**
     * Parse an attribute/value pair, and store it in provided hash table.
     * The attribute/value pair is defined by:
     * 
     * av := spaces token spaces '=' spaces token spaces
     * 
* Where token is defined by parseToken and * spaces is defined by skipSpaces. * The name is always considered case insensitive, hence stored in its * lower case version. * @param into The Hashtable instance to save the result to. */ private void parseAttributeValue(Hashtable into) throws ParseException { // Get the attribute name: skipSpaces(); String name = parseToken(false); // Check for an equal sign: skipSpaces(); if ( peekChar() != '=' ) throw new ParseException(mark(), Constants.getString("jsp.error.attr.novalue", new Object[] { name })); char ch = (char) nextChar(); // Get the attribute value: skipSpaces(); String value = parseToken(true); skipSpaces(); // Add the binding to the provided hashtable: into.put(name, value); return; } /** * Parse some tag attributes for Beans. * The stream is assumed to be positioned right after the tag name. The * syntax recognized is: *
     * tag-attrs := empty | attr-list (">" | "-->" | %>)
     * attr-list := empty | av spaces attr-list
     * empty     := spaces 
     * 
* Where av is defined by parseAttributeValue. * @return A Hashtable mapping String instances (variable names) into * String instances (variable values). */ public Hashtable parseTagAttributesBean() throws ParseException { Hashtable values = new Hashtable(11); while ( true ) { skipSpaces(); int ch = peekChar(); if ( ch == '>' ) { // End of the useBean tag. return values; } else if ( ch == '/' ) { Mark mark = mark(); nextChar(); // XMLesque Close tags try { if ( nextChar() == '>' ) return values; } finally { reset(mark); } } if ( ch == -1 ) break; // Parse as an attribute=value: parseAttributeValue(values); } // Reached EOF: throw new ParseException(mark(), Constants.getString("jsp.error.tag.attr.unterminated")); } /** * Parse some tag attributes. * The stream is assumed to be positioned right after the tag name. The * syntax recognized is: *
     * tag-attrs := empty | attr-list (">" | "-->" | %>)
     * attr-list := empty | av spaces attr-list
     * empty     := spaces 
     * 
* Where av is defined by parseAttributeValue. * @return A Hashtable mapping String instances (variable names) into * String instances (variable values). */ public Hashtable parseTagAttributes() throws ParseException { Hashtable values = new Hashtable(11); while ( true ) { skipSpaces(); int ch = peekChar(); if ( ch == '>' ) { return values; } if ( ch == '-' ) { Mark mark = mark(); nextChar(); // Close NCSA like attributes "->" try { if ( nextChar() == '-' && nextChar() == '>' ) return values; } finally { reset(mark); } } else if ( ch == '%' ) { Mark mark = mark(); nextChar(); // Close variable like attributes "%>" try { if ( nextChar() == '>' ) return values; } finally { reset(mark); } } else if ( ch == '/' ) { Mark mark = mark(); nextChar(); // XMLesque Close tags try { if ( nextChar() == '>' ) return values; } finally { reset(mark); } } if ( ch == -1 ) break; // Parse as an attribute=value: parseAttributeValue(values); } // Reached EOF: throw new ParseException(mark(), Constants.getString("jsp.error.tag.attr.unterminated")); } /** * Parse PARAM tag attributes into the given hashtable. * Parses the PARAM tag as defined by: *
     * <PARAM tag-attributes %gt;
     * 
* Two special tag attributes are recognized here: *
    *
  1. The name attribute, *
  2. The value attribute. *
* The resulting name, value pair is stored in the provided hash table. * @param into Storage for parameter values. */ public void parseParamTag(Hashtable into) throws ParseException { // Really check for a param tag: if ( matches("param") ) { advance(6); parseParams (into); } else { // False alarm, just skip it } } /** * Parse jsp:param tag attributes into the given hashtable. * Parses the jsp:param tag as defined by: *
     * <jsp:param tag-attributes %gt;
     * 
* Two special tag attributes are recognized here: *
    *
  1. The name attribute, *
  2. The value attribute. *
* The resulting name, value pair is stored in the provided hash table. * @param into Storage for parameter values. */ public void parsePluginParamTag(Hashtable into) throws ParseException { // Really check for a param tag: if ( matches("isSpace. * @return A boolean. */ private boolean isDelimiter() throws ParseException { 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; } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.