/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.servlet.template;

import org.bodington.servlet.*;
import org.bodington.servlet.facilities.*;
import org.bodington.i18n.Localiser;
import org.bodington.xml.ClasspathEntityResolver;

import java.io.*;
import java.lang.reflect.*;
import java.net.URI;
import java.util.Hashtable;

import javax.servlet.*;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.sun.tools.javac.Main;



/**
 * Class that encapsulates the process of template compilation. This class
 * handles both the newer (XML) style of template and acts as a wrapper to the
 * legacy ({@link org.bodington.servlet.template.LegacyTemplate}) style too.
 * This class encapsulates references to the template source (<code>.html</code>
 * file) and its corresponding compiled form (<code>.class</code> file). This
 * class performs the compilation, handling the dependancies based on file
 * timestamps, itself.
 * 
 * At the moment this class has an unnecessary dependacy on ServletException 
 * which should be removed so that is can exist cleanly outside a servlet container.
 *  
 * @author bmb6jrm
 * @see org.bodington.servlet.template.LegacyTemplate
 */
public class XmlTemplate
{
    private static final String[] expression_tag_names=
    {
	"literal",
	"variable",
	"assign",
	"call",
	"class"
    };
    
    private static final String[] htmlfragment_tag_names=
    {
	"htmlfragment"
    };
    
    private static final String[] call_tag_names=
    {
	"target",
	"parameters"
    };
    
    private static boolean getDoneCompilerOutputCheck=false;
    private static boolean getCanGetCompilerOutput = false;
    
    // class loaders hashed against class directories
    private static Hashtable class_loaders = new Hashtable();
    
    
    
    private static DocumentBuilderFactory docbuilderfactory;
    private static org.xml.sax.EntityResolver entity_resolver;
    static
    {
	docbuilderfactory = DocumentBuilderFactory.newInstance();
	docbuilderfactory.setValidating( true );
	entity_resolver = new ClasspathEntityResolver(XmlTemplate.class);
    }

    // This is set by <i18n resoource-file=""/>
    private String resourceFile = null;
    Localiser res = null;

    // This controls what language resources to use and is set via Template.setProcessorLangauge
    private String language = null;

    XmlTemplateClassLoader class_loader;
    XmlTemplateProcessor processor;
    
    File xml_source;
    File class_dir;
    String class_name;
    String class_path;
    String lang_neutral_class_name;
    
    // indicates the timestamp of the version of the xml file that was
    // last compiled
    long compiled_xml_source_timestamp = -1;
    
    boolean compiling;
    boolean languageChanged = false;

    //Hashtable attribute_tables = new Hashtable();
    PrintWriter writer=null;
    boolean preparation_done;
    String html_body_class=null;
    Hashtable template_variable_class_table;
    
    
    
    /**
     * Create an instance of this class.
     * @param xml_source the absolute path to the template source file.
     * @param class_dir the destination directory of the compiled class file.
     * @param class_name the name of the compiled class file. (This is the
     * <i>language-neutral</i> name of the file).
     * @param class_path the <code>CLASSPATH</code> to be used for the template 
     * java -> bytecode compilation stage.
     */
    public XmlTemplate( File xml_source, File class_dir, String class_name, String class_path  )
    {
	this.xml_source = xml_source;
	this.class_dir = class_dir;
	this.class_name = class_name;
	this.class_path = class_path;
	processor = null;
	compiling = false;
    this.lang_neutral_class_name = this.class_name = class_name;
	class_loader = getSuitableClassLoader();
	File class_file = class_loader.getClassFile( class_name );

    // We note the compiled XML timestamp in case it changes within the lifespan
    // of this object.
	if ( class_file !=null && xml_source!=null && xml_source.exists() )
		this.compiled_xml_source_timestamp = xml_source.lastModified();
    }
    
    /**
     * Create an instance of this class. This version of the constructor, uses
     * the method {@link #templateToClassName(String)} to generate the class
     * name, by relativizing the template name against the value of the
     * <code>srcdir</code> argument.
     * @param srcdir the base directory containing the template source
     * @param xml_source the absolute path to the template source file.
     * @param class_dir the destination directory of the compiled class file.
     * @param class_path the <code>CLASSPATH</code> to be used for the
     *        template java -> bytecode compilation stage.
     * @see #XmlTemplate(File, File, String, String)
     */
    public XmlTemplate( File srcdir, File xml_source, File class_dir, 
        String class_path  )
    {
        this( xml_source, class_dir, templateToClassName( relativize( srcdir,
            xml_source ) ), class_path );
    }
    
    /**
     * Relativize one file against the other. The first argument is the base
     * directory and the second is the file to be relativized against it.
     * @param baseDir the base directory to be relativized against.
     * @param file the file to be relativized.
     * @return the second argument in relativized form.
     * @see java.net.URI
     */
    protected static String relativize( File baseDir, File file )
    {
        URI baseURI = baseDir.toURI();
        URI uri = baseURI.relativize( file.toURI() );
        return uri.toString();
    }
    
    /**
     * Create a class name for the specified template name. This method expects
     * the argument to be in <i>relative URI form</i>. An example of this would
     * be <code>style_default/default/index.html</code>. 
     * @param template the path to the template (in relative URI form).
     * @return the corresponding class name.
     */
    protected static String templateToClassName( String template )
    {
        StringBuffer sb = new StringBuffer( template );
        // prepend "working.":
        sb.insert( 0, "working." );
        // replace first "/" with "_":
        int i = sb.indexOf( "/" );
        sb.replace( i, i + 1, "_" );
        // replace last "/" with ".template_":
        i = sb.lastIndexOf( "/" );
        sb.replace( i, i + 1, ".template_" );
        // replace all "-" with "_":
        while ( (i = sb.indexOf( "-" )) != -1 )
            sb.replace( i, i + 1, "_" );
        // remove ".html" from end:
        sb.delete( sb.length() - ".html".length(), sb.length() );
        return sb.toString();
    }
    
    private ErrorHandler errorHandler;
    
