// copyright => http://www.jxml.com/mdsax/License.txt

package com.jxml.quick;
import com.jxml.quick.recycle.*;
import com.jxml.quick.engine.*;
import java.io.*;
import org.xml.sax.*;
import java.util.*;

/**
 * This class provides static access to a large majority of the
 * Quick API. 
 * <p><a href="../../../../src/com/jxml/quick/Quick.java">source</a>
 * <p>Revised in 1.1
 **/
public class Quick
{
    public static QParserPool parserPool=new QParserPool();
    public static QSoftDocHash schemaHash=new QSoftDocHash();
    
    /**
     * The encoding used by express in generating the encoding line.
     * Initialized to ISO-8859-1 encoding. 
     * When set to null, no encoding line is expressed.
     */
    public static String encoding="ISO-8859-1";
    static
    {
	    try
	    {
		    // Look for a file in the classpath called encoding.txt
		    InputStream 				is=ClassLoader.getSystemResourceAsStream("encoding.txt");
		    if (is!=null)
		    {
		    	InputStreamReader isr=new InputStreamReader(is);
		    	BufferedReader br=new BufferedReader(isr);
		    	encoding=br.readLine().trim();
		    }
	    }
	    catch(Exception e)
	    {
		    System.err.println(e.toString());
		    System.err.println("Unable to access encoding.txt file");
		    encoding="ISO-8859-1";
	    }
	    System.setProperty("file.encoding",encoding);
    }

    /**
     * Set the SAX parser class name.
     * @deprecated
     **/
    public static void setParserClassName(String parserClassName)
    {
	parserPool=new QParserPool(parserClassName);
    }

    /**
     * Return the root object of the object tree held by the QDoc object.
     **/
    public static Object getRoot(QDoc doc)
    {
	return doc.getRoot();
    }

    /**
     * Create a new QDoc object.
     * @param root The root object of the object tree.
     * @param schema The QDoc object which knows how to navigate the tree.
     * @return The new QDoc object.
     **/
    public static QDoc createDoc(Object root, QDoc schema)
    {
	QDoc rv=QCMLBindings.createDoc(root);
	rv.setSchema(schema);
	return rv;
    }

    /**
     * Create a new unique QDoc object.
     * @param url The String containing the URL or filename which names the document.
     * @param root The root object of the object tree.
     * @param schema The QDoc object which knows how to navigate the tree.
     * @return The new QDoc object.
     **/
    public static QDoc createDoc(String url, Object root, QDoc schema)
	throws SAXException
    {
	QDoc rv=QCMLBindings.createDoc(root);
	rv.setSchema(schema);
	rv.setKey(QCMLBindings.forceUrl(url));
	QSoftDocHash sdh=schema.getDocHash();
	synchronized (sdh)
	{
		QDoc rv1=sdh.add(rv);
		if (rv==rv1)
		    QRefHash.add(rv);
		return rv1;
	}
    }

    /**
     * Returns the ID of an object. If no ID exists, one will
     * be automatically generated. Note that the object tree held by the QDoc object
     * does not contain the ID. This information is held by the QDoc object in a hash table.
     * @param doc The QDoc object whose object tree includes the object.
     * @param value The object for which the ID is to be obtained.
     **/
    public static String getId(QDoc doc, Object value)
	throws SAXException
    {
	return doc.getId(value);
    }

    /**
     * Assign an ID to an object held by a QDoc object.
     * @param doc The QDoc object which contains the object whose ID
     * is to be set.
     * @param id The value of the ID to be assigned to the object.
     * @param value The object to which an ID is to be assigned.
     **/
    public static void setId(QDoc doc, String id, Object value)
	throws SAXException
    {
	doc.setId(id,value);
    }

    /**
     * Get the HRef of an object being referenced by an object tree.
     * @param doc The QDoc object which contains the object tree that references the object.
     * @param key The object being referenced.
     * @return The HRef object or null.
     **/
    public static HRef getHRef(QDoc doc, Object key)
    {
	return doc.getHRef(key);
    }
    
	/**
	 * Format the character encoding.
	 * When the encoding is null, the empty string is returned.
	 * @param encoding Example: ISO-8859
	 * @return The XML encoding. 
	 */
	public static String xmlEncoding(String encoding)
	{
		if (encoding==null)
			return "";
		return "<?xml version=\"1.0\" encoding=\""+encoding+"\"?>";
	}

