/*
 * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.xsl;

import com.caucho.java.JavaCompiler;
import com.caucho.java.JavaWriter;
import com.caucho.loader.DynamicClassLoader;
import com.caucho.log.Log;
import com.caucho.server.util.CauchoSystem;
import com.caucho.util.CharBuffer;
import com.caucho.util.IntArray;
import com.caucho.util.IntMap;
import com.caucho.vfs.Depend;
import com.caucho.vfs.Path;
import com.caucho.vfs.WriteStream;
import com.caucho.xml.QAbstractNode;
import com.caucho.xml.QAttr;
import com.caucho.xml.QElement;
import com.caucho.xml.QName;
import com.caucho.xml.XmlChar;
import com.caucho.xpath.Expr;
import com.caucho.xpath.NamespaceContext;
import com.caucho.xpath.expr.NumericExpr;
import com.caucho.xpath.pattern.*;
import com.caucho.xsl.fun.KeyFun;
import com.caucho.xsl.java.*;

import org.w3c.dom.*;

import java.io.IOException;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.logging.Logger;

/**
 * Generates code for a Java based stylesheet.
 *
 * <pre>
 * package work.xsl;
 * public class foo extends JavaStylesheet {
 * }
 * </pre>
 */
public class JavaGenerator extends Generator {
  private static final Logger log = Log.open(JavaGenerator.class);

  private static HashMap<QName,Class> _tagMap;
  private static HashMap<QName,Class> _topTagMap;
  
  private static int _count;

  Path _path;
  WriteStream _s;
  JavaWriter _out;

  ArrayList<AbstractPattern> _matchPatterns = new ArrayList<AbstractPattern>();
  IntMap _matchMap = new IntMap();
  ArrayList<AbstractPattern> _selectPatterns = new ArrayList<AbstractPattern>();
  IntMap _selectMap = new IntMap();
  
  ArrayList<Expr> _exprs = new ArrayList<Expr>();
  IntMap _exprMap = new IntMap();
  
  ArrayList<Sort[]> _sorts = new ArrayList<Sort[]>();
  ArrayList<NamespaceContext> _namespaces = new ArrayList<NamespaceContext>();
  ArrayList<XslNumberFormat> _formats = new ArrayList<XslNumberFormat>();
  ArrayList<String> _functions = new ArrayList<String>();
  ArrayList<Template> _templateList = new ArrayList<Template>();
  ArrayList<String> _stylesheets = new ArrayList<String>();
  int _templateCount = 0;
  // integer counting unique identifier
  int _unique;
  HashMap<String,String> _macros = new HashMap<String,String>();
  ArrayList<Object> _fragments = new ArrayList<Object>();
  
  ArrayList<String> _strings = new ArrayList<String>();
  IntMap _stringMap = new IntMap();
  
  IntArray _envDepth = new IntArray();

  ArrayList<String> _modes = new ArrayList<String>();

  private XslNode _xslNode;

  private boolean _isLineBegin;
  private int _depth;
  private int _callDepth;

  // integer counting the depth of nested selects
  private int _selectDepth;
  private int _selectLoopDepth;
  
  private int _flagCount;
  private String _className;
  private String _pkg;

  private String _currentPos;
  
  private ClassLoader _parentLoader;
  private JavaCompiler _compiler;
  private boolean _disableEscaping;
  private boolean _printLocation = true;

  private String _oldFilename = null;
  private int _oldLine = -1;

  private boolean _hasHeader;

  /**
   * Creates a new XSL generator for Java.
   *
   * @param xslGenerator the owning factory.
   * @param className the name of the generated class
   * @param encoding the generated output encoding.
   */
  JavaGenerator(AbstractStylesheetFactory xslGenerator, 
                String className, String encoding)
    throws IOException
  {
    super(xslGenerator);

    _parentLoader = xslGenerator.getClassLoader();

    ArrayList pathDepends = new ArrayList();

    _compiler = JavaCompiler.create(_parentLoader);
    _compiler.setClassDir(_workPath);

    if (encoding == null) {
    }
    else if (encoding.equalsIgnoreCase("UTF-16")) {
      // utf-16 isn't supported by some javac
      encoding = "UTF-8";
      _compiler.setEncoding(encoding);
    } else {
      _compiler.setEncoding(encoding);
    }

    int p = className.lastIndexOf('.');
    if (p >= 0) {
      _pkg = className.substring(0, p);
      className = className.substring(p + 1);
    }
    else {
      _pkg = "_xsl";
      className = className;
    }
      
    _className = className;
    init((_pkg + "." + className).replace('.', '/') + ".java");

    String fileName = (_pkg + "." + className).replace('.', '/') + ".java";
    _path = _workPath.lookup(fileName);
    _path.getParent().mkdirs();

    _s = _path.openWrite();
    if (encoding != null)
      _s.setEncoding(encoding);
    if (_s.getEncoding() == null || _s.getEncoding().equals("ISO-8859-1"))
      _s.setEncoding("JAVA");
    _out = new JavaWriter(_s);
    _out.setLineMap(_lineMap);
    
    _matchPatterns = new ArrayList<AbstractPattern>();
    _selectPatterns = new ArrayList<AbstractPattern>();

    _modes = new ArrayList<String>();
    _modes.add("");
  }

  protected JavaWriter getOut()
  {
    return _out;
  }

  public int getSelectDepth()
  {
    return _selectDepth;
  }

  public void setSelectDepth(int depth)
  {
    _selectDepth = depth;
  }

  public int pushSelectDepth()
  {
    return ++_selectDepth;
  }

  public int popSelectDepth()
  {
    return _selectDepth--;
  }

  public int getSelectLoopDepth()
  {
    return _selectLoopDepth;
  }

  public int pushSelectLoopDepth()
  {
    return ++_selectLoopDepth;
  }

  public int popSelectLoopDepth()
  {
    return _selectLoopDepth--;
  }

  public void setSelectLoopDepth(int depth)
  {
    _selectLoopDepth = depth;
  }

  public int generateId()
  {
    return _unique++;
  }

  public void clearUnique()
  {
    _unique = 0;
  }
    

  /**
   * Prints the generated header.
   */
  protected void printHeader()
    throws IOException
  {
    if (_hasHeader)
      return;
    _hasHeader = true;
    
    println("/*");
    println(" * Generated by " + com.caucho.Version.FULL_VERSION);
    println(" */");
    println();
    println("package " + _pkg + ";");
    println();
    println("import java.io.*;");
    println("import java.util.*;");
    println("import org.w3c.dom.*;");
    println("import org.xml.sax.*;");
    println("import com.caucho.util.*;");
    println("import com.caucho.xml.*;");
    println("import com.caucho.xpath.*;");
    println("import com.caucho.xpath.expr.*;");
    println("import com.caucho.xpath.pattern.*;");
    println("import com.caucho.xsl.*;");

    try {
      Class.forName("javax.servlet.Servlet");
      println("import javax.servlet.*;");
      println("import javax.servlet.jsp.*;");
      println("import javax.servlet.http.*;");
    } catch (Throwable e) {
    }
    
    for (int i = 0; i < _imports.size(); i++)
      println("import " + _imports.get(i) + ";");
    println();

    println("public class " + _className + " extends JavaStylesheet {");
    pushDepth();

    println("private StylesheetEnv stylesheets[];");
  }
  
  protected void generateChild(Node child)
    throws Exception
  {
    XslNode node = createChild(child);

    if (node != null)
      node.generate(_out);
  }

  protected XslNode createChild(XslNode parent, Node childNode)
    throws Exception
  {
    XslNode xslNode = _xslNode;

    _xslNode = parent;

    XslNode child = createChild(childNode);

    _xslNode = xslNode;

    return child;
  }
  
  protected XslNode createChild(Node child)
    throws Exception
  {
    XslNode xslNode = null;

    if (child instanceof QElement) {
      QElement elt = (QElement) child;

      Class cl = _tagMap.get(elt.getQName());

      if (cl != null) {
	xslNode = (XslNode) cl.newInstance();
	xslNode.setGenerator(this);
	xslNode.setParent(_xslNode);
	
	xslNode.setStartLocation(((QAbstractNode) child).getBaseURI(),
				 ((QAbstractNode) child).getFilename(),
				 ((QAbstractNode) child).getLine());

	QAttr attr = (QAttr) elt.getFirstAttribute();
	for (; attr != null; attr = (QAttr) attr.getNextSibling()) {
	  xslNode.addAttribute(attr.getQName(), attr.getNodeValue());
	}

	xslNode.endAttributes();

	XslNode oldNode = _xslNode;
	_xslNode = xslNode;
	
	Node node = elt.getFirstChild();
	for (; node != null; node = node.getNextSibling()) {
	  XslNode xslChild = createChild(node);

	  if (xslChild != null)
	    xslNode.addChild(xslChild);
	}

	xslNode.endElement();

	_xslNode = oldNode;
      }
      /*
      else if (elt.getNodeName().equals("jsp:decl") ||
	       elt.getNodeName().equals("jsp:declaration") ||
	       elt.getNodeName().startsWith("jsp:directive")) {
      }
      */
      else if (child.getNodeName().startsWith("xsl:") &&
	       ! XSLNS.equals(child.getNamespaceURI())) {
	throw error(child, L.l("<{0}> has an xsl: prefix, but is not in the {1} namespace.  XSL requires an xmlns:xsl=\"{1}\" namespace attribute.",
			       child.getNodeName(),
			       XSLNS));
      }
      else if (! XSLNS.equals(child.getNamespaceURI()) &&
	       ! XTPNS.equals(child.getNamespaceURI())) {
	xslNode = new XslElementNode(elt.getQName());
	xslNode.setGenerator(this);
	xslNode.setParent(_xslNode);
	
	xslNode.setStartLocation(((QAbstractNode) child).getBaseURI(),
				 ((QAbstractNode) child).getFilename(),
				 ((QAbstractNode) child).getLine());

	QAttr attr = (QAttr) elt.getFirstAttribute();
	for (; attr != null; attr = (QAttr) attr.getNextSibling())
	  xslNode.addAttribute(attr.getQName(), attr.getNodeValue());

	xslNode.endAttributes();

	XslNode oldNode = _xslNode;
	_xslNode = xslNode;
	
	Node node = elt.getFirstChild();
	for (; node != null; node = node.getNextSibling()) {
	  XslNode xslChild = createChild(node);
	  
	  xslNode.addChild(xslChild);
	}

	xslNode.endElement();

	_xslNode = oldNode;
      }
      else {
	throw error(child, L.l("<{0}> is an unknown XSL tag.",
			child.getNodeName()));
	/*
	  XslWrapperNode wrapNode = new XslWrapperNode();
	  wrapNode.setNode(child);
	  xslNode = wrapNode;
	  xslNode.setGenerator(this);
	*/
      }
    }
    else if (child instanceof Text) {
      xslNode = new TextNode(((Text) child).getData());
      xslNode.setGenerator(this);
      xslNode.setParent(_xslNode);
    }
    else if (child instanceof Comment) {
    }
    else if (child instanceof ProcessingInstruction) {
    }
    else
      throw new UnsupportedOperationException(String.valueOf(child));

    if (xslNode != null) {
      xslNode.setStartLocation(((QAbstractNode) child).getBaseURI(),
			       ((QAbstractNode) child).getFilename(),
			       ((QAbstractNode) child).getLine());
    }

    return xslNode;
  }
  