    /**
     * Set the error handler to be used for this template. This method enables
     * a caller to register a handler for XML template parsing errors and 
     * warnings.
     * @param errorHandler the error handler to be used.
     * @see #getErrorHandler()
     */
    public void setErrorHandler(ErrorHandler errorHandler)
    {
        this.errorHandler = errorHandler;
    }
    
    /**
     * Get the error handler used by this template.
     * @return the error handler used by this template.
     * @see #setErrorHandler(ErrorHandler)
     */
    public ErrorHandler getErrorHandler()
    {
        return errorHandler;
    }
    
    private int timestampLatency = 5;
    
    /**
     * Set the value of the timestampLatency property. When this class carries
     * out a dependancy check to see whether or not the HTML templates needs to
     * be compiled or not, it looks at the timestamps of the HTML file and the
     * corresponding class file. If the HTML file is newer than the class file,
     * this indicates that a compilation is required. The value of this property
     * can be used to add a <i>latency</i> to this dependancy check. For
     * example, with a value of <code>5</code> the HTML file can be upto 5
     * seconds older than the class file, before {@link #needsCompiling()} will
     * return a value of <code>true</code>. At <i>design-time</i> users will
     * probably want to give this property a value of <code>0</code> (for
     * obvious reasons). By default the value of this property is <code>5</code>.
     * <p>
     * <i><B>NOTE:</B> the need for this property arose when deploying on
     * Tomcat v5.5. This is because it was discovered that when deploying in the
     * form of a war, the unpacking algorithm always unpacked the class files
     * before the HTML files. This meant that the HTML templates were always
     * considered to be newer, thus rendering pre-compilation futile! Therefore,
     * this property is set to <code>5</code> by default, but can be
     * overridden if required.</i>
     * @param timestampLatency the new value of the timestampLatency property.
     * @see #needsCompiling()
     * @see #getTimestampLatency()
     */
    public void setTimestampLatency( int timestampLatency )
    {
        this.timestampLatency = timestampLatency;
    }

    /**
     * Get the value of the timestampLatency property (in whole seconds). 
     * @return the value of the timestampLatency property (in whole seconds).
     * @see #setTimestampLatency(int)
     */
    public int getTimestampLatency()
    {
        return timestampLatency;
    }

    /**
     * Indicates whether the template represented by this object need compiling.
     * This method compares the timestamps of the template HTML file and it's
     * corresponding class file. In general, if the timestamp of the HTML file
     * is newer than the timestamp of the class file, then this method returns
     * <code>true</code>.
     * @return <code>true</code> if the template represented by this object
     *         needs compiling, otherwise <code>false</code>.
     * @see #setTimestampLatency(int)
     */
    public synchronized boolean needsCompiling()
    {
    // The HTML file may get deleted within the life-cycle of this object ... 
	if ( !xml_source.exists() )
	    return false;
	
	// needs compiling if the template was replaced with a
	// version that has a different timestamp (older or newer)
	// than one that was loaded earlier in the lifetime of 
	// this Template object
	long xml_source_timestamp = xml_source.lastModified();
	if ( xml_source_timestamp != compiled_xml_source_timestamp )
	    return true;
	
	// needs compiling if the class file doesn't exist
	File class_file = class_loader.getClassFile( class_name );
	if ( class_file==null || !class_file.exists() )
	    return true;
	
	// needs compiling if the class file is older than the
	// source file (taking timestampLatency into account).
	long class_timestamp=class_file.lastModified();
	long latency = getTimestampLatency() * 1000; // whole seconds -> ms.
	return class_timestamp < (xml_source_timestamp - latency);
    }
    
    
    /**
     * Get an object to handle the compilation of this template in a web-app.
     * This method actually triggers the compilation process (if required) of
     * the underlying template. This signature of this method is clearly
     * tightly-coupled to a servlet-driven world, but it can be called from
     * elsewhere in order to drive template compilation. It has code to handle
     * template compilation in a multi-threaded environment. If in a
     * single-threaded environment and / or outside of a servlet-driven world,
     * you can call {@link #compile()} directly instead.
     * @return an object to handle the compilation of this template in a
     *         web-app.
     * @throws SAXParseException if there is some problem parsing an XML
     *         template.
     * @throws IOException if there is some problem parsing a legacy template.
     * @throws ParserConfigurationException if there is some problem configuring
     *         the XML parser.
     * @throws SAXException if there is some other problem with XML parsing.
     * @throws ServletException if there are compilation problems, but delivered
     *         in a form targeted at objects in the servlet world.
     * @throws Exception if there is some other problem (most likely configuring
     *         the java compiler).
     * @see #compile()
     */
    public XmlTemplateProcessor getProcessor() throws SAXParseException, 
        IOException, ParserConfigurationException, SAXException, 
        ServletException, Exception
    {
	if ( !xml_source.exists() && !xml_source.isFile() )
	    return null;
	
    if ( needsCompiling() )
    {
        synchronized ( this )
        {
            // if another thread is updating the class file
            // make do with the current one - don't bother waiting
            if ( processor != null && compiling ) 
                return processor;
            // if there isn't a current processor wait for the
            // compilation to complete
            if ( compiling )
            {
                while ( compiling )
                try
				{wait();} catch ( InterruptedException e )
				{}
                return processor;
            }
            // make sure this flag is set before anyone enters this
            // block
            compiling = true;
        }
        try
        {
            compile();
        	loadProcessor();
        	return processor;
        }
        finally
        {
            // guaranteed to clear the flag
            compiling = false;
            synchronized ( this )
            {
                // allow all the waiting threads to pick up
                // the new processor
                notifyAll();
            }
        }
    }
	
	// doesn't need compiling but may need to be loaded.
	if ( processor == null || class_loader.isClassRecompiled( class_name ) || (languageChanged)) {
      languageChanged = false;
	    loadProcessor();
  }

	return processor;
	
    }
    
    private void loadProcessor()
    {
	try
	{
	    Class cl =  class_loader.loadClass( class_name, true );
	    processor = (XmlTemplateProcessor)cl.newInstance();
	}
	catch ( Throwable th)
	{
	    processor = new XmlTemplateErrorProcessor( th.getMessage() );
	}
    }
    