	public static String xmlEncodingNo(String encoding)
	{
		if (encoding==null)
			return "";
		return "<?xml version=\"1.0\" encoding=\""+encoding+"\" standalone=\"no\"?>";
	}

	/**
	 * generate the DOCTYPE for doc.
	 */
	public static String genDocType(String encoding, QDoc doc)
	{
		QDoc schema=((QDocImpl)doc).getSchema();
		if (schema==null)
		{
			return xmlEncoding(encoding);
		}
		QIML schemaRoot=(QIML)schema.getRoot();
		if (schemaRoot==null)
		{
			return xmlEncoding(encoding);
		}
		List factories=schemaRoot.factories;
		if (factories.size()!=1)
			return xmlEncoding(encoding);
		Object efo=factories.get(0);
		if (!(efo instanceof QElementFactory))
			return xmlEncoding(encoding);
		QElementFactory ef=(QElementFactory)efo;
		String rootTagName=ef.tagName;
		return genDocType(encoding,rootTagName,schemaRoot.PUBLIC,schemaRoot.SYSTEM);
	}

	/**
	 * generate the DOCTYPE for doc.
	 */
	public static String genDocType(String encoding, String rootTag, String PUBLIC, String SYSTEM)
	{
		if (rootTag==null || "".equals(rootTag) || rootTag.indexOf(" ")>-1)
			return xmlEncoding(encoding);
		if (SYSTEM==null || "".equals(SYSTEM))
		{
			return xmlEncoding(encoding);
		}
		StringBuffer sb=new StringBuffer();
		sb.append(xmlEncodingNo(encoding));
		sb.append(NL.LS);
		sb.append("<!DOCTYPE ");
		sb.append(rootTag);
		if (PUBLIC!=null && !"".equals(PUBLIC))
		{
			sb.append(" PUBLIC \"");
			sb.append(PUBLIC);
			sb.append("\" ");
		}
		else
			sb.append(" SYSTEM ");
		sb.append("\"");
		sb.append(SYSTEM);
		sb.append("\"");
		sb.append(">");
		return sb.toString();
	}

    /**
     * Express an object tree as an XML document using the default encoding.
     * @param doc The QDoc object holding the object tree to be expressed in XML.
     * @param filename File name of the new XML document.
     */
    public static void express(QDoc doc, String filename)
        throws SAXException,IOException
    {
        PrintWriter pw = new PrintWriter(new FileWriter(filename, false), false);
        express(doc,pw);
        pw.flush();
        pw.close();
    }

    /**
     * Express an object tree as an XML document using the default encoding.
     * @param doc The QDoc object holding the object tree to be expressed in XML.
     * @param filename File name of the new XML document.
     * @param expressDefaults Turns on the expression of default values.
     */
    public static void express(QDoc doc, String filename, boolean expressDefaults)
        throws SAXException,IOException
    {
        PrintWriter pw = new PrintWriter(new FileWriter(filename, false), false);
        express(doc,pw,expressDefaults);
        pw.flush();
        pw.close();
    }

    	/**
     	 * Express an object tree as an XML document.
     	 * @param doc The QDoc object holding the object tree to be expressed in XML.
     	 * @param PW PrintWriter where the XML document is to be written.
     	 */
   	public static void express(QDoc doc, PrintWriter pw)
	throws SAXException, IOException
    	{
		express(doc,pw,false);
	}

    	/**
     	 * Express an object tree as an XML document.
     	 * @param doc The QDoc object holding the object tree to be expressed in XML.
     	 * @param PW PrintWriter where the XML document is to be written.
	 * @param expressDefaults Turns on the expression of default values.
     	 */
   	public static void express(QDoc doc, PrintWriter pw, boolean expressDefaults)
	throws SAXException, IOException
    	{
        	QContext expressContext = (new 		QPrintWriterExpressFactory(pw)).createContext();
		expressContext.expressDefaults=expressDefaults;
		Object root=doc.getRoot();
		if (root instanceof QConverter)
		{
			QConverter croot=(QConverter)root;
			pw.print(genDocType(
					encoding,
					croot.getRootTag(),
					croot.getPUBLIC(),
					croot.getSYSTEM()));
			QCMLBindings.convert(doc,expressContext);
		}
		else
		{
			pw.print(genDocType(encoding,doc));
			QDoc schema=doc.getSchema();
			QContext context=schema.allocateContext();
			ClassLoader cl=root.getClass().getClassLoader();
//System.out.println("root="+root);
//System.out.println("e1 cl="+cl);
			context.cl=cl;
			context.walk(doc,expressContext);
			schema.releaseContext(context);
		}
    	}