  /**
   * Generates code for a template
   *
   * @param absNode the XSL node for the emplace
   * @param name the template name
   * @param pattern the pattern string
   * @param mode the template's mode
   * @param priority the template's priority
   */
  protected void printTemplate(Element absNode, String name, 
			       String pattern, String mode, double priority)
    throws Exception
  {
    throw new RuntimeException();
    /*
    QElement node = (QElement) absNode;
    
    if (name != null && ! name.equals(""))
      addMacro(name, node);
    
    if (! pattern.equals("")) {
      String fun = createTemplatePattern(name, pattern,
					 mode, priority);
      
      print("// '" + pattern.replace('\n', ' ') + "'");
      
      if (mode != null && mode != "") {
        if (! _modes.contains(mode))
          _modes.add(mode);
        println(" mode '" + mode + "'");
      }
      else
        println();
      
      printString("// " + node.getFilename() + ":" + node.getLine());
      println();
      
      println("private void " + fun +
	      "(XslWriter out, Node inputNode, Env env)");
      println("  throws Exception");
      println("{");
      pushDepth();

      println("Object _xsl_tmp;");
      println("Node node = inputNode;");
      println("int _xsl_top = env.getTop();");

      if (_isRawText)
	println("boolean oldEscaping = out.disableEscaping(true);");
      else
	println("boolean oldEscaping = out.disableEscaping(false);");

      String filename = node.getBaseURI();
      if (filename != null) {
        int pos = _stylesheets.indexOf(filename);
        if (pos < 0) {
          pos = _stylesheets.size();
          _stylesheets.add(filename);
        }
        
        println("env.setStylesheetEnv(stylesheets[" + pos + "]);");
      }

      _selectDepth = 0;
      _unique = 0;
      
      if (node.getLocalName().equals("template") ||
	  node.getLocalName().equals("xsl:template"))
        generateChildren(node);
      else
        generateChild((QAbstractNode) node);
      
      if (! _isCacheable)
	println("out.setNotCacheable();");

      println("out.disableEscaping(oldEscaping);");
      println("env.popToTop(_xsl_top);");
      popDepth();
      println("}");
      println();
    }
    */
  }

  public void addMacro(String name, String functionName)
  {
    _macros.put(name, functionName);
  }

  /*
  public void addMacro(String name)
  {
    addMacro(name, "_xsl_macro_" + toJavaIdentifier(name);
  }
  */

  public boolean hasMacro(String name)
  {
    return _macros.keySet().contains(name);
  }

  /**
   * Generates the pattern for a matching pattern
   *
   * @param name the mangled name of the function
   * @param match the XPath match pattern
   * @param mode the template mode
   * @param priority the template priority
   * @param node the source XML node from the XSL file
   *
   * @return the name of the function
   */
  public String createTemplatePattern(String name, AbstractPattern match,
				      String mode, double priority)
    throws Exception
  {
    String tagName;

    if (name != null)
      tagName = getName(name);
    else
      tagName = getName(match.toString());

    String function = "_xsl_template_" + tagName;
    _functions.add(function);

    if (match != null) {
      Template template = addPattern(match,
                                     mode, priority, function,
                                     _functions.size());
      _templateList.add(template);
    }
    else
      _templateList.add(null);

    return function;
  }
  
  protected void startDisableEscaping()
    throws IOException
  {
    if (! _isRawText)
      println("out.disableEscaping(true);");
  }
 
  protected void endDisableEscaping()
    throws IOException
  {
    if (! _isRawText)
      println("out.disableEscaping(false);");
  }

  /**
   * Creates Java code to print plain text.
   */
  protected void writeText(String text)
    throws Exception
  {
    if (text == null || text.length() == 0)
      return;

    int index = _stringMap.get(text);
    if (index < 0) {
      index = _strings.size();
      _stringMap.put(text, index);
      _strings.add(text);
    }

    printLocation(_systemId, _filename, _line);
    println("out.write(_xsl_string" + index + ", 0, " + text.length() + ");");
  }

  protected void printElement(Node node)
    throws Exception
  {
    QElement elt = (QElement) node;
    String name = node.getNodeName();

    if (name.equals("jsp:decl") || name.equals("jsp:declaration")) {
      println("if (out.isFlagFirst(" + _flagCount++ + ")) {");
      pushDepth();
    }
    
    String prefix = elt.getPrefix();
    String local = elt.getLocalName();
    String namespace = elt.getNamespaceURI();

    String []postPrefix = (String []) _namespaceAliases.get(namespace);
    if (postPrefix != null) {
      prefix = postPrefix[0];
      namespace = postPrefix[1];
      if (prefix == null || prefix.equals(""))
        name = local;
      else
        name = prefix + ":" + local;
    }
    if (_excludedNamespaces.get(namespace) != null)
      namespace = null;

    printLocation(_systemId, _filename, _line);
    if (namespace == null || namespace.equals("")) {
      print("out.pushElement(");
      print(name == null ? "null" : ("\"" + name + "\""));
      println(");");
    } else {
      print("out.pushElement(");
      print(namespace == null ? "null" : ("\"" + namespace + "\""));
      print(prefix == null ? ", null" : (", \"" + prefix + "\""));
      print(local == null ? ", null" : (", \"" + local + "\""));
      print(name == null ? ", null" : (", \"" + name + "\""));
      println(");");
    }
    
    printUseAttributeSet((QElement) node, false);
    
    NamedNodeMap list = node.getAttributes();
    for (int i = 0; i < list.getLength(); i++) {
      QAbstractNode attr = (QAbstractNode) list.item(i);

      printAttribute(attr, elt);
    }
    
    generateChildren(node);
    
    println("out.popElement();");

    if (node.getNodeName().equals("jsp:decl") ||
        node.getNodeName().equals("jsp:declaration")) {
      popDepth();
      println("}");
    }
  }

  /**
   * Prints a command to set the current file and line into the
   * generated document.
   *
   * @param filename the source filename
   * @param line the source line number.
   */
  public void printLocation(String systemId, String filename, int line)
    throws Exception
  {
    if (_printLocation && filename != null && ! _isSpecial) {
      print("out.setLocation(");
      if (systemId != null) {
	print("\"");
	printString(systemId);
	print("\"");
      }
      else
	print("null");
      print(", \"");
      printString(filename);
      println("\", " + line + ");");
      _oldFilename = filename;
      _oldLine = line;
    }
  }

  /**
   * Prints code for an element's attributes.
   */
  private void printAttribute(QAbstractNode attr, QElement elt)
    throws Exception
  {
    if (attr.getNodeName().equals("xsl:use-attribute-sets")) {
    }
    else if (XSLNS.equals(elt.getNamespace(attr.getPrefix()))) {
    }
    else if (XTPNS.equals(elt.getNamespace(attr.getPrefix()))) {
    }
    else {
      QAbstractNode qnode = (QAbstractNode) attr;
      String prefix = qnode.getPrefix();
      String local = qnode.getLocalName();
      String namespace = qnode.getNamespaceURI();
      String value = attr.getNodeValue();

      String []postSuffix = (String []) _namespaceAliases.get(namespace);
      if (postSuffix != null) {
	prefix = postSuffix[0];
	namespace = postSuffix[1];
      }

      else if (value.equals(XSLNS) && prefix.equals("xmlns"))
        return;
      else if (value.equals(XTPNS) && prefix.equals("xmlns"))
        return;

      if (_excludedNamespaces.get(namespace) != null)
	namespace = null;
      
      if ("".equals(prefix) && ("".equals(namespace) || namespace == null)) {
        String var = generateStringVar(value, elt);
        println("out.setAttribute(\"" + local + "\", " + var + ");");
      }
      else {
        print("out.pushAttribute(");
        print(prefix == null ? "null" : ("\"" + prefix + "\""));
        print(local == null ? ", null" : (", \"" + local + "\""));
        print(namespace == null ? ", null" : (", \"" + namespace + "\""));
        println(");");
        generateString(value, ',', elt);
        println("out.popAttribute();");
      }
    }
  }

  protected void pushCall()
    throws IOException
  {
    println("{");
    pushDepth();
    _callDepth++;
    println("Env _xsl_arg" + _callDepth + " = XPath.createCall(env);");
  }

  public int pushCallDepth()
  {
    return ++_callDepth;
  }

  public int popCallDepth()
  {
    return _callDepth--;
  }

  public int getCallDepth()
  {
    return _callDepth;
  }

  protected void popCall()
    throws IOException
  {
    //println("_xsl_arg" + callDepth + ".free();");
    _callDepth--;
    popDepth();
    println("}");
  }

  /**
   * Prints code for xsl:apply-templates
   *
   * @param select the select pattern
   * @param mode the template mode
   * @param sort the sort expressions
   */
  protected void printApplyTemplates(AbstractPattern select,
                                     String mode,
                                     Sort []sort)
    throws Exception
  {
    int min = 0;
    int max = Integer.MAX_VALUE;

    String applyName = "applyNode" + getModeName(mode);
    String env = "_xsl_arg" + _callDepth;

    if (select == null && sort == null) {
      println("for (Node _xsl_node = node.getFirstChild();");
      println("     _xsl_node != null;");
      println("     _xsl_node = _xsl_node.getNextSibling()) {");
      println("  " + env + ".setSelect(node, null);");
      println("  " + env + ".setCurrentNode(_xsl_node);");
      println("  " + applyName + "(out, _xsl_node, " + env + ", " +
              min + ", " + max + ");");
      println("}");
    }
    else if (sort == null) {
      int oldSelectDepth = _selectDepth;
      println(env + ".setSelect(node, _select_patterns[" +
              addSelect(select) + "]);");

      String name = printSelectBegin(select, false, null);

      println(env + ".setCurrentNode(" + name + ");");

      println(applyName + "(out, " + name + ", " + env + ", " + 
              min + ", " + max + ");");

      for (; _selectDepth > oldSelectDepth; _selectDepth--) {
        popDepth();
        println("}");
      }
    }
    else {
      println("{");
      pushDepth();
      println("ArrayList _xsl_list = xslSort(node, env" +
	      ", _select_patterns[" + addSelect(select) + "]" +
	      ", _xsl_sorts[" + _sorts.size() + "]);");
      println(env + ".setContextSize(_xsl_list.size());");
      println("for (int _xsl_i = 0; _xsl_i < _xsl_list.size(); _xsl_i++) {");
      println("  " + env + ".setContextPosition(_xsl_i + 1);");
      println("  " + applyName + "(out, (Node) _xsl_list.get(_xsl_i)" + 
	      ", " + env + ", " + min + ", " + max + ");");
      println("}");
      popDepth();
      println("}");

      _sorts.add(sort);
    }
  }

  public int addSort(Sort []sort)
  {
    int index = _sorts.size();
    
    _sorts.add(sort);

    return index;
  }

  /**
   * Prints code to implement xsl:apply-imports
   *
   * @param mode the mode of the imported files
   * @param min the min importance
   * @param max the max importance
   */
  protected void printApplyImports(String mode, int min, int max)
    throws Exception
  {
  }

  protected void printCallTemplate(String name, String mode)
    throws Exception
  {
    println(getMacroName(name) + "(out, node, _xsl_arg" +
            _callDepth + ");");
  }

  public String getMacroName(String name)
  {
    return _macros.get(name);
    //return "_xsl_macro_" + toJavaIdentifier(name);
  }

  /**
   * Prints the value for a parameter.
   */
  protected void printParam(String name, String value, Element elt)
    throws Exception
  {
    print("_xsl_arg" + _callDepth + ".addVar(\"" + name + "\", ");
    generateString(value, '+', elt);
    println(");");
  }
  