    private PrintWriter error_writer = null;
    private boolean isDefaultErrorWriter = true;
    
    /**
     * Get the object to write java compilation error messages to.
     * @return the object to write java compilation error messages to.
     * @see #isDefaultErrorWriter()
     */
    public PrintWriter getErrorWriter()
    {
        return error_writer;
    }
    
    /**
     * Set the object to write java compilation error messages to.
     * @param error_writer the object to write java compilation error messages 
     * to.
     * @see #isDefaultErrorWriter()
     * @throws NullPointerException if the argument is null.
     */
    public void setErrorWriter(PrintWriter error_writer)
    {
        if (error_writer != null)
        {
            this.error_writer = error_writer;
            isDefaultErrorWriter = false;
        }
        else
            throw new NullPointerException("The argument must be non-null.");
    }
    
    /**
     * Indicates whether this instance is using the default error writer. By
     * default, instances of this class will write any errors from the java
     * compilation stage to a corresponding file. This file will be in the
     * same directory as the generated java file. It will have the same name,
     * but with the suffix <code>.err.txt</code>.
     * @return <code>true</code> if this instance is using the default error
     * writer, otherwise <code>false</code>.
     * @see #setErrorWriter(PrintWriter)
     * @see #getErrorWriter()
     */
    public boolean isDefaultErrorWriter()
    {
        return isDefaultErrorWriter;
    }
    
    /**
     * Compile the XML template.
     * @param xml_document a document representing the XML template.
     * @param directory the directory where the java source code and the binary
     *        class file should be output.
     * @param class_name the name of the template binary class file.
     * @param class_path the CLASSPATH to be used by the java compiler.
     * @return the return value of the java compiler. 
     * @throws ServletException if there are compilation problems, but delivered
     *         in a form targeted at objects in the servlet world.
     * @throws IOException if there is some kind of problem with legacy template
     *         compilation.
     * @throws Exception if there is some other problem (most likely configuring
     *         the java compiler).
     */
    private int compile( Document xml_document, File directory, String class_name, String class_path )
    throws ServletException, IOException, Exception
    {
        if ( !directory.exists() || !directory.isDirectory() )
	    throw new ServletException( "Invalid compilation directory for templates." );
	
	String file_name = class_name.replace( '.', File.separatorChar ) + ".java";
	File java_file = new File( directory, file_name );
	
	File parent_dir = java_file.getParentFile();
	if ( !parent_dir.exists() )
	    parent_dir.mkdirs();

	writer =
	new PrintWriter(
	new OutputStreamWriter(
	new FileOutputStream( java_file ), "US-ASCII" ) );
	
	String unqual_class_name;
	if ( class_name.indexOf( '.' ) > 0 )
	{
	    unqual_class_name = class_name.substring( class_name.lastIndexOf( '.' )+1 );
	    writer.print( "package " );
	    writer.print( class_name.substring( 0, class_name.lastIndexOf( '.' ) ) );
	    writer.println( ";" );
	}
	else
	    unqual_class_name = class_name;

  // See if there's an i18n attribute for the template...
  Element templateRootElement = xml_document.getDocumentElement();
  // ...if there is, set up the localisation of the template
  if (!templateRootElement.getAttribute("resources").equalsIgnoreCase(""))
    res = new Localiser(templateRootElement.getAttribute("resources"), language);
	    
	
	writer.println( "import org.bodington.servlet.*;" );
	writer.println( "import org.bodington.servlet.template.*;" );
	writer.println( "import java.io.*;" );
	writer.println( "import javax.servlet.*;" );
	writer.println( "import javax.servlet.http.*;" );
    writer.println( "import org.bodington.i18n.Localiser;" );

	writer.println( "public class " + unqual_class_name );
	writer.println( " extends XmlTemplateProcessor" );
	writer.println( "{" );
  if (!templateRootElement.getAttribute("resources").equalsIgnoreCase("")) {
    writer.println("Localiser res = null;");
    writer.println("String buffer = null;");
  }
	writer.println( "   public " + unqual_class_name + "()" );
	writer.println( "       {" );
  // Localisation intialised in the constructor
  if (!templateRootElement.getAttribute("resources").equalsIgnoreCase("")) {
    writer.println("res = new Localiser(\"" + templateRootElement.getAttribute("resources") + "\", \"" + language + "\");");
  }
	writer.println( "       }" );
	writer.println( "   public void process( Request request, Response response )" );
	writer.println( "       throws IOException, ServletException" );
	writer.println( "       {" );
	template_variable_class_table = new Hashtable();
	template_variable_class_table.put( "request", Request.class );
	template_variable_class_table.put( "response", Response.class );
	createTemplateVariable( "writer", PrintWriter.class );
	writer.println( "writer=null;" );

  if (!templateRootElement.getAttribute("resources").equalsIgnoreCase("")) {
    // ...and code the dynamic checking for localisation debug mode
    writer.println("buffer = (String)request.getSession().getAttribute(\"lang.debug\");");
    writer.println("if (buffer != null) {");
    writer.println("  if (buffer.equalsIgnoreCase(\"off\"))");
    writer.println("    res.setDebug(Localiser.DEBUG_OFF);");
    writer.println("  else if (buffer.equalsIgnoreCase(\"minimal\"))");
    writer.println("    res.setDebug(Localiser.DEBUG_MINIMAL);");
    writer.println("  else if (buffer.equalsIgnoreCase(\"verbose\"))");
    writer.println("    res.setDebug(Localiser.DEBUG_VERBOSE);");
    writer.println("}");
  }


	preparation_done = false;
	
        // needed for all HTML templates
        writer.println( "response.disableCaching();" );

	try
	{
	    Element template_element = xml_document.getDocumentElement();
	    if ( !"template".equals( template_element.getTagName() ) )
		throw new IOException( "Invalid template file." );
	    
	    compileElement( template_element );
	    
	    writer.println( "       }" );
	    writer.println( "   }" );
	    writer.close();
	    writer = null;
        
        if (isDefaultErrorWriter())
        {
            File error_log_file = new File( directory, 
                class_name.replace( '.', File.separatorChar ) + ".err.txt" );    
            error_writer = new PrintWriter( new FileWriter( error_log_file ) );
            error_writer.println( "Compilation messages..." );
            error_writer.println( "-classpath " + class_path );
        }
            
        // JDK 1.4.0 does not have the static method 
        // int compile(String[], PrintWriter), so check to see if available and 
        // fall back if not. If this fails don't try again for lifetime of JVM.
        if ( !getDoneCompilerOutputCheck )
        {
            try
            {
                Main.class.getMethod("compile", 
                    new Class[] {new String[0].getClass(), PrintWriter.class});
                getCanGetCompilerOutput = true;
            }
            catch ( Exception e )
            {
                getCanGetCompilerOutput = false;
            }
            finally
            {
                getDoneCompilerOutputCheck = true;
        	}
        }
        
        if (!getCanGetCompilerOutput && isDefaultErrorWriter())
        {
            error_writer.println();
            error_writer.println( "*** NOTE: Compiler errors not available "
                + "because the compiler in your JDK does not have the required "
                + "method. Try upgrading to JDK 1.4.2 / 1.5 ***" );
        }

	    String[] args = null;
	    args = new String[]
	    {
		"-classpath", class_path,
		"-sourcepath", directory.getAbsolutePath(),
		"-d", directory.getAbsolutePath(),
        "-source", "1.4",
		java_file.getAbsolutePath()
	    };
	    
        // NOTE: whilst the below will *run* on J2SDK 1.4.0, it will not 
        // *build*! For that, reflection gymnastics is required.
	    javaCompileStatus = getCanGetCompilerOutput 
            ? Main.compile(args, error_writer) : Main.compile(args);
        if (isDefaultErrorWriter())   
        {
            error_writer.println();
            error_writer.println( "Compilation exit code = " + javaCompileStatus );
        }
	}
	finally
	{
	    if ( writer != null )
	    {
		writer.close();
		writer = null;
	    }
        if ( isDefaultErrorWriter() && error_writer!=null )
            error_writer.close();
	}
    return javaCompileStatus;
    }
    