    	/**
    	 * Express an object tree as an XML document.
    	 * @param doc The QDoc object holding the object tree to be expressed in XML.
    	 * @return The XML representation
    	 **/
    	public static String express(QDoc doc)
	throws SAXException, IOException
    	{
		return express(doc,false);
	}

    	/**
    	 * Express an object tree as an XML document.
    	 * @param doc The QDoc object holding the object tree to be expressed in XML.
	 * @param expressDefaults Turns on the expression of default values.
    	 * @return The XML representation
    	 **/
    	public static String express(QDoc doc, boolean expressDefaults)
	throws SAXException, IOException
    	{
		QContext expressContext=(new QExpressFactory()).createContext();
		expressContext.expressDefaults=expressDefaults;
		QDoc xd=null;
		Object root=doc.getRoot();
		if (root instanceof QConverter)
		{
			QConverter croot=(QConverter)root;
			QCMLBindings.convert(doc,expressContext);
			xd=expressContext.doc;
			return genDocType(
				encoding,
				croot.getRootTag(),
				croot.getPUBLIC(),
				croot.getSYSTEM())+(String)getRoot(xd);
		}
		else
		{
			QDoc schema=doc.getSchema();
			QContext context=schema.allocateContext();
			ClassLoader cl=root.getClass().getClassLoader();
//System.out.println("root="+root);
//System.out.println("e2 cl="+cl);
			context.cl=cl;
			context.walk(doc,expressContext);
			schema.releaseContext(context);
			xd=expressContext.doc;
//System.out.println((String)getRoot(xd));
			return genDocType(encoding,doc)+(String)getRoot(xd);
		}
    	}

    /**
     * Use a conversion tree to create a second object tree.
     * Do not use this method when the schema uses links.
     * @param doc The QDoc object holding the conversion tree.
     * The root object of this tree must implement the QConverter interface.
     * @param schema The schema for the object tree to be built.
     * @return A new QDoc object holding the newly constructed object tree.
     **/
    public static QDoc convert(QDoc doc, QDoc schema)
	throws SAXException, IOException
    {
	QContext destination=schema.allocateContext();
	QDoc rv=QCMLBindings.convert(doc,destination);
	schema.releaseContext(destination);
	rv.setSchema(schema);
	return rv;
    }

    /**
     * Use a conversion tree to create a second object tree.
     * This method must be used when the schema uses links.
     * @param doc The QDoc object holding the conversion tree.
     * The root object of this tree must implement the QConverter interface.
     * @param schema The schema for the object tree to be built.
     * @param url The String containing the URL or filename which names the unique document.
     * @return A new QDoc object holding the newly constructed object tree.
     **/
    public static QDoc convert(QDoc doc, QDoc schema, String url)
	throws SAXException, IOException
    {
	QContext destination=schema.allocateContext();
	QDoc rv=QCMLBindings.convert(doc,destination);
	rv.setSchema(schema);
	rv.setKey(QCMLBindings.forceUrl(url));
	QSoftDocHash sdh=schema.getDocHash();
	QDoc rv1=null;
	synchronized (sdh)
	{
		rv1=sdh.add(rv);
		if (rv==rv1)
		{
		    QRefHash.add(rv);
		    rv.postParse();
		    if (schema==QIMLSchema()) // schema?
		        ((QDocImpl)rv).pool=new QContextPool(rv);
		}
	}
	schema.releaseContext(destination);
	return rv1;
    }

    /**
     * Use the structure and content of one object tree to build a second object tree.
     * (Different classes can be used to build the second tree.)
     * Do not use this method when the schema uses links.
     * @param doc The QDoc object containing the data to be extracted.
     * @param schema The schema for the object tree to be built.
     * @return A new QDoc object holding the newly constructed object tree.
     **/
    public static QDoc transcribe(QDoc doc, QDoc schema)
	throws SAXException
    {
	return transcribe(doc,schema,false);
    }

