/*
 * XmlTemplate.java
 *
 * Created on 06 June 2002, 15:56
 */

package org.bodington.servlet.template;

import org.bodington.servlet.*;
import org.bodington.servlet.facilities.*;
import java.util.logging.*;

import java.io.*;
import java.lang.reflect.*;
import java.util.Hashtable;
import javax.servlet.*;
import javax.servlet.http.*;

import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;


import com.sun.tools.javac.v8.Main;


/**
 *
 * @author  bmb6jrm
 * @version
 */
public class XmlTemplate
{
    private static final String[] expression_tag_names=
    {
	"literal",
	"variable",
	"assign",
	"call"
    };
    
    private static final String[] htmlfragment_tag_names=
    {
	"htmlfragment"
    };
    
    private static final String[] call_tag_names=
    {
	"target",
	"parameters"
    };
    
    
    // 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 org.bodington.xml.EntityResolver( "bodington.home", "dtd" );
    }
    
    
    XmlTemplateClassLoader class_loader;
    XmlTemplateProcessor processor;
    
    File xml_source;
    File class_dir;
    String class_name;
    String class_path;
    
    // indicates the timestamp of the version of the xml file that was
    // last compiled
    long compiled_xml_source_timestamp = -1;
    
    boolean compiling;
    
    //Hashtable attribute_tables = new Hashtable();
    PrintWriter writer=null;
    boolean preparation_done;
    String html_body_class=null;
    Hashtable template_variable_class_table;
    
    
    
    /** Creates new XmlTemplate */
    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;
	
	
	class_loader = getSuitableClassLoader();
	File class_file = class_loader.getClassFile( class_name );
	
	// if hte current class file is newer than the current xml
	// file assume that the former is an up to date compilation
	// of the latter.
	if ( class_file !=null && xml_source!=null && xml_source.exists() )
	{
	    long c = class_file.lastModified();
	    long x = xml_source.lastModified();
	    if ( c>x )
		compiled_xml_source_timestamp = xml_source.lastModified();
	}
    }
    
    
    private long last_checked = 0;
    public synchronized boolean needsCompiling()
    {
	// don't bother even checking if it was checked
	// less than a minute ago
	long now = System.currentTimeMillis();
	//if ( (now - last_checked) < (60*1000) )
	//    return false;
	last_checked = now;
	
	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
	long class_timestamp=class_file.lastModified();
	
	return class_timestamp < xml_source_timestamp;
    }
    
    
    public XmlTemplateProcessor getProcessor()
    {
	if ( !xml_source.exists() && !xml_source.isFile() )
	    return null;
	
	
	if ( needsCompiling() )
	{
	    long x = xml_source.lastModified();
	    
	    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
	    {
		if ( isXml() )
		{
		    DocumentBuilder docbuilder = docbuilderfactory.newDocumentBuilder();
		    docbuilder.setEntityResolver( entity_resolver );
		    Document xml_document = docbuilder.parse( xml_source );
		    compile( xml_document, class_dir, class_name, class_path );
		}
		else
		{
		    LegacyTemplate legacy = new LegacyTemplate();
		    legacy.compile( xml_source, class_dir, class_name, class_path );
		}
		
		loadProcessor();
		return processor;
	    }
	    catch ( Throwable th )
	    {
		Logger.getLogger( "org.bodington" ).logp(
		Level.SEVERE,
		"XmlTemplate",
		"getProcessor",
		th.getMessage(),
		th );
		processor = new XmlTemplateErrorProcessor( th.getMessage() );
		return processor;
	    }
	    finally
	    {
		// guaranteed to clear the flag
		compiling = false;
		compiled_xml_source_timestamp = x;
		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 ) )
	    loadProcessor();
	
	return processor;
	
    }
    
    private void loadProcessor()
    {
	try
	{
	    Class cl =  class_loader.loadClass( class_name, true );
	    processor = (XmlTemplateProcessor)cl.newInstance();
	}
	catch ( Throwable th)
	{
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "XmlTemplate",
	    "loadProcessor",
	    th.getMessage(),
	    th );
	    processor = new XmlTemplateErrorProcessor( th.getMessage() );
	}
    }
    
    public void compile( Document xml_document, File directory, String class_name, String class_path )
    throws ServletException, IOException
    {
	if ( !directory.exists() || !directory.isDirectory() )
	    throw new ServletException( "Invalid compilation directory for templates." );
	
	Logger.getLogger("org.bodington").finer( "Compiling in " + directory.getAbsolutePath() );
	Logger.getLogger("org.bodington").finer( "Compiling " + class_name );
	
	String file_name = class_name.replace( '.', File.separatorChar ) + ".java";
	Logger.getLogger("org.bodington").finer( "Creating java source in " + file_name );
	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;
	
	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( "public class " + unqual_class_name );
	writer.println( " extends XmlTemplateProcessor" );
	writer.println( "{" );
	writer.println( "   public " + unqual_class_name + "()" );
	writer.println( "       {" );
	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;" );
	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;
	    
	    Main compiler = new Main( "xhtmltemplate->javabytecode" );
	    String[] args = null;
	    args = new String[]
	    {
		//"-encoding", "UTF-8",
		"-classpath", class_path,
		"-sourcepath", directory.getAbsolutePath(),
		"-d", directory.getAbsolutePath(),
		java_file.getAbsolutePath()
	    };
	    
	    compiler.compile(args);
	}
	finally
	{
	    if ( writer != null )
	    {
		writer.close();
		writer = null;
	    }
	    //java_file.delete();
	}
    }
    
    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.facility;" );
            }
	    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 ( "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 ( "filter".equals( tag_name ) )
	{
	    compileFilter( element );
	}
	else
	{
	    if ( "html".equals( tag_name ) )
	    {
		preparation_done = true;
                writer.println( "           response.setContentType(\"text/html; charset=utf-8\");" );
		writer.println( "           writer = response.getWriter();" );
	    }
	    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 );
	}
    }
    
    
    private void compileChildren( Element element, boolean output_parent, boolean output_children )
    throws ServletException, IOException
    {
	int i, j, k;
	Node child_node;
	Element child_element;
	Attr child_attribute;
	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() );
	    
	    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( "\\\"" );
		}
	    }
	}
	
	
	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;
		String hex;
		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() + ">\" );" );
	    }
	}
    }
    
    
    
    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 )
	{
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "XmlTemplate",
	    "getProcessor",
	    e.getMessage(),
	    e );
	    throw new ServletException( "Technical error trying to compile template." );
	}
	
	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;
	    }
	}
	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 );
	
	throw new ServletException( "Technical error trying to compile template - 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 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 ( "java.lang.String".equals( type ) )
	{
	    compileStringLiteral( strcdata );
	    return java.lang.String.class;
	}
	else
	{
	    throw new ServletException( "Literals can only be of type 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( "Technical error trying to compile template." );
	}
    }
    
    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] );
	    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 ( !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." );
	    
	    target_method = methods[best];
	    
	    return target_method.getReturnType();
	}
	catch ( RuntimeException runex )
	{
	    //request.facility.logException( writer, "BuildingServlet", "insertInteractive",
	    //"Technical error trying to insert iteractive item.",
	    //runex );
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "XmlTemplate",
	    "getProcessor",
	    runex.getMessage(),
	    runex );
	    throw new ServletException( "Technical error trying to insert iteractive item." );
	}
    }
    
    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 )
	    {
	    }
	}
    }
    
    
    
    
}