  protected void printParam(String name, Object value)
    throws Exception
  {
    if (value instanceof Expr) {
      print("_exprs[" + addExpr((Expr) value) + "]");
      println(".addVar(_xsl_arg" + _callDepth + ", \"" + name + "\", " +
              "node, env);");
    }
    else {
      print("_xsl_arg" + _callDepth + ".addVar(\"");
      print(name);
      print("\", ");
      printVariableValue(value);
      println(");");
    }
  }

  /**
   * Prints code to add the value of an expression as a parameter.
   */
  protected void printParamVariable(String name, Expr value)
    throws Exception
  {
    print("_exprs[" + addExpr(value) + "]");
    println(".addParam(env, \"" + name + "\", " +
            "node, env);");
  }

  protected void printParamVariable(String name, Element value)
    throws Exception
  {
    if (value.getFirstChild() != null) {
      println("_xsl_tmp = env.getVar(\"" + name + "\");"); 
      println("if (_xsl_tmp == null)");
      print("  _xsl_tmp = ");
      printVariableValue(value);
      println(";");
      println("env.addVar(\"" + name + "\", _xsl_tmp);");
    }
  }

  protected void printVariable(String name, Object value)
    throws Exception
  {
    if (value instanceof Expr) {
      print("_exprs[" + addExpr((Expr) value) + "]");
      println(".addVar(env, \"" + name + "\", node, env);");
    }
    else {
      print("env.addVar(\"");
      print(name);
      print("\", ");
      printVariableValue(value);
      println(");");
    }
  }

  protected void printAssign(String name, Object value)
    throws Exception
  {
    if (value instanceof Expr) {
      print("_exprs[" + addExpr((Expr) value) + "]");
      println(".setVar(\"" + name + "\", node, env, node);");
    }
    else {
      print("env.setVar(\"");
      print(name);
      print("\", ");
      printVariableValue(value);
      println(");");
    }
  }

  private void printVariableValue(Object value)
    throws Exception
  {
    if (value instanceof Expr) {
      print("_exprs[" + addExpr((Expr) value) + "].evalObject(node, env)");
    }
    else if (value instanceof Node) {
      print("_xsl_fragment" + _fragments.size() + "(out, node, env)");
      _fragments.add(value);
    }
    else
      throw new RuntimeException();
  }

  protected void printPopScope(int count)
    throws Exception
  {
    if (count > 0)
      println("env.popVars(" + count + ");");
  }

  protected void printCopyOf(String select, Element elt)
    throws Exception
  {
    println("out.copyOf(_exprs[ " + addExpr(select) +
            "].evalObject(node, env));");
  }

  protected void printSelectValue(String select, Element elt)
    throws Exception
  {
    printStringExpr(select, elt);
  }

  protected void printForEach(Element element, String select)
    throws Exception
  {
    println("{");
    pushDepth();

    AbstractPattern selectPattern = null;
    try {
      selectPattern = parseSelect(select);
    } catch (Exception e) {
    }
    
    boolean hasExprEnv = ! allowJavaSelect(selectPattern);

    int id = _unique++;
    
    String sel = "_xsl_sel" + id;
    String oldCxt = "_xsl_cxt" + id;
    String oldCur = "_xsl_cur" + id;
    String oldSel = "_xsl_old_sel" + id;
    String oldEnv = "_xsl_env" + id;

    println("com.caucho.xpath.pattern.AbstractPattern " + sel + ";");
    print(sel + " = _select_patterns[");
    print(createNodeSet(select, element));
    println("];");
    println("Node " + oldCxt + " = env.getContextNode();");
    println("Node " + oldCur + " = env.getCurrentNode();");
    
    if (! hasExprEnv) {
      println("AbstractPattern " + oldSel + " = env.setSelect(node, " + sel + ");");
    }
    
    
    // String pos = "_xsl_pos" + unique++;
    String iter = "_xsl_iter" + _unique++;

    int oldSelectDepth = _selectDepth;
    
    // println("int " + pos + " = 0;");

    boolean hasEnv = false;
    
    if (allowJavaSelect(selectPattern)) {
      println("ExprEnvironment " + oldEnv + " = env.setExprEnv(null);");
      
      String ptr = printSelectBegin(selectPattern, true, null);

      pushLoop();
      println("Node " + getElement() + " = node;");
      println("node = " + ptr + ";");
    }
    else {
      print("NodeIterator " + iter + " = " + sel);
      println(".select(node, " + getEnv() + ");");
      println("ExprEnvironment " + oldEnv + " = env.setExprEnv(" + iter + ");");
      println("while (" + iter + ".hasNext()) {");
      pushDepth();
      _selectDepth++;
      
      pushLoop();
      
      println("Node " + getElement() + " = node;");
      println("node = " + iter + ".nextNode();");
      
    }
    println("env.setCurrentNode(node);");
    
    // println(pos + "++;");

    // String oldPos = currentPos;
    // currentPos = pos;

    AbstractPattern oldNodeListContext = _nodeListContext;
    _nodeListContext = parseMatch(select);

    generateChildren(element);

    _nodeListContext = oldNodeListContext;
    
    // currentPos = oldPos;
    
    println("node = " + getElement() + ";");
    println("env.setCurrentNode(" + oldCur + ");");
    
    for (; _selectDepth > oldSelectDepth; _selectDepth--) {
      popDepth();
      println("}");
    }
    
    println("env.setExprEnv(" + oldEnv + ");");
    
    if (! hasExprEnv) {
      println("env.setSelect(" + oldCxt + ", " + oldSel + ");");
    //println("env.setCurrentNode(node);");
    }
    
    popDepth();
    println("}");
    popLoop();
  }

  /**
   * Prints code for xsl:for-each when the for-each has any xsl:sort.
   */
  protected void printForEach(Element element, String select, Sort []sort)
    throws Exception
  {
    println("{");
    pushDepth();
    println("env.setCurrentNode(node);");
    String pos = "_xsl_pos" + _unique++;
    String list = "_xsl_list" + _unique++;
    
    println("ArrayList " + list +
	    " = xslSort(node, env" +
	    ", _select_patterns[" + addSelect(select) + "]" + 
	    ", _xsl_sorts[" + _sorts.size() + "]);");
    println("env.setContextSize(" + list + ".size());");
    println("for (int " + pos + " = 1; " + pos +
	    " <= " + list + ".size(); " + pos + "++) {");
    pushLoop();
    pushDepth();
    println("Node " + getElement() + " = node;");
    println("node = (Node) " + list + ".get(" + pos + " - 1);");

    String oldPos = _currentPos;
    _currentPos = pos;
    
    println("env.setPosition(" + _currentPos + ");");
    
    _sorts.add(sort);
    
    AbstractPattern oldNodeListContext = _nodeListContext;
    _nodeListContext = parseMatch(select);

    generateChildren(element);

    _currentPos = oldPos;

    _nodeListContext = oldNodeListContext;
    
    println("node = " + getElement() + ";");
    
    popDepth();
    println("}");
    popLoop();
    popDepth();
    println("}");
  }

  public String getCurrentPosition()
  {
    return _currentPos;
  }

  public void setCurrentPosition(String pos)
  {
    _currentPos = pos;
  }

  public AbstractPattern getNodeListContext()
  {
    return _nodeListContext;
  }

  public void setNodeListContext(AbstractPattern context)
  {
    _nodeListContext = context;
  }

  protected void printIf(Element element, Expr test)
    throws Exception
  {
    print("if (");
    printExprTest(test, "node");
    println(") {");
    pushDepth();
    generateChildren(element);
    popDepth();
    println("}");
  }

  protected void printChoose(Element element, Expr expr, boolean first)
    throws Exception
  {
    if (! first)
      print("else if (");
    else
      print("if (");
    printExprTest(expr, "node");
    println(") {");
    pushDepth();
    generateChildren(element);
    popDepth();
    println("}");
  }

  protected void printOtherwise(Element element, boolean first)
    throws Exception
  {
    if (! first)
      print("else ");
    println("{");
    pushDepth();
    generateChildren(element);
    popDepth();
    println("}");
  }

  void printNumber(Expr expr, XslNumberFormat format)
    throws Exception
  {
    print("exprNumber(out, node, env, _exprs[" + addExpr(expr) + "]");
    print(", _xsl_formats[" + _formats.size() + "]");
    println(");");

    _formats.add(format);
  }

  void printNumber(String level,
                   AbstractPattern countPattern,
                   AbstractPattern fromPattern,
		   XslNumberFormat format)
    throws Exception
  {
    if (level.equals("single"))
      print("singleNumber(out, ");
    else if (level.equals("multiple"))
      print("multiNumber(out, ");
    else if (level.equals("any"))
      print("anyNumber(out, ");
    else
      throw error("xsl:number cannot understand level=`" + level + "'");

    print("node, env, ");
    printPattern(countPattern);
    print(", ");
    printPattern(fromPattern);
    print(", _xsl_formats[" + _formats.size() + "]");
    println(");");

    _formats.add(format);
  }

  public int addFormat(XslNumberFormat format)
  {
    int index = _formats.size();

    _formats.add(format);

    return index;
  }

  protected void printCopy(Element element)
    throws Exception
  {
    println("out.pushCopy(node);");
    printUseAttributeSet(element, true);
    generateChildren(element);      
    println("out.popCopy(node);");
  }

  protected void printResultDocument(Element element, String href, String format)
    throws Exception
  {
    println("XslWriter oldOut = out;");
    println("OutputStream os = null;");
    println("try {");
    pushDepth();
    print("os = out.openWrite(env, ");
    generateString(href, '+', element);
    println(");");

    println("out = out.openResultDocument(os);");
    generateChildren(element);
    println("out.close();");
    popDepth();
    println("} finally {");
    println("  if (os != null)");
    println("    os.close();");
    println("  out = oldOut;");
    println("}");
  }

  protected void printElement(Element element, String name)
    throws Exception
  {
    print("out.pushElement(");
    generateString(name, '+', element);
    if (_namespace != null) {
      print(", ");
      printNamespace(_namespace);
    }
    println(");");
    printUseAttributeSet(element, true);
    generateChildren(element);      
    println("out.popElement();");
  }

  protected void printElement(Element element, String name, String namespace)
    throws Exception
  {
    print("out.pushElementNs(");
    generateString(name, '+', element);
    print(", ");
    generateString(namespace, '+', element);
    println(");");
    printUseAttributeSet(element, true);
    generateChildren(element);      
    print("out.popElement();");
  }

  /**
   * Prints the attributes in a use-attribute-set.
   */
  private void printUseAttributeSet(Element element, boolean isXSL)
    throws Exception
  {
    Attr attr = (Attr) ((QElement) element).getFirstAttribute();
    for (; attr != null; attr = (Attr) attr.getNextSibling()) {
      if (isXSL && attr.getNodeName().equals("use-attribute-sets") ||
          ! isXSL && attr.getNodeName().equals("xsl:use-attribute-sets")) {
	HashMap set = getAttributeSet(attr.getNodeValue());
	if (set == null)
	  continue;
	Iterator iter = set.keySet().iterator();
	while (iter.hasNext()) {
	  String key = (String) iter.next();
	  String value = (String) set.get(key);
          
          printAttributeValue(key, value, element);
	}
      }
    }
  }