    /**
     * Use the structure and content of one object tree to build a second object tree.
     * (Different classes can be used to build the second tree.)
     * Do not use this method when the schema uses links.
     * @param doc The QDoc object containing the data to be extracted.
     * @param schema The schema for the object tree to be built.
     * @param expressDefaults Turns on the expression of default values.
     * @return A new QDoc object holding the newly constructed object tree.
     **/
    public static QDoc transcribe(QDoc doc, QDoc schema, boolean expressDefaults)
	throws SAXException
    {
	QDoc sourceSchema=doc.getSchema();
	QContext source=sourceSchema.allocateContext();
	QContext destination=schema.allocateContext();
	destination.expressDefaults=expressDefaults;
	source.walk(doc,destination);
	sourceSchema.releaseContext(source);
	QDoc rv=destination.doc;
	rv.setSchema(schema);
	schema.releaseContext(destination);
	return rv;
    }

    /**
     * Use the structure and content of one object tree to build a second, unique object tree.
     * (Different classes can be used to build the second tree.)
     * This method must be used when the schema uses links.
     * @param doc The QDoc object containing the data to be extracted.
     * @param schema The schema for the object tree to be built.
     * @param url The String containing the URL or filename which names the unique document.
     * @return A new QDoc object holding the newly constructed object tree.
     **/
    public static QDoc transcribe(QDoc doc, QDoc schema, String url)
	throws SAXException
    {
	return transcribe(doc,schema,url,false);
    }

    /**
     * Use the structure and content of one object tree to build a second, unique object tree.
     * (Different classes can be used to build the second tree.)
     * This method must be used when the schema uses links.
     * @param doc The QDoc object containing the data to be extracted.
     * @param schema The schema for the object tree to be built.
     * @param url The String containing the URL or filename which names the unique document.
	 * @param expressDefaults Turns on the expression of default values.
     * @return A new QDoc object holding the newly constructed object tree.
     **/
    public static QDoc transcribe(QDoc doc, QDoc schema, String url, boolean expressDefaults)
	throws SAXException
    {
	QDoc sourceSchema=doc.getSchema();
	QContext source=sourceSchema.allocateContext();
	QContext destination=schema.allocateContext();
	destination.expressDefaults=expressDefaults;
	source.walk(doc,destination);
	sourceSchema.releaseContext(source);
	QDoc rv=destination.doc;
	rv.setSchema(schema);
	rv.setKey(QCMLBindings.forceUrl(url));
	QSoftDocHash sdh=schema.getDocHash();
	QDoc rv1=null;
	synchronized (sdh)
	{
		rv1=sdh.add(rv);
		if (rv==rv1)
		{
		    QRefHash.add(rv);
		    rv.postParse();
		    if (schema==QIMLSchema()) // schema?
		        ((QDocImpl)rv).pool=new QContextPool(rv);
		}
	}
	schema.releaseContext(destination);
	return rv1;
    }
    
    	/**
    	 * Returns the binding schema for QIML
    	 **/
    	public static QDoc QIMLSchema()
    		throws SAXException
 	{
		try
		{
			return Create.createSchema();
		}
		catch (ClassNotFoundException cnfe)
		{
			throw new SAXException(cnfe.toString());
		}
		catch (InstantiationException ie)
		{
			throw new SAXException(ie.toString());
		}
		catch (IllegalAccessException iae)
		{
			throw new SAXException(iae.toString());
		}
    	}

    /**
     * Create a (unique) QDoc object which holds the schema referenced by a URL.
     * If url is http://www.jxml.com/qiml.qiml, then the internal schema is returned.
     * @param is An InputSource object specifying the schema to be parsed.
     * @param url The String containing the unique name of the schema to be parsed.
     **/
    public static QDoc parseQIML(InputSource is, String url)
	throws IOException, SAXException
    {
	return uniqueParse(QIMLSchema(),is,url);
    }

    /**
     * Create a (unique) QDoc object which holds the schema referenced by a URL.
     * If url is http://www.jxml.com/qiml.qiml, then the internal schema is returned.
     * @param url The String containing the URL or filename of the QIML schema to
     * be parsed.
     **/
    public static QDoc parseQIML(String url)
	throws IOException, SAXException
    {
	return uniqueParse(QIMLSchema(),url);
    }

