package com.sun.msv.writer;

import org.xml.sax.ContentHandler;
import org.xml.sax.DocumentHandler;
import org.xml.sax.AttributeList;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.NamespaceSupport;
import org.xml.sax.helpers.AttributesImpl;
import java.util.Enumeration;

/**
 * Adapt SAX2 ContentHandler as a SAX1 DocumentHandler.
 * 
 * This class wraps a ContentHandler and makes it act as a DocumentHandler.
 * 
 * @author David Megginson, 
 *         <a href="mailto:sax@megginson.com">sax@megginson.com</a>
 */
public class ContentHandlerAdaptor implements DocumentHandler {
	
    private final NamespaceSupport nsSupport = new NamespaceSupport();
	private final ContentHandler contentHandler;
    private final AttributeListAdapter attAdapter = new AttributeListAdapter();
    private final AttributesImpl atts = new AttributesImpl();

    private final boolean namespaces = true;
    private final boolean prefixes = false;

    private final String nameParts[] = new String[3];
	
	public ContentHandlerAdaptor( ContentHandler handler ) {
		this.contentHandler = handler;
	}
	
	////////////////////////////////////////////////////////////////////
	// Implementation of org.xml.sax.DocumentHandler.
	////////////////////////////////////////////////////////////////////


	/**
	 * Adapt a SAX1 document locator event.
	 *
	 * @param locator A document locator.
	 * @see org.xml.sax.ContentHandler#setDocumentLocator
	 */
	public void setDocumentLocator (Locator locator)
	{
		if (contentHandler != null) {
			contentHandler.setDocumentLocator(locator);
		}
	}


	/**
	 * Adapt a SAX1 start document event.
	 *
	 * @exception org.xml.sax.SAXException The client may raise a
	 *			processing exception.
	 * @see org.xml.sax.DocumentHandler#startDocument
	 */
	public void startDocument () throws SAXException
	{
		if (contentHandler != null) {
			contentHandler.startDocument();
		}
	}


	/**
	 * Adapt a SAX1 end document event.
	 *
	 * @exception org.xml.sax.SAXException The client may raise a
	 *			processing exception.
	 * @see org.xml.sax.DocumentHandler#endDocument
	 */
	public void endDocument () throws SAXException
	{
		if (contentHandler != null) {
			contentHandler.endDocument();
		}
	}


	/**
	 * Adapt a SAX1 startElement event.
	 *
	 * <p>If necessary, perform Namespace processing.</p>
	 *
	 * @param qName The qualified (prefixed) name.
	 * @param qAtts The XML 1.0 attribute list (with qnames).
	 */
	public void startElement (String qName, AttributeList qAtts) throws SAXException
	{
		// If we're not doing Namespace
		// processing, dispatch this quickly.
		if (!namespaces) {
			if (contentHandler != null) {
			attAdapter.setAttributeList(qAtts);
			contentHandler.startElement("", "", qName.intern(),
							attAdapter);
			}
			return;
		}

		// OK, we're doing Namespace processing.
		nsSupport.pushContext();
		boolean seenDecl = false;
		atts.clear();
	
		// Take a first pass and copy all
		// attributes into the SAX2 attribute
		// list, noting any Namespace 
		// declarations.
		int length = qAtts.getLength();
		for (int i = 0; i < length; i++) {
			String attQName = qAtts.getName(i);
			String type = qAtts.getType(i);
			String value = qAtts.getValue(i);

					// Found a declaration...
			if (attQName.startsWith("xmlns")) {
				String prefix;
				int n = attQName.indexOf(':');
				if (n == -1) {
					prefix = "";
				} else {
					prefix = attQName.substring(n+1);
				}
				if (!nsSupport.declarePrefix(prefix, value)) {
					reportError("Illegal Namespace prefix: " + prefix);
				}
				if (contentHandler != null) {
					contentHandler.startPrefixMapping(prefix, value);
				}
				// We may still have to add this to
				// the list.
				if (prefixes) {
					atts.addAttribute("", "", attQName.intern(),
							  type, value);
				}
				seenDecl = true;
				
				// This isn't a declaration.
			} else {
				String attName[] = processName(attQName, true);
				atts.addAttribute(attName[0], attName[1], attName[2],
					  type, value);
			}
		}
	
		// If there was a Namespace declaration,
		// we have to make a second pass just
		// to be safe -- this will happen very
		// rarely, possibly only once for each
		// document.
		if (seenDecl) {
			length = atts.getLength();
			for (int i = 0; i < length; i++) {
				String attQName = atts.getQName(i);
				if (!attQName.startsWith("xmlns")) {
					String attName[] = processName(attQName, true);
					atts.setURI(i, attName[0]);
					atts.setLocalName(i, attName[1]);
				}
			}
		}

		// OK, finally report the event.
		if (contentHandler != null) {
			String name[] = processName(qName, false);
			contentHandler.startElement(name[0], name[1], name[2], atts);
		}
	}