  /**
   * Returns the named attribute set.
   */
  public HashMap<String,String> getAttributeSet(String name)
  {
    CharBuffer cb = CharBuffer.allocate();
    int i = 0;
    int len = name.length();

    HashMap<String,String> map = new HashMap<String,String>();
    
    while (i < len) {
      for (; i < len && name.charAt(i) == ' '; i++) {
      }

      cb.clear();
      for (; i < len && name.charAt(i) != ' '; i++)
        cb.append(name.charAt(i));

      if (cb.length() > 0) {
        XslAttributeSet newSet = _attributeSets.get(cb.toString());

        if (newSet != null) {
	  ArrayList<XslAttribute> attrList = newSet.getAttributes();

	  for (int j = 0; j < attrList.size(); j++) {
	    XslAttribute attr = attrList.get(j);

	    map.put(attr.getName(), attr.getValue());
          }
        }
      }
    }

    return map;
  }

  /**
   * Returns the named attribute set.
   */
  public ArrayList<XslAttribute> getAttributeSetList(String name)
  {
    CharBuffer cb = CharBuffer.allocate();
    int i = 0;
    int len = name.length();

    ArrayList<XslAttribute> set = new ArrayList<XslAttribute>();
    
    while (i < len) {
      for (; i < len && name.charAt(i) == ' '; i++) {
      }

      cb.clear();
      for (; i < len && name.charAt(i) != ' '; i++)
        cb.append(name.charAt(i));

      if (cb.length() > 0) {
        XslAttributeSet newSet = _attributeSets.get(cb.toString());

        if (newSet != null) {
	  set.addAll(newSet.getAttributes());
        }
      }
    }

    return set;
  }

  /**
   * Prints an xsl:attribute
   */
  protected void printAttribute(Element element, String name)
    throws Exception
  {
    print("out.pushAttribute(");
    generateString(name, '+', element);
    
    if (_namespace != null) {
      print(", ");
      printNamespace(_namespace);
    }
    
    println(");");
      
    generateChildren(element);      
    println("out.popAttribute();");
  }

  /**
   * Prints a single attribute value.
   */
  private void printAttributeValue(String key, String value, Element elt)
    throws Exception
  {
    if (_namespace == null && ! attributeHasSpecial(key) &&
        ! attributeHasSpecial(value)) {
      print("out.setAttribute(");
      generateString(key, '+', elt);
      print(", ");
      generateString(value, '+', elt);
      println(");");
    }
    else {
      print("out.pushAttribute(");
      generateString(key, '+', elt);
      if (_namespace != null) {
        print(", ");
        printNamespace(_namespace);
      }
      println(");");
      generateString(value, ',', elt);
      println("out.popAttribute();");
    }
  }

  public void printNamespace(NamespaceContext namespace)
    throws Exception
  {
    for (int i = 0; i < _namespaces.size(); i++) {
      if (_namespaces.get(i).equals(namespace)) {
        print("_namespaces[" + i + "]");
        return;
      }
    }
    
    print("_namespaces[" + _namespaces.size() + "]");
    _namespaces.add(namespace);
  }

  public int addNamespace(NamespaceContext namespace)
    throws Exception
  {
    for (int i = 0; i < _namespaces.size(); i++) {
      if (_namespaces.get(i).equals(namespace)) {
	return i;
      }
    }
    
    _namespaces.add(namespace);

    return _namespaces.size() - 1;
  }

  protected void printAttribute(Element element, String name, String namespace)
    throws Exception
  {
    print("out.pushAttributeNs(");
    generateString(name, '+', element);
    print(", ");
    generateString(namespace, '+', element);
    println(");");
    generateChildren(element);      
    println("out.popAttribute();");
  }

  protected void printPi(Element element)
    throws Exception
  {
    String name = element.getAttribute("name");
    if (name.equals(""))
      throw error("xsl:pi expected `name' attribute.");

    print("out.pushPi();");
    
    generateChildren(element);
    println("out.popPi(");
    generateString(name, '+', element);
    println(");");
  }

  protected void printComment(Element element)
    throws Exception
  {
    println("out.pushComment();");
    generateChildren(element);
    println("out.popComment();");
  }

  protected void printError(String msg)
    throws Exception
  {
    println("if (true) throw new javax.xml.transform.TransformerException(\"" + msg + "\");");
  }

  protected void printMessage(Element msg)
    throws Exception
  {
    int unique = _unique++;
    
    println("XMLWriter frag" + unique + " = out.pushFragment();");
    generateChildren(msg);

    String terminate = msg.getAttribute("terminate");
    if (terminate.equals("yes"))
      println("if (true) throw new javax.xml.transform.TransformerException(((QAbstractNode) out.popFragment(frag" + unique + ")).getTextValue());");
    else
      println("System.err.println(((QAbstractNode) out.popFragment(frag" + unique + ")).getTextValue());");
  }

  /**
   * Prints code to implement the xtp:expression tag, i.e. print
   * the value of the Java expression.
   */
  protected void printExpression(Element element)
    throws Exception
  {
    String expr = element.getAttribute("expr");

    if (! expr.equals("")) {
      print("out.print(");
      print(expr);
      println(");");
    }
    else {
      print("out.print(");
      print(((QAbstractNode) element).getTextValue());
      println(");");
    }
  }

  protected void printScriptlet(Element element)
    throws Exception
  {
    println(((QAbstractNode) element).getTextValue());
  }

  protected void printWhile(Element element, Expr test)
    throws Exception
  {
    print("while (");
    printExprTest(test, "node");
    println(") {");
    pushDepth();
    generateChildren(element);
    popDepth();
    println("}");
  }

  protected void printDeclaration(Element element)
    throws Exception
  {
    println(((QAbstractNode) element).getTextValue());
  }

  protected void printCacheDepends(String name)
    throws Exception
  {
    print("out.addCacheDepend(((com.caucho.vfs.Path) out.getProperty(\"caucho.pwd\")).lookup(\"");
    printString(name);
    println("\"));");
  }

  public String getElement()
  {
    return "node" + _loopDepth;
  }

  public void pushLoop()
  {
    _loopDepth++;
  }

  public void popLoop()
  {
    _loopDepth--;
  }

  public String getEnv()
  {
    return "env";
  }

  void pushEnv()
  {
    _envDepth.add(0);
  }

  void popEnv()
  {
    _envDepth.pop();
  }

  void printPattern(AbstractPattern pattern)
    throws Exception
  {
    if (pattern == null)
      print("null");
    else {
      print("_match_patterns[" + _matchPatterns.size() + "]");
      _matchPatterns.add(pattern);
    }
  }

  private int createNodeSet(String select, Element element)
    throws Exception
  {
    return addSelect(select);
  }
  
  int createSelectPattern(AbstractPattern pattern)
    throws Exception
  {
    return addSelect(pattern);
  }

  int createMatchPattern(String select, Element element)
    throws Exception
  {
    AbstractPattern pattern = parseMatch(select);

    _matchPatterns.add(pattern);

    return _matchPatterns.size() - 1;
  }

  String getName(String tag)
  {
    CharBuffer newTag = new CharBuffer();

    for (int i = 0; i < tag.length(); i++) {
      int ch = tag.charAt(i);
      switch (ch) {
      case ' ':
      case '\t':
      case '\r':
      case '\n':
      case '(':
      case ')':
	break;

      case ':':
      case '.':
      case '|':
	newTag.append('_');
	break;

      default:
	if (ch >= 'a' && ch <= 'z' ||
	    ch >= 'A' && ch <= 'Z' ||
	    ch >= '0' && ch <= '9')
	  newTag.append((char) ch);
      }
    }
    tag = newTag.toString();

    if (_names.get(tag) == null) {
      _names.put(tag, tag);
      return tag;
    }

    int i = 0;
    while (true) {
      String subname = tag + i;
      if (_names.get(subname) == null) {
	_names.put(subname, subname);
	return subname;
      }

      i++;
    }
  }

  void printExprTest(Expr expr, String element)
    throws Exception
  {
    print("_exprs[" + addExpr(expr) + "].evalBoolean(" + element + 
	  ", " + getEnv() + ")");
  }

  public void printExprTest(int exprId, String element)
    throws Exception
  {
    print("_exprs[" + exprId + "].evalBoolean(" + element + 
	  ", " + getEnv() + ")");
  }

  private boolean attributeHasSpecial(String string)
  {
    int length = string.length();
    
    for (int i = 0; i < length; i++) {
      char ch = string.charAt(i);

      if (ch == '{' && i + 1 < length) {
	// {{ is treated as a single {
	if (string.charAt(i + 1) == '{') {
	  i++;
          continue;
	}

        return true;
      }
      // <#= interpolates
      else if (i + 2 < length && ch == '<' && 
	       string.charAt(i + 1) == '#' &&
	       string.charAt(i + 2) == '=')
        return true;
    }

    return false;
  }

  /**
   * Produces code to generate an attribute value template.  The same
   * code is used to produce a string ('a{b}c' -> "a" + b + "c") or a series of
   * print statements (',').
   *
   * @param string the source template
   * @param mode separator: either '+' or ','
   * @param elt the containing element.  Needed for namespaces.
   */
  void generateString(String string, int mode, Element elt)
    throws Exception
  {
    CharBuffer cb = new CharBuffer();
    int i = 0;
    boolean first = true;
    int length = string.length();

    for (; i < length; i++) {
      char ch = string.charAt(i);

      if (ch == '\n') {
	cb.append("\\n");
      }
      else if (ch == '"') {
	cb.append("\\\"");
      }
      else if (ch == '{' && i + 1 < length) {
	// {{ is treated as a single {
	if (string.charAt(i + 1) == '{') {
	  cb.append('{');
	  i++;
	}
	// the value is computed from an XPath expr
	else {
	  // print the gathered text if any
	  if (mode == ',') {
	    if (cb.length() > 0)
	      println("out.print(\"" + cb.toString() + "\");");
	  }
	  else {
	    if (! first)
	      print((char) mode);

	    if (cb.length() > 0) {
	      print("\"");
	      print(cb.toString());
	      print("\"");
	      print((char) mode);
	    }
	  }

	  // scan the contents of '{' ... '}'
	  cb.clear();
	  for (i++; i < length && string.charAt(i) != '}'; i++)
	    cb.append(string.charAt(i));

	  // and add the results
	  if (mode == ',')
	    printStringExpr(cb.toString(), elt);
	  else
	    stringExpr(cb.toString(), elt);
	  cb.clear();
	  first = false;
	}
      }
      // }} is treated as a single }
      else if (ch == '}' && i + 1 < length) {
	if (string.charAt(i + 1) == '}') {
	  cb.append('}');
	  i++;
	}
	else
	  cb.append('}');
      }
      // <#= interpolates
      else if (i + 2 < length && ch == '<' && 
	       string.charAt(i + 1) == '#' &&
	       string.charAt(i + 2) == '=') {
	// print the gathered text if any
	if (mode == ',') {
	  if (cb.length() > 0)
	    println("out.print(\"" + cb.toString() + "\");");
	}
	else {
	  if (! first)
	    print((char) mode);

	  if (cb.length() > 0) {
	    print("\"");
	    print(cb.toString());
	    print("\"");
	    print((char) mode);
	  }
	}

	// scan the contents of '{' ... '}'
	cb.clear();
	for (i += 3;
	     i + 1 < length && string.charAt(i) != '#' &&
	       string.charAt(i + 1) != '>';
	     i++)
	  cb.append(string.charAt(i));

	i++;
	
	// and add the results
	if (mode == ',')
	  println("out.print(" + cb + ");");
	else {
	  print("(" + cb + ")");
	}
	cb.clear();
	first = false;
      }
      else
	cb.append((char) ch);
    }

    // add any trailing text
    if (cb.length() > 0) {
      if (mode == ',') 
	println("out.print(\"" + cb + "\");");
      else {
	if (! first)
	  print((char) mode);

	print("\"" + cb + "\"");
      }
    } else if (first && mode == '+')
      print("\"\"");
  }

