/* GNU Server Pages - A free page compilation servlet
 * Copyright (C) 1998  Ed Korthof and James Cooper
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 * For more information about this software, visit:
 * http://www.bitmechanic.com/projects/
 */

package com.bitmechanic.gsp;

import java.io.File;
import java.io.FileReader;
import java.util.StringTokenizer;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
%%
%line
%intwrap

%full
%public
%implements TemplateParser

%yylexthrow{
ParserException
%yylexthrow}

%class JavaTagsFormatParser

%state YYTRANS, YYJAVA, YYBACKTICK, YYSERVLET, YYANCHOR

%{
    private StringBuffer currentBuffer;
    private StringBuffer temp = new StringBuffer(1024);
    private StringBuffer importsStuff = new StringBuffer(4096);
    private StringBuffer codeStuff = new StringBuffer(8096);
    private StringBuffer classStuff = new StringBuffer(4096);
    private StringBuffer implementsStuff = new StringBuffer(1024);
    private String extendsString = null;
    private boolean uses_servlets = false; // are there any servlets on the page
    private String cacheHash = null;
    private long cache_ttl;
    private int cacheCount = 0;
    private boolean sessionCache = false;
    private boolean globalCache = false;
    private boolean stripCR = false;
    private Vector charVector = new Vector(20);
    private int charChunk = 0;

// the order is: import; code; class; extends; implements; print;
// expression (ie. backticks); include; cache.  however, code, print, and
// expression should all go into the same buffer: codeStuff
// import goes to temp so it can be cleaned up first.
// similarly for extends and implements.
    private StringBuffer stuff[] = {
        null, temp, codeStuff, classStuff, temp,
        temp, temp, temp, temp, codeStuff
    };

    private static final int importsNum = 1, codeNum = 2, classNum = 3,
        extendsNum = 4, implementsNum = 5, printNum = 6, expressionNum=7,
        includesNum = 8, cacheNum = 9;
    private static final int backtickNum = -3, servletNum = -4, 
	                     anchorNum = -5;
    private String sourceFile = "";
    private Vector dependancies = new Vector();
    private int state = -1;
    private int javaState = 0;
    private boolean rewriteURL = false;
    private String[] hosts;

    public void setStripCR(boolean strip) {
      this.stripCR = strip;
    }

    public void setCharChunk(int charChunk) {
      this.charChunk = charChunk;
    }

    public void setSourceFile(String filename) {
        int stripFrom = filename.indexOf("(");
        String name;
        if (stripFrom > 1)
            name = filename.substring(0,stripFrom);
        else 
            name = filename;
        dependancies.addElement(name);
        this.sourceFile = filename;
    }

    public void setRewriteURL(boolean b) {
      rewriteURL = b;
    }    

    public void setRewriteHosts(String hosts[]) {
      this.hosts = hosts;
    }

    public void setCacheCount(int count) {
      cacheCount = count;
    }

    public int getCacheCount() {
      return cacheCount;
    }

    public boolean usesServlets() {
      return uses_servlets;
    }

    public void setUsesServlets(boolean value) {
      uses_servlets = value;
    }

    public String getImportsStuff()    { return importsStuff.toString(); }
    public String getClassStuff()      { return classStuff.toString(); }
    public String getCodeStuff()       { return codeStuff.toString(); }
    public String getExtendsStuff()    { return extendsString; }
    public String getImplementsStuff() { return implementsStuff.toString(); }
    public Vector getDependancies()    { return dependancies; }
    public Vector getCharVector()      { return charVector; }
    public boolean usesSessionCache()  { return sessionCache; }
    public boolean usesGlobalCache()   { return globalCache; }

    private void setState(int state) {
        if (state == -2) state = this.javaState; // ie. reset.

        int new_yystate;
        if (state == -1)        // -1 is what I'm using for YYINITIAL
            new_yystate = YYINITIAL;
        else if (state == backtickNum)
            new_yystate = YYBACKTICK;
	else if (state == servletNum)
	    new_yystate = YYSERVLET;
	else if (state == anchorNum)
            new_yystate = YYANCHOR;
        else if (state == 0)   // 0 is what I'm using for YYTRANS
            new_yystate = YYTRANS;
        else                  // everything else means we're in a code section:
            new_yystate = YYJAVA;

        //state + "/" + new_yystate); 
        this.javaState = state;
        setYyState(new_yystate);
    }

    private void setYyState(int state) {
        this.state = state;
        yybegin(state);
    }

    public Hashtable parseAttributes(String str, boolean makeLowerCase) {
      Hashtable hash = new Hashtable();
      char arr[] = str.toCharArray();    
      StringBuffer buffer = new StringBuffer(128);
      boolean in_value=false;
      String key = null;
      for(int i = 0; i < arr.length; i++) {
	switch(arr[i]) {
	case ' ':
	  if(in_value) buffer.append(arr[i]);
	  else if(key != null) {
	    if (makeLowerCase) key = key.toLowerCase();
	    if(buffer.length() > 0) hash.put(key, buffer.toString());
	    else hash.put(key, new Boolean(true));
	    buffer.setLength(0);
	    key = null;
	    in_value = false;
	  }
	  break;
	case '=':
	  if(in_value) buffer.append(arr[i]);
	  else {
	    key = buffer.toString();
	    buffer.setLength(0);
	  }
	  break;
	case '"':
	  if(in_value) {
	    if (makeLowerCase) key = key.toLowerCase();
	    if(buffer.length() > 0) hash.put(key, buffer.toString());
	    else hash.put(key, new Boolean(true));
	    buffer.setLength(0);
	    key = null;
	    in_value = false;
	  }
	  else in_value = true;
	  break;
	default:
	  buffer.append(arr[i]);
	  break;
	}
      }    
      
      // parse the last arg
      if(buffer.length() > 0) {
	if(makeLowerCase) key = key.toLowerCase();
	if(key != null) {
	  hash.put(key, buffer.toString());
	}
	else hash.put(buffer.toString(), new Boolean(true));
      }
      
      return hash;
    }

    private void handleIncludeFile(Hashtable directives) {
	File includeFile = null;
        String type = (String)directives.get("type");
        if (type.equals("subrequest-include")) {
            String page = (String)directives.get("page");
            if (page == null || page.equals("")) {
                codeStuff.append("out.print(\"[you must include a page={uri} ");
                codeStuff.append("argument in subrequest-include directives: ");
                codeStuff.append(getFilePosition()).append("]\");\n");
            } else {
                codeStuff.append("\n\tapplication.renderTemplate(request, ");
                codeStuff.append("response, \"").append(page).append("\"); //");
                codeStuff.append(getFilePosition()).append("\n");
            }
            return;
        }
     
        try {
            String includeFilename = (String)directives.get("file");
            if (includeFilename != null) {
                includeFile = new File(includeFilename);
                if (!includeFile.isAbsolute()) {
		    int firstParen = sourceFile.indexOf("(");
		    String dir;
		    if (firstParen > 0)
			dir = sourceFile.substring(0,firstParen);
		    else
			dir = sourceFile;

		    dir =
			dir.substring(0, dir.lastIndexOf(File.separator));
                    includeFile = new File(dir, includeFilename);
		}
                FileReader input = new FileReader(includeFile);
                JavaTagsFormatParser parser = new JavaTagsFormatParser(input);
                parser.setCharChunk(charChunk);

        
                parser.setSourceFile(includeFile.getAbsolutePath() + "(" +
                                     getFilePosition() + ")");
		parser.setUsesServlets(uses_servlets);
                parser.setRewriteURL(rewriteURL);
                if(rewriteURL) parser.setRewriteHosts(hosts);
                parser.setCacheCount(cacheCount);
                Integer returnValue = parser.yylex();
                setCacheCount(parser.getCacheCount());
                implementsStuff.append(parser.getImplementsStuff());
                if (extendsString == null)
                    extendsString = parser.getExtendsStuff();
            
                importsStuff.append(parser.getImportsStuff());
                codeStuff.append(parser.getCodeStuff());
                classStuff.append(parser.getClassStuff());
                Enumeration enum = parser.getDependancies().elements();
                while (enum.hasMoreElements()) {
                    dependancies.addElement(enum.nextElement());
                }
                enum = parser.getCharVector().elements();
                while(enum.hasMoreElements()) {
                  charChunk++;
                  charVector.addElement(enum.nextElement());
                }

            	if(parser.usesSessionCache()) sessionCache = true;
		if(parser.usesGlobalCache())  globalCache = true;
		if(parser.usesServlets())  uses_servlets = true;
            }
        } catch (Exception e) {
            codeStuff.append("out.print(\"[the following include directive generated an exception :"+yytext().replace('"','\'')+"<br>the error was: "+e.toString()+"]\");\n");
	    if (includeFile != null)
		dependancies.addElement(includeFile.getAbsolutePath());
        } finally {
            temp.setLength(0);
        }
        setState(-2);
    }

    private void handleEndQuotedText () {
        if (temp.length() != 0) {
            codeStuff.append("out.write(__byteArr[" + charChunk +"]);\n");
            charVector.addElement(temp.toString());
            temp.setLength(0);
            charChunk++;
        }
    }

    private void handleEndBacktick() {
        if (temp.length() != 0) {
            codeStuff.append("out.print(");
            codeStuff.append(temp.toString());
            codeStuff.append(");\n");
            temp.setLength(0);
        }
    }

    public String getFilePosition () {
        int firstParen = sourceFile.indexOf("(");
        StringBuffer stuff = new StringBuffer(256);
        if (firstParen > 0) {
            String rawName = sourceFile.substring(0,firstParen);
            String remain = sourceFile.substring(firstParen);
            stuff.append(rawName).append(":").append(yyline+1).append(remain);
        } else {
            stuff.append(sourceFile).append(":").append(yyline+1);
        }
        return stuff.toString();
    }
        
    private void handleBeginSpecialSection() throws ParserException {
        String yytext = yytext(); // now yytext has <java[^>]*>
        int initialSpace = yytext.indexOf(' ');
        int newNumber = -1;
        String args = null;
        if (initialSpace != -1)
            args = yytext.substring(initialSpace,yytext.length() - 1);

        if (args == null || args.equals(""))
            newNumber = codeNum;
        else {
            //args = args.toLowerCase();
	    Hashtable argHash = parseAttributes(args, true);
	    args = (String)argHash.get("type");
            if (args == null || args.startsWith("code"))
                newNumber = codeNum;
            else if (args.startsWith("import")) {
                newNumber = importsNum;
            } else if (args.startsWith("class"))
                newNumber = classNum;
            else if (args.startsWith("extends"))
                newNumber = extendsNum;
            else if (args.startsWith("implements"))
                newNumber = implementsNum;
            else if (args.startsWith("print"))
                newNumber = printNum;
            else if (args.startsWith("include") ||
                     args.startsWith("subrequest-include")) {
                handleIncludeFile(argHash);
                newNumber = includesNum;
            }
	    else if (args.startsWith("cache")) {
                handleCacheBegin(argHash);
	        newNumber = cacheNum;
            }
            else System.err.println("didn't match! "+args + " from "+yytext());
        }
        currentBuffer = stuff[newNumber];
        setState(newNumber);
     }

    private void handleEndSpecialSection () throws ParserException {
        if (javaState == extendsNum) {
            if (this.extendsString != null)
                throw new ParserException("you can have only one extends (extend directives in included files will cause errors if the inclusion occurs before the extends directive in the first file)");
            else this.extendsString = temp.toString();
	    temp.setLength(0);
        } else if (javaState == implementsNum) {
            implementsStuff.append(","); // we always add a comma because all
            implementsStuff.append(temp.toString()); // pages implement Template
	    temp.setLength(0);
        } else if (javaState == codeNum || javaState == classNum ||
                   javaState == importsNum) {
            handleJavaNewline(null);
        } else if (javaState == printNum || javaState == expressionNum) {
            codeStuff.append("out.print(").append(temp.toString());
            codeStuff.append(");");
            handleJavaNewline(codeStuff);
            temp.setLength(0);
        } else if (javaState == cacheNum) {
	    handleJavaNewline(null);
            handleCacheEnd();
        } else if (javaState == includesNum) {
        } else { System.err.println("handleEndSpecialSection called at an inappropriate time -- javaState = " + javaState); }
        currentBuffer = null;
        setState(-1); // YYINITIAL
    }

    private String cleanImportsLine(String line) throws ParserException {
        int commentOffset = line.indexOf("//");

        String comment = line.substring(commentOffset);
        String importsString = line.substring (0, commentOffset-1);
        importsString = importsString.trim();
        if (importsString.equals(""))
	    return "";

        if (!importsString.endsWith(";"))
            importsString = importsString + ";";
        if (!importsString.startsWith("import "))
            importsString = "import " + importsString;

        // now, a sanity check.  We should have ^import (\w+);$.  To test
        // this...
        int walker = 7; // start after "import "
        while (walker < importsString.length() -1) { // continue until the ";"
            char c = importsString.charAt(walker++);
            if (!Character.isLetterOrDigit(c) && c != '_' && c != '.'
                    && c != '*')
                throw new ParserException ("invalid char in import classname/packagename: '"+c+"'");
        }
        // ok, we should have a good value.
        return importsString + comment;
    }

    public void handleJavaChar () {
        char c = yytext().charAt(0);
        currentBuffer.append(c);
    }

    public void handleJavaNewline(StringBuffer b) throws ParserException {
        if (b == null) b = currentBuffer;
        if (javaState == extendsNum || javaState == implementsNum ||
            javaState == includesNum) {
            b.append(" ");
            return;
        }
        b.append(" //").append(getFilePosition());
        if (javaState == importsNum) {
	    // basically, b == temp, so we pull out the string,
	    // fix it up, then stick it in importsStuff
	    String importsString = b.toString();
	    importsString = cleanImportsLine(importsString);
	    if (!importsString.equals(""))
		importsStuff.append(importsString).append("\n");
	    b.setLength(0);
        } else {
            b.append("\n");
	}
    }

  private void handleCacheBegin(Hashtable argHash) {
    //
    // Figure out which fields to base the caching on
    String fields = (String)argHash.get("fields");
    String fieldKey = null;
    if(fields != null) {
      StringBuffer fieldBuf = new StringBuffer(256);
      StringTokenizer st = new StringTokenizer(fields, ",");
      boolean first = true;
      while(st.hasMoreTokens()) {
        String f = st.nextToken();
        if(first) first = false;
        else fieldBuf.append(" + ");
        fieldBuf.append("request.getForm().getValue(\"" + f + "\")");
      }
      fieldKey = fieldBuf.toString();
    }

    //
    // Figure out which hash to use
    String policy = (String)argHash.get("policy");
    String cacheKey = null;
    if(policy.startsWith("session")) {
      sessionCache = true;
      cacheHash = "__sessionCacheHash";
      cacheKey = "Integer.toString(this.hashCode())";
    }
    else {
      globalCache = true;
      cacheCount++;
      cacheHash = "__cacheHash";
      cacheKey = '"' + Integer.toString(cacheCount) + '"';
    }
    if(fieldKey != null) cacheKey = cacheKey + "+" + fieldKey;

    //
    // Figure out the ttl in milliseconds
    String ttl = (String)argHash.get("ttl");
    cache_ttl = 0;
    int multiplier = 0;
    if(ttl.endsWith("s")) multiplier = 1000;
    else if(ttl.endsWith("m")) multiplier = 60000;
    else if(ttl.endsWith("h")) multiplier = 3600000;
    if(multiplier > 0) { 
      try { cache_ttl = Integer.parseInt(ttl.substring(0, ttl.length() - 1)); }
      catch(Exception e) { }
      cache_ttl = cache_ttl * multiplier;
    }
    else {
      try { cache_ttl = Integer.parseInt(ttl); }
      catch(Exception e) { }
    }

    //
    // output the start of the cache block
    codeStuff.append("\t\t__cacheKey = " + cacheKey + ";\n");
    codeStuff.append("\t\t__cacheString = (CachedString)" + cacheHash +
	".get(__cacheKey);\n");
    codeStuff.append("\t\tif(__cacheString == null || __cacheString.hasExpired()) {\n");
    codeStuff.append("\t\t  StringBuffer cacheBuffer = new StringBuffer(1024);\n");
  }
  
  private void handleCacheEnd() {
    codeStuff.append("\t\t __cacheString = new CachedString(cacheBuffer.toString(), " + cache_ttl + ");\n");
    codeStuff.append("\t\t " + cacheHash + ".put(__cacheKey, __cacheString);\n");
    codeStuff.append("\t\t}\n");
    codeStuff.append("\t\tout.print(__cacheString.toString());\n");
  }

    public void handleBeginAnchor() {
       setState(anchorNum);
    }

    public void handleEndAnchor() {
	Hashtable args = parseAttributes(temp.toString(), true);
        String href = (String)args.get("href");
	boolean munge = false;
	if(href != null) {
          if(href.startsWith("/")) munge = true;
          else if((href.startsWith("http://")) ||
                  (href.startsWith("https://"))) {
            int pos = href.indexOf("://") + 3;
	    int pos2 = href.indexOf("/", pos);
	    String hostname;
            if(pos2 == -1) hostname = href.substring(pos);
            else           hostname = href.substring(pos, pos2);
            for(int i = 0; i < hosts.length && !munge; i++) {
              if(hostname.equals(hosts[i])) munge = true;
            }            
          }
        }

        codeStuff.append("out.print(\"<a ");
        for(Enumeration e = args.keys(); e.hasMoreElements();) {
          String key = (String)e.nextElement();
          codeStuff.append(key);
          codeStuff.append("=\\\"");
          if(munge && key.equals("href")) {
            codeStuff.append("\");\n");
            codeStuff.append("out.print(response.encodeUrl(\"");
            codeStuff.append(args.get(key));
            codeStuff.append("\"));\n");
            codeStuff.append("out.print(\"");
          }
          else codeStuff.append(args.get(key));
          codeStuff.append("\\\" ");
        }
        codeStuff.append(">\");\n");

	temp.setLength(0);
        currentBuffer = null;
        setState(-1); // YYINITIAL
    }

    public void handleBeginServlet() throws ParserException {
	boolean firstServletUsed = !this.uses_servlets;
        this.uses_servlets = true;
        if (firstServletUsed) { // this was then the first servlet used
	    temp.append("\t\tjava.util.Hashtable __servletParams = new java.util.Hashtable();\n");
	    temp.append("\t\tjava.util.Hashtable __currentParams;\n");
        } else {
            temp.append("\t\t__servletParams.clear();\n");
        }
        String matchedText = yytext();
        String args = matchedText.substring(9,matchedText.length() - 1);
        //args = args.toLowerCase();
        Hashtable argHash = parseAttributes(args, true);
        String code = (String)argHash.get("code");
	String name = (String)argHash.get("name");
	if (code == null) {
	    code = name;
	} else {
	    if (code.endsWith(".class")) {
		code = code.substring(0,code.length()-6);
	    }
	}
	if (code == null || code.equals("") || code.equals(".class"))
            throw new ParserException ("no class named in servlet tag");

        // now we have the servlet.  let's store it.
	temp.append ("\t\t");
	if (firstServletUsed)
           temp.append ("HttpServlet ");

	temp.append ("__servlet = (HttpServlet) request.getServletContext().getServlet(\"").append(code).append("\");\n");
        temp.append("\t\tif (__servlet == null) throw new Exception(\"unable to find '").append(code).append("' servlet\");\n");

        setState(servletNum);
    }

    public void handleParamTag() throws ParserException {
        String matchedText = yytext();
        String args = matchedText.substring(6,matchedText.length() - 1);
	//args = args.toLowerCase();
        Hashtable argHash = parseAttributes(args, true);
        String name = (String)argHash.get("name");
        String value = (String)argHash.get("value");
        Vector stuff = parseForBackTicks(value);
        temp.append("\t\t__servletParams.put(\"").append(name).append("\",");

        StringContainer next;
        for (int i = 0; i < stuff.size(); ++i) {
            next = (StringContainer) stuff.elementAt(i);

            if (i > 0)
                 temp.append(" + ");

            if (next.getType() == 1)
                temp.append("\"").append(next.getStuff()).append("\"");
            else if (next.getType() == 2)
                temp.append(next.getStuff());
	    else
		throw new ParserException("invalid StringContainer type: " +
			  next.getType() + " for '" + next.getStuff() + "'");
           
        }
        temp.append(");\n");
    }

    public class StringContainer {
        private String stuff;
        private int type;
        public StringContainer(String stuff, int type) {
            this.stuff = stuff;
            this.type = type;
        }
        public String getStuff() { return stuff; }
        public int    getType()  { return type; }
    }

    /**
     * this parses a string for backticks, and returns an Vector of
     * StringContainers.
     */
    private Vector parseForBackTicks(String value)
	    throws ParserException {
        int backtickCount = 0; int lastBacktick = -1, nextBacktick = 0;
        Vector stuff = new Vector();
        while (true) {
	    nextBacktick = value.indexOf("`",lastBacktick + 1);
	    if (nextBacktick < 0) { // we're at the end of the value
                if (lastBacktick < value.length()-1) {
                    stuff.addElement(new  // but there's still some text to get
			StringContainer(value.substring(lastBacktick + 1), 1));
		}
		break;
	    }

	    // we found another backtick.
            if (nextBacktick > lastBacktick + 1) { // there's text before it
                stuff.addElement(new StringContainer
			(value.substring(lastBacktick + 1, nextBacktick), 1));
	    }

            lastBacktick = nextBacktick;
	    nextBacktick = value.indexOf("`",lastBacktick + 1);
	    if (nextBacktick < 1 || lastBacktick < 0)
		throw new ParserException ("unbalanced backticks! " + lastBacktick + "-" + nextBacktick);

	    stuff.addElement(new StringContainer
			(value.substring(lastBacktick + 1, nextBacktick), 2));

	    lastBacktick = nextBacktick;
	    
	}
	return stuff;
    }

    public void handleEndServlet () {
        temp.append("\t\t__currentParams = request.getOverrideParameters();\n\t\trequest.setOverrideParameters(__servletParams);\n\t\ttry {\n\t\t\t__servlet.service((javax.servlet.ServletRequest)request, (javax.servlet.ServletResponse)response);\n\t\t} catch (Throwable e) {\n\t\t\te.printStackTrace(); throw new Exception(\"exception executing servlet (from <servlet>): \"+e.toString());\n\t\t} finally {\n\t\t\trequest.setOverrideParameters(__currentParams);\n\t\t}\n");
	codeStuff.append(temp.toString());
	temp.setLength(0);
        currentBuffer = null;
        setState(-1); // YYINITIAL
    }

%}