    /**
     * A constant representing java compile success.
     */
    public static final int JAVA_COMPILE_SUCCESS = 0;
    private int javaCompileStatus = JAVA_COMPILE_SUCCESS;
    
    /**
     * Get the value of the java compile status. This is the value returned by
     * the java compiler (javac) when it last successfully compiled the
     * template. A value equal to {@link #JAVA_COMPILE_SUCCESS} indicates that
     * the template compiled successfully.
     * @return the value of the java compile status.
     * @see #JAVA_COMPILE_SUCCESS
     */
    public int getJavaCompileStatus()
    {
        return javaCompileStatus;
    }
    
    /**
     * Compile this template. This template will only be compiled if it requires
     * compiling (based on the timestamps of the input and output files).
     * <p>
     * There are essentially 3 stages to compiling a template:
     * <ol>
     * <li>parsing the XML and validating it against the DTD.
     * <li>transforming the valid XML into a <code>.java</code> file.
     * <li>compiling the <code>.java</code> file into a <code>.class</code>
     * file.
     * </ol>
     * <p>
     * The process can fail at any stage. For example, stage 2 can generate a
     * <code>.java</code> file that will not compile. This accounts for the
     * numerous exceptions that can get thrown. An entirely successful
     * invocation of this method results in no exceptions being thrown and a
     * return result equal to {@link #JAVA_COMPILE_SUCCESS}.
     * @return the return value of the java compiler.
     * @throws SAXParseException if there is some problem parsing an XML
     *         template.
     * @throws IOException if there is some problem parsing a legacy template.
     * @throws ParserConfigurationException if there is some problem configuring
     *         the XML parser.
     * @throws SAXException if there is some other problem with XML parsing.
     * @throws ServletException if there are compilation problems, but delivered
     *         in a form targeted at objects in the servlet world.
     * @throws Exception if there is some other problem (most likely configuring
     *         the java compiler).
     * @see #JAVA_COMPILE_SUCCESS
     */
    public int compile() throws SAXParseException, IOException,
        ParserConfigurationException, SAXException, ServletException, Exception
    {
        if ( needsCompiling() )
        {
            compiled_xml_source_timestamp = xml_source.lastModified();
            if ( isXml() )
            {
                DocumentBuilder docbuilder = 
                    docbuilderfactory.newDocumentBuilder();
                docbuilder.setEntityResolver( entity_resolver );
                docbuilder.setErrorHandler( errorHandler );
                Document xml_document = docbuilder.parse( xml_source );
                javaCompileStatus = compile( xml_document, class_dir,
                    class_name, class_path );
            }
            else
            {
                LegacyTemplate legacy = new LegacyTemplate();
                javaCompileStatus = legacy.compile( xml_source, class_dir,
                    class_name, class_path );
            }
        }
        
        return javaCompileStatus;
    }
   