  /**
   * Produces code to generate an attribute value template.  The same
   * code is used to produce a string ('a{b}c' -> "a" + b + "c") or a series of
   * print statements (',').
   *
   * @param string the source template
   * @param elt the containing element.  Needed for namespaces.
   *
   * @return the variable storing the generated string.
   */
  String generateStringVar(String string, Element elt)
    throws Exception
  {
    CharBuffer cb = new CharBuffer();
    int i = 0;
    boolean first = true;
    int length = string.length();

    String strVar = "_xsl_str" + _unique++;

    if (string.indexOf('{') < 0 &&
        string.indexOf('}') < 0) {
      print("String " + strVar + " = \"");
      printString(string);
      println("\";");
      
      return strVar;
    }
    else if (string.lastIndexOf('{') == 0 &&
        string.indexOf('}') == string.length() - 1) {
      println("String " + strVar + " = \"\";");
      string = string.substring(1, string.length() - 1);
      
      addStringExpr(strVar, string, elt, true);
      return strVar;
    }

    
    String cbVar = "_xsl_cb" + _unique++;

    println("com.caucho.util.CharBuffer " + cbVar +
            " = com.caucho.util.CharBuffer.allocate();");

    for (; i < length; i++) {
      char ch = string.charAt(i);

      if (ch == '\n') {
	cb.append("\\n");
      }
      else if (ch == '"') {
	cb.append("\\\"");
      }
      else if (ch == '{' && i + 1 < length) {
	// {{ is treated as a single {
	if (string.charAt(i + 1) == '{') {
	  cb.append('{');
	  i++;
	}
	// the value is computed from an XPath expr
	else {
	  // print the gathered text if any
          if (cb.length() > 0)
            println(cbVar + ".append(\"" + cb.toString() + "\");");

	  // scan the contents of '{' ... '}'
	  cb.clear();
	  for (i++; i < length && string.charAt(i) != '}'; i++)
	    cb.append(string.charAt(i));

	  // and add the results
          addStringExpr(cbVar, cb.toString(), elt, false);
          
	  cb.clear();
	  first = false;
	}
      }
      // }} is treated as a single }
      else if (ch == '}' && i + 1 < length) {
	if (string.charAt(i + 1) == '}') {
	  cb.append('}');
	  i++;
	}
	else
	  cb.append('}');
      }
      // <#= interpolates
      else if (i + 2 < length && ch == '<' && 
	       string.charAt(i + 1) == '#' &&
	       string.charAt(i + 2) == '=') {
	// print the gathered text if any
        if (cb.length() > 0)
          println(cbVar + ".append(\"" + cb.toString() + "\");");

	// scan the contents of '<#=' ... '#>'
	cb.clear();
	for (i += 3;
	     i + 1 < length && string.charAt(i) != '#' &&
	       string.charAt(i + 1) != '>';
	     i++)
	  cb.append(string.charAt(i));

	i++;
	
	// and add the results
        println(cbVar + ".append(" + cb + ");");
	cb.clear();
	first = false;
      }
      else
	cb.append((char) ch);
    }

    // add any trailing text
    if (cb.length() > 0)
      println(cbVar + ".append(\"" + cb + "\");");

    println("String " + strVar + " = " + cbVar + ".close();");

    return strVar;
    
  }

  /**
   * Prints a value-of expression
   */
  private void printStringExpr(String exprString, Element elt)
    throws Exception
  {
    int length = exprString.length();
    
    if (length == 0)
      return;

    AbstractPattern select = null;
    try {
      select = parseSelect(exprString);
    } catch (Exception e) {
    }
    
    if (exprString.equals(".")) {
      println("out.valueOf(node);");
      return;
    }
    else if (exprString.charAt(0) == '@') {
      boolean isSimple = true;
      
      for (int i = 1; i < length; i++) {
        char ch = exprString.charAt(i);
        if (! XmlChar.isNameChar(ch) || ch == ':')
          isSimple = false;
      }

      if (isSimple) {
        println("if (node instanceof Element)");
        print("  out.print(((Element) node).getAttribute(\"");
        print(exprString.substring(1));
        println("\"));");
        return;
      }
    }
    else if (allowJavaSelect(select)) {
      int oldSelectDepth = _selectDepth;

      String loop = "_xsl_loop" + _unique++;
      _selectLoopDepth = 0;
      
      String ptr = printSelectBegin(select, true, loop);

      println("out.valueOf(" + ptr + ");");
      println("break " + loop + ";");

      for (; _selectDepth > oldSelectDepth; _selectDepth--) {
        popDepth();
        println("}");
      }

      return;
    }

    println("out.valueOf(_exprs[" + addExpr(exprString) +
            "].evalObject(node, " + getEnv() + "));");
  }

  /**
   * Prints a value-of expression
   */
  private void addStringExpr(String var, String exprString,
                             Element elt, boolean isSingleString)
    throws Exception
  {
    int length = exprString.length();
    
    if (length == 0)
      return;

    AbstractPattern select = null;
    try {
      select = parseSelect(exprString);
    } catch (Exception e) {
    }
    
    if (exprString.equals(".")) {
      if (isSingleString)
        println(var + " = XmlUtil.textValue(node);");
      else
        println("XmlUtil.textValue(" + var + ", node);");
      return;
    }
    else if (exprString.charAt(0) == '@') {
      boolean isSimple = true;
      
      for (int i = 1; i < length; i++) {
        char ch = exprString.charAt(i);
        if (! XmlChar.isNameChar(ch) || ch == ':')
          isSimple = false;
      }

      if (isSimple) {
        println("if (node instanceof Element)");
        if (isSingleString) {
          print("  " + var + " = ((Element) node).getAttribute(\"");
          print(exprString.substring(1));
          println("\");");
        }
        else {
          print("  " + var + ".append(((Element) node).getAttribute(\"");
          print(exprString.substring(1));
          println("\"));");
        }
        return;
      }
    }
    else if (allowJavaSelect(select)) {
      int oldSelectDepth = _selectDepth;

      String loopVar = "_xsl_loop" + _unique++;
      _selectLoopDepth = 0;
      
      String ptr = printSelectBegin(select, true, loopVar);

      if (isSingleString)
        println(var + " = XmlUtil.textValue(" + ptr + ");");
      else
        println("XmlUtil.textValue(" + var + ", " + ptr + ");");
      println("break " + loopVar + ";");

      for (; _selectDepth > oldSelectDepth; _selectDepth--) {
        popDepth();
        println("}");
      }

      return;
    }

    if (isSingleString) {
      println(var + " = _exprs[" + addExpr(exprString) +
              "].evalString(node, " + getEnv() + ");");
    }
    else {
      println("_exprs[" + addExpr(exprString) + "].evalString(" +
              var + ", node, " + getEnv() + ");");
    }
  }

  /**
   * Prints iterator code to start a select.
   */
  private String printSelectBegin(AbstractPattern select,
                                  boolean isForEach, String loopVar)
    throws IOException, XslParseException
  {
    if (select == null)
      throw new NullPointerException();
    
    if (select instanceof FromContext &&
        ((FromContext) select).getCount() == 0)
      return "node";

    else if (select instanceof FromRoot)
      return "ownerDocument(node)";

    boolean useXPath = allowJavaSelect(select);
    
    String name = "node";

    if (! useXPath) {
      // punt and let XPath handle it.
      String iterName = "_xsl_iter" + _unique++;
      
      String ptrName = "_xsl_ptr" + _unique++;

      if (isForEach)
        println("env.setCurrentNode(node);");
      println("Iterator " + iterName + " = _select_patterns[" +
              addSelect(select) + "].select(" + name + ", env);");
      
      if (loopVar != null && _selectLoopDepth == 0)
        println(loopVar + ":");
      
      println("while (" + iterName + ".hasNext()) {");
      pushDepth();
      _selectDepth++;
      _selectLoopDepth++;
      println("Node " + ptrName + " = (Node) " + iterName + ".next();");

      return ptrName;
    }

    if (select instanceof FromChildren) {
      name = printSelectBegin(select.getParent(), isForEach, loopVar);
      
      String ptrName = "_xsl_ptr" + _unique++;

      if (loopVar != null && _selectLoopDepth == 0)
        println(loopVar + ":");
      
      println("for (Node " + ptrName + " = " + name + ".getFirstChild();");
      println("     " + ptrName + " != null;");
      println("     " + ptrName + " = " + ptrName + ".getNextSibling()) {");
      pushDepth();
      _selectDepth++;
      _selectLoopDepth++;

      return ptrName;
    }
    else if (select instanceof FromNextSibling) {
      name = printSelectBegin(select.getParent(), isForEach, loopVar);
      
      String ptrName = "_xsl_ptr" + _unique++;
      
      if (loopVar != null && _selectLoopDepth == 0)
        println(loopVar + ":");
      
      println("for (Node " + ptrName + " = " + name + ".getNextSibling();");
      println("     " + ptrName + " != null;");
      println("     " + ptrName + " = " + ptrName + ".getNextSibling()) {");
      pushDepth();
      _selectDepth++;
      _selectLoopDepth++;

      return ptrName;
    }
    else if (select instanceof NodePattern) {
      name = printSelectBegin(select.getParent(), isForEach, loopVar);
      
      NodePattern pat = (NodePattern) select;
      
      println("if (" + name + ".getNodeName() == \"" + pat.getNodeName() + "\" &&");
      println("    " + name + " instanceof Element) {");
      pushDepth();
      _selectDepth++;

      return name;
    }
    else if (select instanceof NodeTypePattern) {
      name = printSelectBegin(select.getParent(), isForEach, loopVar);
      
      NodeTypePattern pat = (NodeTypePattern) select;

      if (pat.getNodeType() >= 0) {
        println("if (" + name + ".getNodeType() == " + pat.getNodeType() + ") {");
        pushDepth();
        _selectDepth++;
      }

      return name;
    }
    else if (select instanceof FilterPattern) {
      String posId = "_xsl_pos" + _unique++;

      println("int " + posId + " = 0;");
      
      name = printSelectBegin(select.getParent(), isForEach, loopVar);

      println(posId + "++;");
      
      FilterPattern pat = (FilterPattern) select;
      Expr expr = pat.getExpr();

      if (expr instanceof NumericExpr) {
        NumericExpr num = (NumericExpr) expr;
        if (num.isConstant()) {
          println("if (" + posId + " > " + (int) num.getValue() + ")");
          println("  break;");
          println("else if (" + posId + " == " + (int) num.getValue() + ") {");
          pushDepth();
          _selectDepth++;

          return name;
        }
      }

      throw new RuntimeException();
    }

    throw new RuntimeException(String.valueOf(select));
  }

  /**
   * Returns true if we can compile in the java select.
   */
  private boolean allowJavaSelect(AbstractPattern select)
  {
    if (select == null)
      return false;

    else if (! select.isStrictlyAscending())
      return false;
    
    else if (select instanceof FromContext)
      return ((FromContext) select).getCount() == 0;

    else if (select instanceof FromRoot)
      return true;

    else if (select instanceof NodePattern)
      return allowJavaSelect(select.getParent());

    else if (select instanceof NodeTypePattern)
      return allowJavaSelect(select.getParent());

    else if (select instanceof FromChildren)
      return allowJavaSelect(select.getParent());

    else if (select instanceof FromNextSibling)
      return allowJavaSelect(select.getParent());

    else if (select instanceof FilterPattern) {
      if (! allowJavaSelect(select.getParent()))
        return false;

      Expr expr = ((FilterPattern) select).getExpr();

      return ((expr instanceof NumericExpr) &&
              ((NumericExpr) expr).isConstant());
    }

    else
      return false;
  }