    /**
     * Create a (unique) QDoc object which holds the schema referenced by a URL.
     * The URL must end in .qiml or .qjml, or an exception will be raised.
     * @param is An InputSource object specifying the schema to be parsed.
     * @param url The String containing the unique name of the schema to be parsed.
     **/
    public static QDoc parseSchema(InputSource is, String url)
	throws IOException, SAXException
    {
	if (url.toLowerCase().endsWith(".qiml"))
		return parseQIML(is,url);
	else throw new SAXException("Unknown Schema type: "+url);
    }

public static String xxx="";
    /**
     * Create a (unique) QDoc object which holds the schema referenced by a URL.
     * The URL must end in .qiml or .qjml, or an exception will be raised.
     * @param url The String containing the URL or filename of the QJML or QIML schema to
     * be parsed.
     **/
    public static QDoc parseSchema(String url)
	throws IOException, SAXException
    {
	if (url.toLowerCase().endsWith(".qiml"))
		return parseQIML(url);
	else throw new SAXException("Unknown Schema type: "+url);
    }

    /**
     * Create a unique QDoc object which holds the object tree build from the XML document referenced by a URL.
     * @param schema The schema for the object tree to be built.
     * @param url The String containing the URL or filename of the document to be parsed.
     **/
    public static QDoc uniqueParse(QDoc schema, String url)
	throws IOException, SAXException
    {
	return uniqueParse(schema,null,url);
    }

    /**
     * Create a unique QDoc object which holds the object tree build from the XML document referenced by a URL.
     * @param schema The schema for the object tree to be built.
     * @param is An InputSource object specifying the document to be parsed.
     * @param url The String containing the unique name of the document to be parsed.
     **/
    public static QDoc uniqueParse(QDoc schema, InputSource is, String url)
	throws IOException, SAXException
    {
	url=QCMLBindings.forceUrl(url);
	if ("http://www.jxml.com/qiml.qiml".equals(url))
		return QIMLSchema();
	QSoftDocHash sdh=schemaHash;
	if (!("http://www.jxml.com/qiml.qiml".equals(schema.getKey())))
		sdh=schema.getDocHash();
	QDoc rv=sdh.get(url);
	if (rv!=null)
	{
	    return rv;
	}
	QContext context=QCMLBindings.parse(schema,is,url);
	rv=context.doc;
	QDoc rv1=null;
	synchronized (sdh)
	{
		rv1=sdh.add(rv);
		if (rv==rv1)
		{
		    QRefHash.add(rv);
		    rv.postParse();
		    if (schema==QIMLSchema()) // schema?
		        ((QDocImpl)rv).pool=new QContextPool(rv);
		}
	}
	schema.releaseContext(context);
	return rv1;
    }

    /**
     * Create a QDoc object which holds the object tree build from the XML document referenced by a URL.
     * @param schema The schema for the object tree to be built.
     * @param url The String containing the URL or filename of the document to be parsed.
     **/
    public static QDoc parse(QDoc schema, String url)
	throws IOException, SAXException
    {
	url=QCMLBindings.forceUrl(url);
	QContext context=QCMLBindings.parse(schema,null,url);
	QDoc rv=context.doc;
	rv.postParse();
	schema.releaseContext(context);
	return rv;
    }

    /**
     * Create a QDoc object which holds the object tree build from the XML document 
     * specified by a SAX InputSource object.
     * @param schema The schema for the object tree to be built.
     * @param is An InputSource object specifying the document to be parsed.
     **/
    public static QDoc parse(QDoc schema, InputSource is)
	throws IOException, SAXException
    {
        QContext context;
	context=QCMLBindings.parse(schema,is,null);
	QDoc rv=context.doc;
	rv.postParse();
	schema.releaseContext(context);
	return rv;
    }

    /**
     * Create a QDoc object which holds the object tree build from the XML document 
     * held in a String.
     * @param schema The schema for the object tree to be built.
     * @param document The String containing the XML document to be parsed.
     **/
    public static QDoc parseString(QDoc schema, String document)
	throws IOException, SAXException
    {
	StringReader sr=new StringReader(document);
	InputSource is=new InputSource(sr);
	return parse(schema,is);
    }
}