	/**
	 * Adapt a SAX1 end element event.
	 *
	 * @param qName The qualified (prefixed) name.
	 * @exception org.xml.sax.SAXException The client may raise a
	 *			processing exception.
	 * @see org.xml.sax.DocumentHandler#endElement
	 */
	public void endElement (String qName) throws SAXException
	{
		// If we're not doing Namespace
		// processing, dispatch this quickly.
		if (!namespaces) {
			if (contentHandler != null) {
			contentHandler.endElement("", "", qName.intern());
			}
			return;
		}

		// Split the name.
		String names[] = processName(qName, false);
		if (contentHandler != null) {
			contentHandler.endElement(names[0], names[1], names[2]);
			Enumeration prefixes = nsSupport.getDeclaredPrefixes();
			while (prefixes.hasMoreElements()) {
				String prefix = (String)prefixes.nextElement();
				contentHandler.endPrefixMapping(prefix);
			}
		}
		nsSupport.popContext();
	}


	/**
	 * Adapt a SAX1 characters event.
	 *
	 * @param ch An array of characters.
	 * @param start The starting position in the array.
	 * @param length The number of characters to use.
	 * @exception org.xml.sax.SAXException The client may raise a
	 *			processing exception.
	 * @see org.xml.sax.DocumentHandler#characters
	 */
	public void characters (char ch[], int start, int length)
		throws SAXException
	{
		if (contentHandler != null) {
			contentHandler.characters(ch, start, length);
		}
	}


	/**
	 * Adapt a SAX1 ignorable whitespace event.
	 *
	 * @param ch An array of characters.
	 * @param start The starting position in the array.
	 * @param length The number of characters to use.
	 * @exception org.xml.sax.SAXException The client may raise a
	 *			processing exception.
	 * @see org.xml.sax.DocumentHandler#ignorableWhitespace
	 */
	public void ignorableWhitespace (char ch[], int start, int length)
		throws SAXException
	{
		if (contentHandler != null) {
			contentHandler.ignorableWhitespace(ch, start, length);
		}
	}


	/**
	 * Adapt a SAX1 processing instruction event.
	 *
	 * @param target The processing instruction target.
	 * @param data The remainder of the processing instruction
	 * @exception org.xml.sax.SAXException The client may raise a
	 *			processing exception.
	 * @see org.xml.sax.DocumentHandler#processingInstruction
	 */
	public void processingInstruction (String target, String data)
		throws SAXException
	{
		if (contentHandler != null) {
			contentHandler.processingInstruction(target, data);
		}
	}


	
	
    private String [] processName (String qName, boolean isAttribute) throws SAXException
    {
		String parts[] = nsSupport.processName(qName, nameParts, isAttribute);
		if (parts == null) {
		    parts = new String[3];
		    parts[2] = qName.intern();
		    reportError("Undeclared prefix: " + qName);
		}
		return parts;
    }

    /**
     * Report a non-fatal error.
     *
     * @param message The error message.
     * @exception org.xml.sax.SAXException The client may throw
     *            an exception.
     */
    void reportError (String message) throws SAXException {
		throw new SAXParseException(message, null, null, -1, -1 );
    }
	
	
	

    ////////////////////////////////////////////////////////////////////
    // Inner class to wrap an AttributeList when not doing NS proc.
    ////////////////////////////////////////////////////////////////////