  private void stringExpr(String exprString, Element element)
    throws Exception, XslParseException
  {
    print("_exprs[" + addExpr(exprString) +
          "].evalString(node, " + getEnv() + ")");
  }

  /**
   * Adds an expression constant returning its index.
   *
   * @param expr the expression to add.
   *
   * @return the index into the runtime expression array
   */
  public int addExpr(Expr expr)
    throws XslParseException
  {
    String exprStr = expr.toString();

    int i = _exprMap.get(exprStr);
    if (i >= 0)
      return i;

    i = _exprs.size();
    _exprMap.put(exprStr, i);
    _exprs.add(expr);
    
    return i;
  }

  /**
   * Adds an expression constant returning its index.
   *
   * @param exprString the expression to add.
   *
   * @return the index into the runtime expression array
   */
  public int addExpr(String exprString)
    throws XslParseException
  {
    int i = _exprMap.get(exprString);

    if (i >= 0)
      return i;
    
    Expr expr = parseExpr(exprString);
    i = _exprs.size();
    _exprs.add(expr);

    _exprMap.put(exprString, i);

    return i;
  }

  /**
   * Adds a select pattern returning its index.
   *
   * @param select the select pattern to add.
   *
   * @return the index into the runtime expression array
   */
  public int addSelect(AbstractPattern select)
    throws IOException, XslParseException
  {
    String selectStr = select.toString();

    int i = _selectMap.get(selectStr);
    if (i >= 0)
      return i;

    i = _selectPatterns.size();
    _selectMap.put(selectStr, i);
    _selectPatterns.add(select);
    
    return i;
  }

  /**
   * Adds a select pattern, returning its index.
   *
   * @param selectString the expression to add.
   *
   * @return the index into the runtime select pattern array
   */
  public int addSelect(String selectString)
    throws IOException, XslParseException
  {
    int i = _selectMap.get(selectString);

    if (i >= 0)
      return i;
    
    AbstractPattern select = parseSelect(selectString);
    i = _selectPatterns.size();
    _selectPatterns.add(select);

    _selectMap.put(selectString, i);

    return i;
  }

  /**
   * Adds a match pattern, returning its index.
   *
   * @param pattern the expression to add.
   *
   * @return the index into the runtime expression array
   */
  public int addMatch(AbstractPattern pattern)
    throws XslParseException
  {
    int index = _matchPatterns.size();

    _matchPatterns.add(pattern);

    return index;
  }

  protected StylesheetImpl completeGenerate(ArrayList<XslNode> inits,
                                            ArrayList globals)
    throws Exception
  {
    printTemplates();
    printMacros();

    printInitVars(inits);
    printFragments();

    printInit();
    
    printStrings();
    printExpressions();
    printPatterns();

    popDepth();
    println("}");
    _s.close();
    _s = null;

    /*
    if (dbg.canWrite()) {
      ReadStream is = path.openRead();
      dbg.writeStream(is);
      is.close();
    }
    */
    
    if (_parentLoader instanceof DynamicClassLoader)
      ((DynamicClassLoader) _parentLoader).make();
    
    _compiler.compile(_path.getPath(), _lineMap);

    StylesheetImpl stylesheet;

    stylesheet = (StylesheetImpl) _xslGenerator.loadStylesheet(_path.getFullPath(),
                                                               _pkg + "." + _className);
    //if (stylesheet != null)
    //  stylesheet.init(context);

    return stylesheet;
  }

  private long getLastModified()
  {
    long lastModified = 0;
    for (int i = 0; i < _depends.size(); i++) {
      Path path = _depends.get(i);
      if (path.getLastModified() > lastModified)
	lastModified = path.getLastModified();
    }

    return lastModified;
  }

  /**
   * Generate code executed for all transformations.
   * <ul>
   * <li>Add the stylesheet namespaces to the generated document.
   * <li>Assign the global variables.
   * <li>Initialize the cache dependencies.
   */
  protected void printInitVars(ArrayList<XslNode> inits)
    throws Exception
  {
    println("private void _xsl_init_vars(XslWriter out, Node node, Env env)");
    println("  throws Exception");
    println("{");
    pushDepth();

    // Add the stylesheet namespaces to the generated document.
    HashMap namespaces = _qDoc.getNamespaces();
    if (namespaces != null) {
      Iterator prefixes = namespaces.keySet().iterator();
      while (prefixes.hasNext()) {
        String prefix = (String) prefixes.next();
        String url = (String) namespaces.get(prefix);

        if (url.startsWith("http://www.w3.org/XSL/Transform/") ||
            url.startsWith("http://www.w3.org/1999/XSL/Transform") ||
            url.startsWith("http://www.w3.org/XML/2000/xmlns") ||
            url.startsWith("http://www.w3.org/2000/xmlns") ||
	    url.equals(XTPNS))
          continue;
        else if (_excludedNamespaces.get(url) != null)
          continue;
        else if (_namespaceAliases.get(url) != null)
          continue;

	if (prefix == null)
	  println("out.addNamespace(\"\", \"" + url + "\");");
	else
	  println("out.addNamespace(\"" + prefix + "\", \"" + url + "\");");
      }
    }

    // Initialize the global stylesheet variables
    println("Object _xsl_tmp;");
    for (int i = 0; i < inits.size(); i++) {
      XslNode node = inits.get(i);
      // NamespaceContext oldNamespace = addNamespace(elt);

      node.generate(getOut());

      /*
      if ("variable".equals(getXslLocal(elt)) ||
          "assign".equals(getXslLocal(elt))) {
	String name = elt.getAttribute("name");
	String expr = elt.getAttribute("select");
	print("env.setGlobal(\"" + name + "\", ");
	if (! expr.equals(""))
	  printVariableValue(parseExpr(expr));
	else
	  printVariableValue(elt);
	println(");");
      }
      else if ("param".equals(getXslLocal(elt))) {
        String name = elt.getAttribute("name");
        String expr = elt.getAttribute("select");
        print("env.setGlobal(\"" + name + "\", ");
        if (! expr.equals(""))
          printVariableValue(parseExpr(expr));
        else
          printVariableValue(elt);
        println(");");
         
        println("_xsl_tmp = out.getParameter(\"" + name + "\");");
        println("if (_xsl_tmp != null)");
        println("  env.setGlobal(\"" + name + "\", _xsl_tmp);");
      }
      */
      
      // oldNamespace = _namespace;
    }

    // Initialize the cache dependencies.
    println("com.caucho.vfs.Path pwd;");
    println("pwd = (com.caucho.vfs.Path) out.getProperty(\"caucho.pwd\");");
    for (int i = 0; i < _cacheDepends.size(); i++) {
      String depend = (String) _cacheDepends.get(i);

      print("out.addCacheDepend(pwd.lookup(\"");
      printString(depend);
      println("\"));");
    }
    
    popDepth();
    println("}");
  }

  protected void printInit()
    throws Exception
  {
    println("protected void _xsl_init(XslWriter out, Node node, Env env)");
    println("  throws Exception");
    println("{");
    pushDepth();
    println("Object _xsl_tmp;");
    println("_xsl_init_vars(out, node, env);");

    // Generic init vars
    // println("templates = _staticTemplates;");

    for (int i = 0; _globalActions != null && i < _globalActions.size(); i++) {
      QAbstractNode node = (QAbstractNode) _globalActions.get(i);
      generateChild(node);
    }

    popDepth();
    println("}");

    // depends
    println("public boolean isModified()");
    println("{");
    pushDepth();
    println("return com.caucho.server.util.CauchoSystem.getVersionId() != " +
            CauchoSystem.getVersionId() + "L ||");
    println("       super.isModified();");
    popDepth();
    println("}");

    println("public void init(com.caucho.vfs.Path path)");
    println("  throws Exception");
    println("{");
    pushDepth();
    println("super.init(path);");
    println("com.caucho.vfs.Path pwd = path.getParent();");

    for (int i = 0; i < _depends.size(); i++) {
      Path path = _depends.get(i);
      
      if (path.canRead() && ! path.isDirectory()) {
	Depend depend = new Depend(path);
 
	print("addDepend(new com.caucho.vfs.Depend(pwd.lookup(\"");
	printString(path.getRelativePath());
	println("\"), " + depend.getDigest() + "L));");
      }
    }

    println("stylesheets = new StylesheetEnv[" + _stylesheets.size() + "];");
    println("StylesheetEnv env;");
    
    for (int i = 0; i < _stylesheets.size(); i++) {
      String ss = _stylesheets.get(i);

      println("env = new StylesheetEnv();");
      println("stylesheets[" + i + "] = env;");
      print("env.setPath(pwd.lookup(\"");
      printString(ss);
      println("\"));");
    }

    if (! _strip.isEmpty()) {
      println("HashMap preserve = new HashMap();");
      println("HashMap preservePrefix = new HashMap();");
      Iterator iter = _preserve.keySet().iterator();
      while (iter.hasNext()) {
        String key = (String) iter.next();
        if (key.endsWith(":*")) {
          String prefix = key.substring(0, key.length() - 2);
          println("preservePrefix.put(\"" + prefix + "\", \"true\");");
        }
        else
          println("preserve.put(\"" + key + "\", \"true\");");
      }
      println("HashMap strip = new HashMap();");
      println("HashMap stripPrefix = new HashMap();");
      iter = _strip.keySet().iterator();
      while (iter.hasNext()) {
        String key = (String) iter.next();
        if (key.endsWith(":*")) {
          String prefix = key.substring(0, key.length() - 2);
          println("stripPrefix.put(\"" + prefix + "\", \"true\");");
        }
        else
          println("strip.put(\"" + key + "\", \"true\");");
      }
      println("setSpaces(preserve, preservePrefix, strip, stripPrefix);");
    }

    printOutput();

    if (_errorPage != null) {
      print("setProperty(\"caucho.error.page\", \"");
      printString(_errorPage);
      println("\");");
    }

    if (_globalParameters != null && _globalParameters.size() > 0) {
      println("ArrayList params = new ArrayList();");
      for (int i = 0; i < _globalParameters.size(); i++) {
        String param = _globalParameters.get(i);

        println("params.add(\"" + param + "\");");
      }
      print("setProperty(\"caucho.global.param\", params);");
    }
    
    String disable = null;
    /*
    if (_outputAttributes != null)
      disable = (String) _outputAttributes.get("disable-output-escaping");
    if (disable != null && ! disable.equals("no") && ! disable.equals("false"))
      println("defaultDisableEscaping = true;");
    */
    
    if (_isRawText)
      println("_defaultDisableEscaping = true;");

    printNamespaces();
    printFunctions();
    printSorts();
    printFormats();
    
    popDepth();
    println("}");
  }

  /**
   * Sets the property for the xsl:output keys.
   */
  private void printOutput() throws Exception
  {
    Iterator iter = _outputAttributes.keySet().iterator();

    if (_outputAttributes.get("encoding") == null)
      println("_output.put(\"encoding\", \"utf-8\");");
    
    while (iter.hasNext()) {
      String key = (String) iter.next();
      String value = (String) _outputAttributes.get(key);

      println("_output.put(\"" + key + "\", \"" + value + "\");");
    }
  }