    private void compileElement( Element element )
    throws ServletException, IOException
    {
	String tag_name = element.getTagName();
	String class_att;
	
	if ( "template".equals( tag_name ) )
	{
	    String facilityclass = element.getAttribute( "facilityclass" );
	    if ( facilityclass == null || facilityclass.length() == 0 )
            {
                createTemplateVariable( "facility", Facility.class );
		writer.println( "facility=request.getFacility();" );
            }
	    else
	    {
                Class c=null;
                try
                {
                    c = Class.forName( facilityclass );
                }
                catch ( ClassNotFoundException e )
                {
                    throw new ServletException( "Class not found: " + facilityclass );
                }
                createTemplateVariable( "facility", c );
		writer.print( "facility=(" );
		writer.print( facilityclass );
		writer.println( ")request.getFacility();" );
	    }
	    compileChildren( element, false, false );
	}
	else if ( "preparation".equals( tag_name ) )
	{
	    compileChildren( element, false, false );
	    preparation_done = true;
	}
	else if ( "declare".equals( tag_name ) )
	{
	    compileDeclare( element );
	}
	else if ( "building".equals( tag_name) )
	{
	    compileBuilding ( element );
	}
	else if ( "assign".equals( tag_name ) )
	{
	    compileAssign( element );
	}
	else if ( "call".equals( tag_name ) )
	{
	    compileCall( element );
	    writer.println( ";" );
	}
	else if ( "if".equals( tag_name ) )
	{
	    compileIf( element );
	}
	else if ( "htmlfragment".equals( tag_name ) )
	{
	    compileChildren( element, false, true );
	}
	else if ( "textarea".equals( tag_name ) )
	{
	    compileTextarea( element );
	}
	else if ( "filter".equals( tag_name ) )
	{
	    compileFilter( element );
	}
  // Localisation compilation
  else if ( "localise".equals( tag_name ) )
  {
      compileLocaliserCall( element );
  }
  else if ( "input".equals( tag_name ) )
  {
      compileInputCall( element );
  }
  else if ( "a".equals( tag_name ) )
  {
      compileLink( element );
  }
	else
	{
	    if ( "html".equals( tag_name ) )
	    {
		preparation_done = true;
                writer.println( "           response.setContentType(\"text/html; charset=utf-8\");" );
		writer.println( "           writer = response.getWriter();" );
                // declare as loose HTML4 - this is an attempt to get I.E. 6 in standards mode
                writer.println( "           writer.println( \"<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD HTML 4.0 Transitional//EN\\\" " +
                                "\\\"http://www.w3.org/TR/html4/loose.dtd\\\">\" );" );
	    }
	    if ( "body".equals( tag_name ) )
	    {
		class_att = element.getAttribute( "class" );
		// make compile time record of class
		html_body_class = class_att;
		// make run time record of class
		if ( class_att!=null && class_att.length() > 0 )
		{
		    writer.print( "           response.setHtmlBodyClass( \"" );
		    writer.print( class_att );
		    writer.println( "\" );" );
		}
	    }
	    
	    compileChildren( element, true, true );
	}
    }
    
    
    /**
     * Compile a textarea tag. This needs special handling as normally
     * the textarea tag is collapsed by the XML parser from two tags
     * to one and HTML needs two.
     * @param element The textarea element from the XML parsing.
     * @throws ServletException 
     * @throws IOException 
     */
    private void compileTextarea(Element element) throws ServletException, IOException
    {
        writer.print("writer.print(\"<textarea ");
        writeAttributes(element);
        writer.print(">\");");
        compileChildren(element, false, true);
        writer.print("writer.print(\"</textarea>\");");
        
    }

    /**
     * Support for the legacy templates building tag.
     * @param element The node element.
     */
    private void compileBuilding(Element element)
    {
        NamedNodeMap attributes = element.getAttributes();
        writer.write("insertInteractive( request, writer, arrayToMap(new String[][]{");
        for (int attributeNo = 0; attributeNo < attributes.getLength(); attributeNo++)
        {
            if (attributeNo > 0)
            {
                writer.write(",");
            }
            Node attribute = attributes.item(attributeNo);
            writer.write( "{ \""+ attribute.getNodeName()+ "\" , \""+ attribute.getNodeValue()+ "\"}\n");
            
        }
        writer.write("}));\n");
    }

    private void compileChildren( Element element, boolean output_parent, boolean output_children )
    throws ServletException, IOException
    {
	int i, j;
	Node child_node;
	Element child_element;
	CharacterData child_chars;
	boolean start_tag_open=false;
	boolean element_empty=true;
	
	if ( output_parent )
	{
	    start_tag_open = true;
	    writer.print( "           writer.print( \"<" );
	    writer.print( element.getTagName() );
	    
	    writeAttributes(element);
	}
	
	
	NodeList nodelist = element.getChildNodes();
	for ( i=0; i< nodelist.getLength(); i++ )
	{
	    child_node = nodelist.item( i );
	    if ( child_node.getNodeType() == Node.ELEMENT_NODE )
	    {
		if ( start_tag_open )
		{
		    writer.println( ">\" );" );
		    start_tag_open = false;
		}
		element_empty = false;
		
		child_element = (Element)child_node;
		compileElement( child_element );
	    }
	    if ( output_children && preparation_done &&
	    (child_node.getNodeType() == Node.CDATA_SECTION_NODE ||
	    child_node.getNodeType() == Node.TEXT_NODE )	    )
	    {
		if ( start_tag_open )
		{
		    writer.println( ">\" );" );
		    start_tag_open = false;
		}
		element_empty = false;
		
		child_chars = (CharacterData)child_node;
		int l = child_chars.getLength();
		String chunk;
		if ( l > 0 )
		    for ( j=0; j<l; j+=100 )
		    {
			chunk = child_chars.substringData( j, 100 );
			writer.print( "         writer.print( " );
			compileStringLiteral( chunk );
			writer.println( " );" );
		    }
	    }
	}
	
	if ( output_parent )
	{
	    if ( element_empty )
		writer.println( " />\" );" );
	    else
	    {
		writer.println( "           writer.print( \"</" + element.getTagName() + ">\" );" );
	    }
	}
    }
    
    
    
    /**
     * Writes through all the attributes of this element.
     * @param element
     */
    private void writeAttributes(Element element)
    {
        int i;
        Node child_node;
        Attr child_attribute;
        NamedNodeMap nodemap = element.getAttributes();
	    for ( i=0; i< nodemap.getLength(); i++ )
	    {
		child_node = nodemap.item( i );
		if ( child_node.getNodeType() == Node.ATTRIBUTE_NODE )
		{
		    child_attribute = (Attr) child_node;
		    writer.print( " " );
		    writer.print( child_attribute.getName() );
		    writer.print( "=\\\"" );
		    writer.print( child_attribute.getValue() );
		    writer.print( "\\\"" );
		}
	    }
    }
    
  /**
     * Compiles the <code>&lt;localise /&gt;</code> elements.
     * <p>
     * There are two modes of localisation:
     * <ul>
     * <li>Dynamic, where the code to instantiate a {@link Localiser} object
     * and call it's methods is written to the generated class file.
     * <li>Static, where the text obtained from a local call to
     * {@link Localiser} is hard coded in the generated class file.
     * </ul>
     * @param e the <code>&lt;localise /&gt;</code> element to compile.
     * @see Localiser
     */
    private void compileLocaliserCall(Element e) {
      writer.println("writer.print(res.getString(\"" + e.getAttribute("id") + "\"));" );
    }