%eof{
    if (this.state == YYINITIAL || this.state == -1) {
        handleEndQuotedText();
    } else {
        System.err.println("missing end of java section(</java>) [" +
                           getFilePosition()+"]");
    }
%eof}

%%

<YYINITIAL>`            { 
                handleEndQuotedText();
                setState(backtickNum);
        }
<YYINITIAL>\r		{
		if (!stripCR) temp.append("\\r");
}

<YYINITIAL>[^\"\n\\]	{ temp.append(yytext()); }
<YYINITIAL>\"		{ temp.append("\\\""); }
<YYINITIAL>\\           { temp.append("\\\\"); }
<YYINITIAL>\n		{ temp.append("\\n"); }

<YYINITIAL>\<[jJ][aA][vV][aA][^\>]*\>	{
		handleEndQuotedText();
		handleBeginSpecialSection();
	}

<YYINITIAL>\<[sS][eE][rR][vV][lL][eE][tT][^\>]*\>	{
		handleEndQuotedText();
		handleBeginServlet();
}

<YYINITIAL>\<[aA] {
    if(rewriteURL) {
		handleEndQuotedText();
		handleBeginAnchor();
    }
    else temp.append(yytext());
}

<YYANCHOR>\> {
		handleEndAnchor();
}

<YYANCHOR>\r {
		if (!stripCR) temp.append(yytext());
}
<YYANCHOR>.|\n {
		temp.append(yytext());
}

<YYSERVLET>\<[Pp][Aa][Rr][Aa][Mm][^\>]*\>	{
		handleParamTag();
}

<YYSERVLET>\r	{ ; // ignore everything else in <param>
}

<YYSERVLET>.|\n	{ ; // ignore everything else in <param> section
}

<YYSERVLET>\</[sS][eE][rR][vV][lL][eE][tT][^\>]*\>	{
		handleEndServlet();
}


<YYBACKTICK>`           { 
                handleEndBacktick();                
                setState(-1);
        }

<YYBACKTICK>\r	{
		if (!stripCR) temp.append(" "); // treat it all as whitepsace.
}

<YYBACKTICK>.           { temp.append(yytext()); }

<YYBACKTICK>\n	{ temp.append(" "); // treat it all as whitepsace.
}


<YYJAVA>\</[jJ][aA][vV][aA]\>	{
		handleEndSpecialSection();
	}

<YYJAVA>\r	{
		if (!stripCR) { handleJavaChar(); } //otherwise, ignore
}
<YYJAVA>.	{ handleJavaChar(); }
<YYJAVA>\n	{ handleJavaNewline(null); }

.|\n	{ System.err.println("unmatched input!: '"+yytext()+"' state: "+this.state); }