  private void printSorts() throws Exception
  {
    if (_sorts.size() == 0)
      return;

    println();
    println("_xsl_sorts = new com.caucho.xsl.Sort[][] { ");
    pushDepth();

    for (int i = 0; i < _sorts.size(); i++) {
      Sort []sorts = _sorts.get(i);

      print("new com.caucho.xsl.Sort[] {");
      
      for (int j = 0; j < sorts.length; j++) {
        Sort sort = sorts[j];

	Expr lang = sort.getLang();
	Expr caseOrder = sort.getCaseOrder();
	
        if (lang != null || caseOrder != null) {
          print("new com.caucho.xsl.Sort(\"" + sort.getExpr() + "\", " +
                "\"" + sort.getAscending() + "\", " +
                (lang == null ? "null, " : "\"" + lang + "\", ") +
                (caseOrder == null ? "null), " : "\"" + caseOrder + "\"), "));
        }
        else
          print("new com.caucho.xsl.Sort(\"" + sort.getExpr() + "\", " +
                "\"" + sort.getAscending() + "\", " +
                sort.isText() + "), ");
      }
      
      println("},");
    }
    popDepth();
    println("};");
  }

  private void printLocale(Locale locale) throws Exception
  {
    String language = locale.getLanguage();
    String country = locale.getCountry();
    String variant = locale.getVariant();

    if (variant != null && country != null) {
      print("new java.util.Locale(\"" + language + "\", " +
            "\"" + country + "\", \"" + variant + "\")");
    }
    else if (country != null) {
      print("new java.util.Locale(\"" + language + "\", " +
            "\"" + country + "\")");
    }
    else {
      print("new java.util.Locale(\"" + language + "\")");
    }
  }

  private void printFormats() throws Exception
  {
    if (_formats.size() == 0)
      return;

    println();
    println("_xsl_formats = new XslNumberFormat[] { ");
    pushDepth();

    for (int i = 0; i < _formats.size(); i++) {
      XslNumberFormat format = (XslNumberFormat) _formats.get(i);

      println("new XslNumberFormat(\"" + format.getFormat() + "\", \"" +
              format.getLang() + "\", " + format.isAlphabetic() + ", \"" +
              format.getGroupSeparator() + "\", " +
              format.getGroupSize() + "),");
    }
    popDepth();
    println("};");
  }

  private void printNamespaces() throws Exception
  {
    if (_namespaces.size() == 0)
      return;

    println();
    println("_namespaces = new NamespaceContext[] { ");
    pushDepth();

    for (int i = 0; i < _namespaces.size(); i++) {
      NamespaceContext ns = _namespaces.get(i);

      printNamespaceDef(ns);
      println(",");
    }
    popDepth();
    println("};");
  }

  private void printNamespaceDef(NamespaceContext ns) throws Exception
  {
    if (ns == null) {
      print("null");
      return;
    }

    print("new NamespaceContext(");
    printNamespaceDef(ns.getPrev());
    print(", \"" + ns.getPrefix() + "\", \"" + ns.getUrl() + "\")"); 
  }

  private void printFunctions() throws Exception
  {
    println();
    println("com.caucho.xsl.fun.KeyFun keyFun = new com.caucho.xsl.fun.KeyFun();");
    HashMap keys = _keyFun.getKeys();
    Iterator iter = keys.keySet().iterator();
    while (iter.hasNext()) {
      String name = (String) iter.next();
      KeyFun.Key key = (KeyFun.Key) keys.get(name);

      println("keyFun.add(\"" + name + "\", XPath.parseMatch(\"" +
              key.getMatch() + "\").getPattern(), XPath.parseExpr(\"" +
              key.getUse() + "\"));");
    }
    println("addFunction(\"key\", keyFun);");
    
    println();
    println("com.caucho.xsl.fun.FormatNumberFun formatFun = new com.caucho.xsl.fun.FormatNumberFun();");
    println("java.text.DecimalFormatSymbols symbols;");
    JavaWriter out = _out;

    HashMap locales = _formatNumberFun.getLocales();
    iter = locales.keySet().iterator();
    while (iter.hasNext()) {
      String name = (String) iter.next();
      DecimalFormatSymbols symbols = (DecimalFormatSymbols) locales.get(name);

      out.println("symbols = new java.text.DecimalFormatSymbols();");
      
      out.print("symbols.setDecimalSeparator(\'");
      out.printJavaChar(symbols.getDecimalSeparator());
      out.println("\');");
      
      out.print("symbols.setGroupingSeparator(\'");
      out.printJavaChar(symbols.getGroupingSeparator());
      out.println("\');");
      
      out.print("symbols.setInfinity(\"");
      out.printJavaString(symbols.getInfinity());
      out.println("\");");
      
      out.print("symbols.setMinusSign(\'");
      out.printJavaChar(symbols.getMinusSign());
      out.println("\');");
      
      out.print("symbols.setNaN(\"");
      out.printJavaString(symbols.getNaN());
      out.println("\");");
      
      out.print("symbols.setPercent(\'");
      out.printJavaChar(symbols.getPercent());
      out.println("\');");
      
      out.print("symbols.setPerMill(\'");
      out.printJavaChar(symbols.getPerMill());
      out.println("\');");
      
      out.print("symbols.setZeroDigit(\'");
      out.printJavaChar(symbols.getZeroDigit());
      out.println("\');");
      
      out.print("symbols.setDigit(\'");
      out.printJavaChar(symbols.getDigit());
      out.println("\');");
      
      out.print("symbols.setPatternSeparator(\'");
      out.printJavaChar(symbols.getPatternSeparator());
      out.println("\');");

      println("formatFun.addLocale(\"" + name + "\", symbols);");
    }
    
    println("addFunction(\"format-number\", formatFun);");
  }

  private void printMacros() throws Exception
  {
    /*
    for (int i = 0; i < _macros.size(); i++) {
      Macro macro = _macros.get(i);

      println("void " + getMacroName(macro.getName()) +
	      "(XslWriter out, Node inputNode, Env env)");
      println("  throws Exception");
      println("{");
      pushDepth();
      println("Object _xsl_tmp;");
      println("Node node = inputNode;");
      generateChildren(macro.getElement());
      popDepth();
      println("}");
    }
    */
  }

  private void printFragments() throws Exception
  {
    for (int i = 0; i < _fragments.size(); i++) {
      Element elt = (Element) _fragments.get(i);

      println("Object _xsl_fragment" + i +
              "(XslWriter out, Node inputNode, Env env )");
      println("  throws Exception");
      println("{");
      pushDepth();
      println("Object _xsl_tmp;");
      println("Node node = inputNode;");
      println("XMLWriter _xsl_frag = out.pushFragment();");
      generateChildren(elt);
      println("return out.popFragment(_xsl_frag);");
      popDepth();
      println("}");
    }
  }

  /**
   * Prints the template definitions, i.e. the set of XPath
   * match patterns to test a node.
   */
  private void printTemplates() throws Exception
  {
    for (int j = 0; j < _modes.size(); j++) {
      String mode = _modes.get(j);
      String modeName = getModeName(mode);

      printApplyNode(mode);

      println();
      println("static HashMap _static_templates" + modeName + ";");
      println();
      println("static {");
      pushDepth();
      println("_static_templates" + modeName + " = new HashMap();");
      println("Template []values;");

      println("try {");
      pushDepth();

      ArrayList defaultTemplateList = (ArrayList) _templates.get("*");
      if (defaultTemplateList == null)
        defaultTemplateList = new ArrayList();
      println("Template []star = new Template[] {");
      pushDepth();
      for (int i = 0; i < defaultTemplateList.size(); i++) {
        Template template = (Template) defaultTemplateList.get(i);
        
        if (template.getMode().equals(mode)) {
          printTemplate(template);
          println(",");
        }
      }
      popDepth();
      println("};");
      println();
      println("_static_templates" + modeName + ".put(\"*\", star);");

      int count = _templates.size();

      for (int i = 0; i < count; i += 64)
        println("_init_templates" + modeName + "_" + i + "(star);");

      popDepth();
      println("} catch (Exception e) {");
      println("  e.printStackTrace();");
      println("}");
    
      popDepth();
      println("}");

      for (int i = 0; i < count; i += 64)
        printTemplateInitFun(mode, i, 64, defaultTemplateList);
    }
  }
  
  /**
   * Prints a function to initialize some of the templates.
   */
  private void printApplyNode(String mode)
    throws Exception
  {
    String modeName = getModeName(mode);
    
    print("protected void applyNode" + modeName);
    println("(XslWriter out, Node node, Env env, int _xsl_min, int _xsl_max)");
    println("  throws Exception");
    println("{");
    pushDepth();
    println("Object _xsl_tmp;");
    println();
    println("switch (getTemplateId(_static_templates" + modeName + ", " +
            "node, env, _xsl_min, _xsl_max)) {");
    // XXX: name issue below functions/templateList
    for (int i = 0; i < _functions.size(); i++) {
      Template template = (Template) _templateList.get(i);

      if (template == null || ! template.getMode().equals(mode))
        continue;
      
      println("case " + (i + 1) + ":");
      println("  " + _functions.get(i) + "(out, node, env);");
      println("  break;");
    }
    println("default:");
    println("  switch (node.getNodeType()) {");
    println("  case Node.ELEMENT_NODE:");
    println("  case Node.DOCUMENT_NODE:");
    println("  case Node.DOCUMENT_FRAGMENT_NODE:");
    println("    env.setSelect(node, null);");
    println("    for (Node child = node.getFirstChild();");
    println("         child != null;");
    println("         child = child.getNextSibling()) {");
    println("      env.setCurrentNode(child);");
    println("      applyNode" + modeName + "(out, child, env, 0, " + Integer.MAX_VALUE + ");");
    println("    }");

    println("    break;");
    println("  default:");
    println("    applyNodeDefault(out, node, env);");
    println("    break;");
    println("  }");
    println("  break;");
    println("}");
    popDepth();
    println("}");
  }

  /**
   * Prints a function to initialize some of the templates.
   */
  private void printTemplateInitFun(String mode, int offset, int length,
                                    ArrayList defaultTemplateList)
    throws Exception
  {
    String modeName = getModeName(mode);
                                  
    println("private static void _init_templates" + modeName + "_" + offset + "(Template []star)");
    println("  throws Exception");
    println("{");
    pushDepth();

    Iterator<String> iter = _templates.keySet().iterator();
    while (iter.hasNext() && length > 0) {
      String name = iter.next();
      
      if (name.equals("*"))
        continue;

      ArrayList templateList = (ArrayList) _templates.get(name);
      
      if (modeTemplateCount(mode, templateList) == 0)
        continue;

      if (offset > 0) {
        offset--;
        continue;
      }

      println("_static_templates" + modeName + ".put(\"" + name + "\", ");
      println("  mergeTemplates(star, new Template[] {");
      pushDepth();
      pushDepth();
      
      for (int i = 0; i < templateList.size(); i++) {
        Template template = (Template) templateList.get(i);

        if (template.getMode().equals(mode)) {
          printTemplate(template);
          println(",");
        }
      }

      popDepth();
      popDepth();
      println("}));");

      length--;
    }

    popDepth();
    println("}");
  }

  /**
   * Returns true if the template list contains a template with the given
   * mode.
   */
  private int modeTemplateCount(String mode, ArrayList templateList)
  {
    int count = 0;
    
    for (int i = 0; i < templateList.size(); i++) {
      Template template = (Template) templateList.get(i);

      if (template.getMode().equals(mode))
        count++;
    }

    return count;
  }