  private void compileInputCall(Element e) {
    NamedNodeMap attrs = e.getAttributes();
    Node attr = null;
    String[] buffer = null;

    writer.print("writer.print(\"<" + e.getNodeName() + " ");

    for (int count=0; count < attrs.getLength(); count++) {
      attr = attrs.item(count);
      if (attr.getNodeValue().startsWith("localise.")) {
        buffer = attr.getNodeValue().split("localise.");
        writer.print(attr.getNodeName() + "=\\\"\" + res.getString(\"" + buffer[1] + "\") + \"\\\"");
      }
      else {
        writer.print(attr.getNodeName() + "=\\\"" + attr.getNodeValue() + "\\\"");
      }

      writer.print(" ");
    }

    writer.println("/>\");");
  }

    private void compileLink(Element e)  throws ServletException, IOException {

    NamedNodeMap attrs = e.getAttributes();
    Node attr = null;
    String[] buffer = null;

    writer.print("writer.print(\"<" + e.getNodeName() + " ");

    for (int count=0; count < attrs.getLength(); count++) {
      attr = attrs.item(count);
      if (attr.getNodeValue().startsWith("localise.")) {
        buffer = attr.getNodeValue().split("localise.");
        writer.print(attr.getNodeName() + "=\\\"\" + res.getString(\"" + buffer[1] + "\") + \"\\\"");
      }
      else {
        writer.print(attr.getNodeName() + "=\\\"" + attr.getNodeValue() + "\\\"");
      }

      writer.print(" ");
    }
    writer.print(">\");");
    compileChildren(e, false, true);
    writer.print("writer.print(\"</" + e.getNodeName() + ">\");");
    }

    private void compileDeclare( Element element )
    throws ServletException, IOException
    {
	String type = element.getAttribute( "type" );
	String name = element.getAttribute( "name" );
	
	if ( "String".equals( type ) )
	{
	    type = "java.lang.String";
	}
	
	Class c = getTemplateVariableClass( name );
	if ( c!=null )
	    throw new ServletException( "Can't declare same variable twice in one template." );
	
	try
	{
	    if ( "boolean".equals( type ) )
	    {
		createTemplateVariable( name, Boolean.TYPE );
		return;
	    }
	    
	    if ( "java.lang.String".equals( type ) )
	    {
		createTemplateVariable( name, String.class );
		return;
	    }
	}
	catch ( Exception e )
	{
	    throw new ServletException( "Template bytecode compilation error: invalid declaration." );
	}
	
	throw new ServletException( "Declared variables in templates must be boolean or String." );
    }
    
    
    private CharacterData getCharacterData( Element parent )
    throws ServletException, IOException
    {
	Node child_node;
	NodeList nodelist = parent.getChildNodes();
	for ( int i=0; i< nodelist.getLength(); i++ )
	{
	    child_node = nodelist.item( i );
	    if ( child_node instanceof CharacterData )
	    {
		return (CharacterData)child_node;
	    }
      else {
        // String literals in a <call> may be replaced by <localise> nodes. So we'll need to interpolate.
        if (child_node.getNodeName().equalsIgnoreCase("localise")) {
          if (child_node.hasAttributes()) {
            // Get the "id" attribute from the <localise> node
            NamedNodeMap attrs = child_node.getAttributes();
            Node idAttr = attrs.getNamedItem("id");
            // It's not needed now, so remove it from the XML template...
            parent.removeChild(child_node);
            // ...and replace it with the localised Text node
            parent.appendChild(parent.getOwnerDocument().createTextNode(res.getString(idAttr.getNodeValue())));
            // Have to now return the new Text node as CharacterData
            return (CharacterData)parent.getFirstChild();
           }
         }
      }
	}
	return null;
    }
    
    
    private Element[] getNamedElements( Element parent, String[] tag_names )
    throws ServletException, IOException
    {
	int i, j, pass, count;
	Node child_node;
	Element child_element;
	NodeList nodelist = parent.getChildNodes();
	Element[] expressions=null;
	
	for ( pass=0; pass<2; pass++ )
	{
	    count=0;
	    for ( i=0; i< nodelist.getLength(); i++ )
	    {
		child_node = nodelist.item( i );
		if ( child_node.getNodeType() == Node.ELEMENT_NODE )
		{
		    child_element = (Element)child_node;
		    for ( j=0; j < tag_names.length; j++ )
		    {
			if ( tag_names[j].equals( child_element.getTagName() ) )
			{
			    if ( pass==0 )
				count++;
			    else
				expressions[count++] = child_element;
			    break;
			}
		    }
		    
		}
	    }
	    if ( expressions == null )
		expressions = new Element[count];
	}
	
	return expressions;
    }
    
    private Class compileExpression( Element element )
    throws ServletException, IOException
    {
	if ( "literal".equals( element.getTagName() ) )
	    return compileLiteral( element );
	else if ( "variable".equals( element.getTagName() ) )
	    return compileVariable( element );
	else if ( "assign".equals( element.getTagName() ) )
	    return compileAssign( element );
	else if ( "call".equals( element.getTagName() ) )
	    return compileCall( element );
	else if ( "class".equals( element.getTagName() ) )
	    return compileClass (element );
	
	throw new ServletException( "Template bytecode compilation error: unknown expression." );
    }
    