    /**
     * Adapt a SAX1 AttributeList as a SAX2 Attributes object.
     *
     * <p>This class is in the Public Domain, and comes with NO
     * WARRANTY of any kind.</p>
     *
     * <p>This wrapper class is used only when Namespace support
     * is disabled -- it provides pretty much a direct mapping
     * from SAX1 to SAX2, except that names and types are 
     * interned whenever requested.</p>
     */
    final class AttributeListAdapter implements Attributes
    {

		/**
		 * Construct a new adapter.
		 */
		AttributeListAdapter ()
		{
		}


		/**
		 * Set the embedded AttributeList.
		 *
		 * <p>This method must be invoked before any of the others
		 * can be used.</p>
		 *
		 * @param The SAX1 attribute list (with qnames).
		 */
		void setAttributeList (AttributeList qAtts)
		{
		    this.qAtts = qAtts;
		}


		/**
		 * Return the length of the attribute list.
		 *
		 * @return The number of attributes in the list.
		 * @see org.xml.sax.Attributes#getLength
		 */
		public int getLength ()
		{
		    return qAtts.getLength();
		}


		/**
		 * Return the Namespace URI of the specified attribute.
		 *
		 * @param The attribute's index.
		 * @return Always the empty string.
		 * @see org.xml.sax.Attributes#getURI
		 */
		public String getURI (int i)
		{
		    return "";
		}


		/**
		 * Return the local name of the specified attribute.
		 *
		 * @param The attribute's index.
		 * @return Always the empty string.
		 * @see org.xml.sax.Attributes#getLocalName
		 */
		public String getLocalName (int i)
		{
		    return "";
		}


		/**
		 * Return the qualified (prefixed) name of the specified attribute.
		 *
		 * @param The attribute's index.
		 * @return The attribute's qualified name, internalized.
		 */
		public String getQName (int i)
		{
		    return qAtts.getName(i).intern();
		}


		/**
		 * Return the type of the specified attribute.
		 *
		 * @param The attribute's index.
		 * @return The attribute's type as an internalized string.
		 */
		public String getType (int i)
		{
		    return qAtts.getType(i).intern();
		}


		/**
		 * Return the value of the specified attribute.
		 *
		 * @param The attribute's index.
		 * @return The attribute's value.
		 */
		public String getValue (int i)
		{
		    return qAtts.getValue(i);
		}


		/**
		 * Look up an attribute index by Namespace name.
		 *
		 * @param uri The Namespace URI or the empty string.
		 * @param localName The local name.
		 * @return The attributes index, or -1 if none was found.
		 * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
		 */
		public int getIndex (String uri, String localName)
		{
		    return -1;
		}


		/**
		 * Look up an attribute index by qualified (prefixed) name.
		 *
		 * @param qName The qualified name.
		 * @return The attributes index, or -1 if none was found.
		 * @see org.xml.sax.Attributes#getIndex(java.lang.String)
		 */
		public int getIndex (String qName)
		{
		    int max = atts.getLength();
		    for (int i = 0; i < max; i++) {
			if (qAtts.getName(i).equals(qName)) {
			    return i;
			}
		    }
		    return -1;
		}


		/**
		 * Look up the type of an attribute by Namespace name.
		 *
		 * @param uri The Namespace URI
		 * @param localName The local name.
		 * @return The attribute's type as an internalized string.
		 */
		public String getType (String uri, String localName)
		{
		    return null;
		}


		/**
		 * Look up the type of an attribute by qualified (prefixed) name.
		 *
		 * @param qName The qualified name.
		 * @return The attribute's type as an internalized string.
		 */
		public String getType (String qName)
		{
		    return qAtts.getType(qName).intern();
		}


		/**
		 * Look up the value of an attribute by Namespace name.
		 *
		 * @param uri The Namespace URI
		 * @param localName The local name.
		 * @return The attribute's value.
		 */
		public String getValue (String uri, String localName)
		{
		    return null;
		}


		/**
		 * Look up the value of an attribute by qualified (prefixed) name.
		 *
		 * @param qName The qualified name.
		 * @return The attribute's value.
		 */
		public String getValue (String qName)
		{
		    return qAtts.getValue(qName);
		}

		private AttributeList qAtts;
    }
}