  /**
   * Prints initialization code for a single template.
   */
  private void printTemplate(Template template)
    throws IOException
  {
    print("new Template(");
    AbstractPattern pattern = template.getPattern();
    print("XPath.parseMatch(\"" +
          template.getPattern().toPatternString() + "\").getPattern(), ");
    print("\"" + template.getMode() + "\", ");
    print(template.getMin() + ", ");
    print(template.getMax() + ", ");
    print(template.getPriority() + ", ");
    print(template.getCount() + ", ");
    print("\"" + template.getFunction() + "\", ");
    print("" + template.getId() + ")");
  }

  /**
   * Prints the constant strings.
   */
  private void printStrings() throws Exception
  {
    for (int j = 0; j < _strings.size(); j++) {
      String text = (String) _strings.get(j);
    
      print("static char[] _xsl_string" + j + " = \"");

      printString(text);

      println("\".toCharArray();");
    }
  }

  /**
   * Prints the precompiled XPath expressions as static variables.
   */
  private void printExpressions() throws Exception
  {
    if (_exprs.size() == 0)
      return;
    
    println("private static Expr []_exprs;");
    println("static {");
    pushDepth();
    println("try {");
    pushDepth();
    
    println("_exprs = new Expr[] { ");
    pushDepth();

    for (int i = 0; i < _exprs.size(); i++) {
      Expr expr = _exprs.get(i);

      println("XPath.parseExpr(\"" + expr + "\"),");

      // System.out.println("EXPR: " + expr + " " + expr.getListContext());
      /*
      if (expr.getListContext() == null) // || currentPos != null)
        println("XPath.parseExpr(\"" + expr + "\"),");
      else {
        print("XPath.parseExpr(\"" + expr + "\", null,");
        println("XPath.parseMatch(\"" + expr.getListContext() +"\").getPattern()),");
      }
      */

    }
    popDepth();
    println("};");

    popDepth();
    println("} catch (Exception e) {");
    println("  e.printStackTrace();");
    println("}");
    
    popDepth();
    println("}");
  }

  /**
   * Prints the precompiled XPath select and match patterns as static
   * variables.
   */
  private void printPatterns() throws Exception
  {
    if (_selectPatterns.size() == 0 && _matchPatterns.size() == 0)
      return;
    
    println("private static com.caucho.xpath.pattern.AbstractPattern []_select_patterns;");
    println("private static com.caucho.xpath.pattern.AbstractPattern []_match_patterns;");
    println("static {");
    pushDepth();
    println("try {");
    pushDepth();
    
    println("_select_patterns = new com.caucho.xpath.pattern.AbstractPattern[] { ");
    pushDepth();

    for (int i = 0; i < _selectPatterns.size(); i++) {
      AbstractPattern pattern = _selectPatterns.get(i);

      println("XPath.parseSelect(\"" + pattern + "\").getPattern(),");
    }
    popDepth();
    println("};");
    
    println("_match_patterns = new com.caucho.xpath.pattern.AbstractPattern[] { ");
    pushDepth();

    for (int i = 0; i < _matchPatterns.size(); i++) {
      AbstractPattern pattern = _matchPatterns.get(i);

      println("XPath.parseMatch(\"" + pattern + "\").getPattern(),");
    }
    popDepth();
    println("};");

    popDepth();
    println("} catch (Exception e) {");
    println("  e.printStackTrace();");
    println("}");
    
    popDepth();
    println("}");
  }

  private boolean isSingleStylesheet()
  {
    // return stylesheets.size() < 2;
    return false;
  }

  /**
   * Prints a character to the generated Java.
   */
  private void print(char ch)
    throws IOException
  {
    _out.print(ch);
  }

  /**
   * Prints a string to the generated Java.
   */
  private void print(String string)
    throws IOException
  {
    _out.print(string);
  }

  /**
   * Prints an integer to the generated Java.
   */
  private void print(int i)
    throws IOException
  {
    _out.print(i);
  }

  /**
   * Prints a new line.
   */
  private void println()
    throws IOException
  {
    _out.println();
  }

  private void println(char ch)
    throws IOException
  {
    _out.println(ch);
  }

  private void println(String s)
    throws IOException
  {
    _out.println(s);
  }

  /**
   * Pushes the pretty-printed depth of the generated java.
   */
  private void pushDepth()
    throws IOException
  {
    _out.pushDepth();
  }

  /**
   * Pops the pretty-printed depth of the generated java.
   */
  private void popDepth()
    throws IOException
  {
    _out.popDepth();
  }

  /**
   * Prints the contents of a string, taking care of escapes.
   */
  protected void printString(String str) throws IOException
  {
    _out.printJavaString(str);
  }

  /**
   * Returns the name of the applyNode method.
   *
   * @param mode the template's mode.
   */
  public String getModeName(String mode)
  {
    if (mode != null && ! _modes.contains(mode))
      _modes.add(mode);

    if (mode == null || mode.equals(""))
      return "";
    else
      return "_" + toJavaIdentifier(mode);
  }

  public void addMode(String mode)
  {
    if (! _modes.contains(mode))
      _modes.add(mode);
  }

  public int addStylesheet(String filename)
  {
    int pos = _stylesheets.indexOf(filename);
    
    if (pos < 0) {
      pos = _stylesheets.size();
      _stylesheets.add(filename);
    }

    return pos;
  }

  /**
   * Converts a string to a Java identifier, encoding unknown characters
   * as "_"
   */
  public String toJavaIdentifier(String name)
  {
    CharBuffer cb = new CharBuffer();

    char ch = name.charAt(0);
    if (Character.isJavaIdentifierStart(ch))
      cb.append(ch);
    else
      cb.append("_");

    for (int i = 1; i < name.length(); i++) {
      ch = name.charAt(i);
      
      if (Character.isJavaIdentifierPart(ch))
	cb.append(ch);
      else {
	cb.append("_");
        cb.append((char) ((ch & 0xf) + 'a'));
        cb.append((char) ((ch / 16 & 0xf) + 'a'));
      }
    }

    return cb.toString();
  }

  /**
   * Close call when an error occurs.
   */
  public void close()
    throws IOException
  {
    if (_s != null)
      _s.close();
  }
  
  static class Macro {
    String _name;
    Element _elt;

    Macro(String name, Element elt)
    {
      _name = name;
      _elt = elt;
    }

    public Element getElement()
    {
      return _elt;
    }
    
    public String getName()
    {
      return _name;
    }
  }

  static {
    _tagMap = new HashMap<QName,Class>();
    
    _tagMap.put(new QName("xsl", "attribute", XSLNS), XslAttribute.class);
    _tagMap.put(new QName("xsl", "attribute-set", XSLNS),
		XslAttributeSet.class);
    _tagMap.put(new QName("xsl", "apply-imports", XSLNS),
		XslApplyImports.class);
    _tagMap.put(new QName("xsl", "apply-templates", XSLNS),
		XslApplyTemplates.class);
    _tagMap.put(new QName("xsl", "call-template", XSLNS), XslCallTemplate.class);
    _tagMap.put(new QName("xsl", "choose", XSLNS), XslChoose.class);
    _tagMap.put(new QName("xsl", "comment", XSLNS), XslComment.class);
    _tagMap.put(new QName("xsl", "copy", XSLNS), XslCopy.class);
    _tagMap.put(new QName("xsl", "copy-of", XSLNS), XslCopyOf.class);
    _tagMap.put(new QName("xsl", "decimal-format", XSLNS),
		XslDecimalFormat.class);
    _tagMap.put(new QName("xsl", "element", XSLNS), XslElement.class);
    _tagMap.put(new QName("xsl", "for-each", XSLNS), XslForEach.class);
    _tagMap.put(new QName("xsl", "if", XSLNS), XslIf.class);
    _tagMap.put(new QName("xsl", "import", XSLNS), XslImport.class);
    _tagMap.put(new QName("xsl", "include", XSLNS), XslInclude.class);
    _tagMap.put(new QName("xsl", "key", XSLNS), XslKey.class);
    _tagMap.put(new QName("xsl", "message", XSLNS), XslMessage.class);
    _tagMap.put(new QName("xsl", "namespace-alias", XSLNS),
		XslNamespaceAlias.class);
    _tagMap.put(new QName("xsl", "number", XSLNS), XslNumber.class);
    _tagMap.put(new QName("xsl", "otherwise", XSLNS), XslOtherwise.class);
    _tagMap.put(new QName("xsl", "output", XSLNS), XslOutput.class);
    _tagMap.put(new QName("xsl", "param", XSLNS), XslParam.class);
    _tagMap.put(new QName("xsl", "processing-instruction", XSLNS),
		XslProcessingInstruction.class);
    _tagMap.put(new QName("xsl", "sort", XSLNS), XslSort.class);
    _tagMap.put(new QName("xsl", "stylesheet", XSLNS), XslStylesheet.class);
    _tagMap.put(new QName("xsl", "text", XSLNS), XslText.class);
    _tagMap.put(new QName("xsl", "transform", XSLNS), XslTransform.class);
    _tagMap.put(new QName("xsl", "value-of", XSLNS), XslValueOf.class);
    _tagMap.put(new QName("xsl", "variable", XSLNS), XslVariable.class);
    _tagMap.put(new QName("xsl", "when", XSLNS), XslWhen.class);
    _tagMap.put(new QName("xsl", "with-param", XSLNS), XslWithParam.class);
    
    _tagMap.put(new QName("xsl", "template", XSLNS),
		XslTemplate.class);
    _tagMap.put(new QName("xsl", "strip-space", XSLNS),
		XslStripSpace.class);
    _tagMap.put(new QName("xsl", "preserve-space", XSLNS),
		XslPreserveSpace.class);
    _tagMap.put(new QName("xsl", "result-document", XSLNS),
		XslResultDocument.class);
    
    _tagMap.put(new QName("xtp", "expression", XTPNS),
		XtpExpression.class);
    _tagMap.put(new QName("xtp:expression", null), XtpExpression.class);
    _tagMap.put(new QName("xtp", "eval", XTPNS), XtpExpression.class);
    _tagMap.put(new QName("xtp:eval", null), XtpExpression.class);
    _tagMap.put(new QName("xtp", "expr", XTPNS), XtpExpression.class);
    _tagMap.put(new QName("xtp:expr", null), XtpExpression.class);
    _tagMap.put(new QName("xtp", "scriptlet", XTPNS),
		XtpScriptlet.class);
    _tagMap.put(new QName("xtp:scriptlet", null), XtpScriptlet.class);
    _tagMap.put(new QName("xtp", "declaration", XTPNS),
		XtpDeclaration.class);
    _tagMap.put(new QName("xtp", "decl", XTPNS),
		XtpDeclaration.class);
    _tagMap.put(new QName("xtp:declaration", null), XtpDeclaration.class);
    _tagMap.put(new QName("xtp:decl", null), XtpDeclaration.class);
    _tagMap.put(new QName("xtp:directive.page", null), XtpDirectivePage.class);
    _tagMap.put(new QName("xtp", "directive.page", XTPNS),
		XtpDirectivePage.class);
    _tagMap.put(new QName("xtp:directive.cache", null), XtpDirectiveCache.class);
    _tagMap.put(new QName("xtp", "directive.cache", XTPNS),
		XtpDirectiveCache.class);
    _tagMap.put(new QName("xtp:assign", null), XslVariable.class);
    _tagMap.put(new QName("xtp", "assign", XTPNS),
		XslVariable.class);
  }
}