    private void compileStringLiteral( String literal )
    throws IOException
    {
	int l, i, k;
	String hex;
	
	l = literal.length();
	writer.print( "\"" );
	for ( k=0; k<l; k++ )
	{
	    if ( literal.charAt( k ) == '\n' )
	    {
		writer.print( "\\n" );
	    }
	    else if ( literal.charAt( k ) == '\r' )
	    {
		writer.print( "\\r" );
	    }
	    else if ( literal.charAt( k ) == '\\' )
	    {
		writer.print( "\\\\" );
	    }
	    else if ( literal.charAt( k ) == '"' )
	    {
		writer.print( "\\\"" );
	    }
	    else if ( literal.charAt( k ) < ' ' || literal.charAt( k ) > '~' )
	    {
		hex = Integer.toHexString( (int)literal.charAt( k ) );
		writer.print( "\\u" );
		for ( i=hex.length(); i<4; i++ )
		    writer.print( "0" );
		writer.print( hex );
	    }
	    else
	    {
		writer.print( literal.charAt( k ) );
	    }
	}
	writer.print( "\"" );
    }
    
    private Class compileClass( Element element)
    throws ServletException
    {
    	String name = element.getAttribute("name");
    	try
	{
    		writer.print(name);
    		return Class.forName(name);
	}
    	catch (ClassNotFoundException cnfe)
	{
    	    throw new ServletException( "Could not find class: "+ name );
    	}
    }
    
    private Class compileLiteral( Element element )
    throws ServletException, IOException
    {
	String type = element.getAttribute( "type" );
	String strcdata=null;
	CharacterData cdata = getCharacterData( element );
	if ( cdata != null )
	    strcdata = cdata.getData();
	else
	    strcdata = "";
	
	if ( "String".equals( type ) )
	    type = "java.lang.String";
	
	if ( "boolean".equals( type ) )
	{
	    if ( strcdata!=null &&
	    strcdata.length()==4 &&
	    strcdata.toLowerCase().equals( "true" ) )
		writer.print( "true" );
	    else
		writer.print( "false" );
	    return Boolean.TYPE;
	}
	else if ( "null".equals(type))
	{
		writer.print("null");
		return null;
	}
	else if ( "java.lang.String".equals( type ) )
	{
	    compileStringLiteral( strcdata );
	    return java.lang.String.class;
	}
	else
	{
	    throw new ServletException( "Literals can only be of type null, boolean or String." );
	}
    }
    
    private Class compileVariable( Element element )
    throws ServletException, IOException
    {
	try
	{
	    String name = element.getAttribute( "name" );
	    Class vclass;
	    synchronized ( template_variable_class_table )
	    {
		vclass = getTemplateVariableClass( name );
		if ( vclass == null )
		    throw new ServletException( "Unknown variable." );
		writer.print( " " );
		writer.print( name );
		writer.print( " " );
	    }
	    return vclass;
	}
	catch ( Exception e )
	{
	    //request.facility.logException( writer, "XmlTemplateSession", "compileVariable",
	    //"Technical error trying to compile template.",
	    //e );
	    throw new ServletException( "Template bytecode compilation error: invalid variable." );
	}
    }
    
    private Class compileAssign( Element element )
    throws ServletException, IOException
    {
	String name = element.getAttribute( "name" );
	
	Class left_class = getTemplateVariableClass( name );
	if ( left_class==null )
	    throw new ServletException( "Can't make assignment to undeclared variable." );
	
	Element[] expressions = getNamedElements( element, expression_tag_names );
	if ( expressions.length != 1 )
	    throw new ServletException( "Expected exactly 1 expression in assignment." );
	
	writer.print( "         " );
	writer.print( name );
	writer.print( " = " );
	Class right_class = compileExpression( expressions[0] );
	writer.println( ";" );
	
	if ( !left_class.isAssignableFrom( right_class ) )
	    throw new ServletException( "Type mismatch in assignment." );
	
	return left_class;
    }
    
    
    private Class compileCall( Element element )
    throws ServletException, IOException
    {
	try
	{
	    Element[] parts = getNamedElements(  element, call_tag_names  );
	    if ( parts == null || parts.length != 2 )
		throw new ServletException( "Call element in template must have target and parameters." );
	    
	    if ( !"target".equals( parts[0].getTagName() ) ||
	    !"parameters".equals( parts[1].getTagName() ) )
		throw new ServletException( "Call element in template must have target and parameters." );
	    
	    Element[] target_expression = getNamedElements(  parts[0], expression_tag_names  );
	    if ( target_expression == null || target_expression.length != 1 )
		throw new ServletException( "Expected single expression in target element." );
	    
	    Class targetclass = compileExpression( target_expression[0] );
	    if (targetclass == null) {
		    throw new ServletException( "Target of a call can't be null.");
	    }
	    writer.print( "." );
	    String method_name = parts[0].getAttribute( "method" );
	    writer.print( method_name );
	    
	    Element[] expressions = getNamedElements(  parts[1], expression_tag_names  );
	    
	    Class[] param_classes = new Class[expressions.length];
	    
	    writer.print( "(" );
	    for ( int i=0; i<expressions.length; i++ )
	    {
		if ( i>0 )
		    writer.print( "," );
		writer.print( " " );
		param_classes[i] = compileExpression( expressions[i] );
	    }
	    writer.print( " )" );
	    
	    
	    // Method method = fclass.getMethod( method_name, param_classes );
	    // can;t just call getMethod because that requires exact match on
	    // the parameters but the parameters here might be subclasses or
	    // implementations of the declared parameters of the target method
	    
	    // only list the public methods
	    Method target_method=null;
	    Method[] methods = targetclass.getMethods();
	    Class[] target_param_classes;
	    int m, p, n;
	    n=0;
	    int[] scores = new int[methods.length];
	    int best=-1;
	    Class superclass;
	    for ( m=0; m<methods.length; m++ )
	    {
		scores[m]=-1;
		if ( !method_name.equals( methods[m].getName() ) )
		    continue;  // not the right method name - keep looking
		target_param_classes = methods[m].getParameterTypes();
		if ( target_param_classes.length != param_classes.length )
		    continue; // not the right no. of params - keep looking
		
		for ( p=0; p<param_classes.length; p++ )
		{
		    if (  param_classes[p] != null && !target_param_classes[p].isAssignableFrom( param_classes[p] ) )
			break;
		}
		if ( p<param_classes.length )
		    continue;  // not the right types of params - keep looking
		
		// this method matches name and parameters
		if ( best==-1 )
		    best = m;
		scores[m]=0;
		for ( p=0; p<param_classes.length; p++ )
		{
		    for ( superclass = param_classes[p]; superclass != null ; superclass = superclass.getSuperclass() )
		    {
			if ( target_param_classes[p].equals( superclass ) )
			    break;
			scores[m]++;
		    }
		    if ( scores[m] < scores[best] )
			best = m;
		}
		n++;
	    }
	    
		
	    if ( best == -1 )
		throw new ServletException( "Unknown method called in template: "+ method_name + "()" );
	    
	    target_method = methods[best];
	    
	    return target_method.getReturnType();
	}
	catch ( RuntimeException runex )
	{
	    throw new ServletException( "Technical error trying to insert iteractive item: "+ runex );
	}
    }
    
    private void compileIf( Element element )
    throws ServletException, IOException
    {
	int i;
	
	
	Node child_node;
	Element child_element;
	NodeList nodelist = element.getChildNodes();
	for ( i=0; i< nodelist.getLength(); i++ )
	{
	    child_node = nodelist.item( i );
	    if ( child_node.getNodeType() == Node.ELEMENT_NODE )
	    {
		child_element = (Element)child_node;
		if ( "test".equals( child_element.getTagName() ) )
		{
		    Element[] expressions = getNamedElements(  child_element, expression_tag_names  );
		    if ( expressions==null || expressions.length != 1 )
			throw new ServletException( "Expected exactly 1 expression in test clause of if statement." );
		    
		    writer.print( "         if ( " );
		    Class exclass = compileExpression( expressions[0] );
		    if ( !exclass.equals( Boolean.TYPE ) )
			throw new ServletException( "Expected boolean expression in test clause of if statement." );
		    writer.println( " )" );
		}
		else if ( "then".equals( child_element.getTagName() ) )
		{
		    writer.println( "         {" );
		    compileChildren( child_element, false, true );
		    writer.println( "         }" );
		}
		else if ( "else".equals( child_element.getTagName() ) )
		{
		    writer.println( "         else" );
		    writer.println( "         {" );
		    compileChildren( child_element, false, true );
		    writer.println( "         }" );
		}
	    }
	}
	
    }
    
    
    private void compileFilter( Element element )
    throws ServletException, IOException
    {
	Element[] elements = getNamedElements( element, htmlfragment_tag_names );
	if ( elements.length != 1 )
	    throw new ServletException( "Expected one htmlfragment in filter element." );
	compileChildren( elements[0], false, true );
	
    }
    
    
    public void createTemplateVariable( String name, Class class_ref )
    {
	synchronized ( template_variable_class_table )
	{
	    if ( class_ref.equals( Void.TYPE ) )
		throw new IllegalArgumentException( "Template variable cannot be of type void." );
	    
	    template_variable_class_table.put( name, class_ref );
	    writer.print( "         " );
	    String cname = class_ref.toString();
	    if ( cname.indexOf( ' ' ) >=0 )
		cname = cname.substring( cname.indexOf( ' ' )+1 );
	    writer.print( cname );
	    writer.print( " " );
	    writer.print( name );
	    writer.println( ";" );
	}
    }
    
    public Class getTemplateVariableClass( String name )
    {
	synchronized ( template_variable_class_table )
	{
	    return (Class)template_variable_class_table.get( name );
	}
    }
    
    
    private XmlTemplateClassLoader getSuitableClassLoader()
    {
	synchronized ( class_loaders )
	{
	    String dirname = class_dir.getAbsolutePath();
	    XmlTemplateClassLoader class_loader =
	    (XmlTemplateClassLoader)class_loaders.get( dirname );
	    if ( class_loader != null )
		return class_loader;
	    
	    // a special class loader is used to load compiled templates because they
	    // a saved to a specific directory but it needs access to the super 
	    // classes of the templates which ought to be loadable by the same class
	    // loader that loaded this class
	    class_loader = new XmlTemplateClassLoader( class_dir, this.getClass().getClassLoader() );
	    class_loaders.put( dirname, class_loader );
	    return class_loader;
	}
    }
    
    
    private static final byte[][] xml_heads =
    {
	{ '<', '?', 'x', 'm', 'l' },
	{ -2, -1, 0, '<', 0, '?', 0, 'x', 0, 'm', 0, 'l' },
	{ -1, -2, '<', 0, '?', 0, 'x', 0, 'm', 0, 'l', 0 }
    };
    
    private boolean isXml()
    throws IOException
    {
	InputStream input=null;
	try
	{
	    input = new FileInputStream( xml_source );
	    int i, j;
	    byte[] head = new byte[12];
	    input.read( head );
	    for ( j=0; j<xml_heads.length; j++ )
	    {
		for ( i=0; i<xml_heads[j].length; i++ )
		    if ( head[i] != xml_heads[j][i] )
			break;
		if ( i == xml_heads[j].length )
		    return true;
	    }
	    return false;
	}
	finally
	{
	    try
	    {
		if ( input!=null )
		    input.close();
	    }
	    catch ( Exception e )
	    {
	    }
	}
    }

    /**
     * Set the required language. This should correspond to an <a
     * href="http://www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html">ISO
     * Country Code</a>. This corresponds to the language that the templates
     * will be compiled in. If this method is called on a legacy template, it is
     * silently ignored, as the <code>language</code> property is only valid
     * for XML templates.
     * @param language the code of the required language.
     * @see #isXml()
     */
    public void setLanguage( String language )
    {
        if (this.language == language)
            return;
        try
        {
            if (!isXml())
                return;
        } catch (IOException e)
        {
            return;
        }
        this.language = language;
        this.class_name = lang_neutral_class_name + "_" + language;
        languageChanged = true;
    }
    
    
    /**
     * Get the language that this template is compiled in.
     * @return the language that this template is compiled in.
     * @see #setLanguage(String)
     */
    public String getLanguage()
    {
        return language;
    }
    
    
    /**
     * Get the name of the compiled template class name. The class name of an
     * XML template is tied to the language it is compiled in.
     * @return the name of the compiled template class name.
     * @see #isXml()
     * @see #getLanguage()
     */
    public String getTemplateClassName()
    {
        return class_name;
    }
    }
