//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v1.0.5-20051113-fcs 
// 	See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// 	Any modifications to this file will be lost upon recompilation of the source schema. 
// 	Generated on: 2005.11.16 um 11:03:34 CET 
//

package phex.xml.impl.runtime;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.xml.bind.JAXBException;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.helpers.NotIdentifiableEventImpl;
import javax.xml.bind.helpers.ValidationEventLocatorImpl;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import com.sun.xml.bind.JAXBAssertionError;
import com.sun.xml.bind.JAXBObject;
import com.sun.xml.bind.marshaller.IdentifiableObject;
import com.sun.xml.bind.marshaller.Messages;
import com.sun.xml.bind.marshaller.NamespacePrefixMapper;
import com.sun.xml.bind.serializer.AbortSerializationException;
import com.sun.xml.bind.serializer.Util;

/**
 * XMLSerializer that produces SAX2 events.
 * 
 * To marshal an object, create an instance of SAXMarshaller
 * and call the serializeElements method of the XMLSerializable
 * object that you want to marshal.
 * 
 * @author  Kohsuke Kawaguchi
 */
public class SAXMarshaller implements XMLSerializer
{
    /**
     * "Attributes" object that is passed to the startElement event.
     * One object is reused throughout the marshalling.
     */
    private final AttributesImpl attributes = new AttributesImpl();
 
    /** This object receives SAX2 events generated from the marshaller. */
    private final ContentHandler writer;
    
    /** Marshaller object to which this object belongs. */
    private final MarshallerImpl owner;
    
    /** Objects referenced through IDREF. */
    private final Set idReferencedObjects = new HashSet();
    
    /** Objects with ID. */
    private final Set objectsWithId = new HashSet();
    
    /** Object currently marshalling itself. */
    private JAXBObject currentTarget;
    
    /**
     * Creates a marshalling context by designating the ContentHandler
     * that receives generated SAX2 events.
     */
    public SAXMarshaller( ContentHandler _writer, NamespacePrefixMapper prefixMapper, MarshallerImpl _owner ) {
        this.writer = _writer;
        this.owner = _owner;
        this.nsContext = new NamespaceContextImpl(
            prefixMapper!=null?prefixMapper:defaultNamespacePrefixMapper);
    }
    
    /** namespace context. */
    private final NamespaceContextImpl nsContext;
    
    public NamespaceContext2 getNamespaceContext() { return nsContext; }
    
    //
    //
    // name stack
    //
    //
    
    /** Element name stack implemented as an array of (uri,local) pairs. */
    private String[] elementStack = new String[16];;
    private int elementLen=0;
    
    
    
    private void pushElement( String uri, String local ) {
        if(elementStack.length==elementLen) {
            // reallocate buffer
            String[] buf = new String[elementStack.length*2];
            System.arraycopy( elementStack, 0, buf, 0, elementStack.length );
            elementStack = buf;
        }
        elementStack[elementLen++] = uri;
        elementStack[elementLen++] = local;
    }
    
    private void popElement() { elementLen-=2;  }
    
    private String getCurrentElementUri()   { return elementStack[elementLen-2]; }
    private String getCurrentElementLocal() { return elementStack[elementLen-1]; }
    
    
    
    
    
    /**
     * Starts marshalling of an element.
     * Calling this method will push the internal state into the
     * internal stack.
     */
    public void startElement( String uri, String local ) throws SAXException {
        boolean isRoot = false;
        String suggestion = null;
        if( elementLen==0 ) {
            isRoot = true;
            // this is the root element. suggest this as the default namespace
            suggestion = "";
        }
        
        writePendingText();
        nsContext.startElement();
        pushElement(uri,local); // memorize element name
                
        // declare this uri
        nsContext.declareNamespace(uri,suggestion,false);
        
        // if this is the root element, declare user-specified namespace URIs.
        if( isRoot ) {
            // work defensively. we are calling an user-defined method.
            String[] uris = nsContext.getNamespacePrefixMapper().getPreDeclaredNamespaceUris();
            if( uris!=null ) {
                for( int i=0; i<uris.length; i++ ) {
                    if( uris[i]!=null )
                        nsContext.declareNamespace(uris[i],null,false);
                }
            }
        }
    }
    
    
    private final PrefixCallback startPrefixCallback = new PrefixCallback() {
        public void onPrefixMapping( String prefix, String nsUri ) throws SAXException {
            writer.startPrefixMapping(prefix,nsUri);
        }
    };
    private final PrefixCallback endPrefixCallback = new PrefixCallback() {
        public void onPrefixMapping( String prefix, String nsUri ) throws SAXException {
            writer.endPrefixMapping(prefix);
        }
    };
    
    public void endNamespaceDecls() throws SAXException {
        nsContext.endNamespaceDecls();
    }
    
    /**
     * Switches to the "marshal child texts/elements" mode.
     * This method has to be called after the 1st pass is completed.
     */
    public void endAttributes() throws SAXException {
        // calculate QName of the element
        String uri = getCurrentElementUri();
        String local = getCurrentElementLocal();
        
        String prefix = nsContext.getPrefix(uri);
        _assert(prefix!=null);  // since we've declared it, it should be available
        
        String qname;
        if(prefix.length()!=0 ) 
            qname = prefix+':'+local;
        else
            qname = local;

        // fire startPrefixMapping events
        nsContext.iterateDeclaredPrefixes(startPrefixCallback);
        
        // fire the startElement event
        writer.startElement( uri, local, qname, attributes );
        
        
        // reset attributes
        attributes.clear();
        
        // prepare to collect texts
        textBuf.setLength(0);
    }
    
    /**
     * Ends marshalling of an element.
     * Pops the internal stack.
     */
    public void endElement() throws SAXException {
        writePendingText();
        
        String uri = getCurrentElementUri();
        String local = getCurrentElementLocal();
        
        String prefix = nsContext.getPrefix(uri);
        _assert(prefix!=null);      // we've declared it earlier.
        
        String qname;
        if(prefix.length()!=0)
            qname = prefix+':'+local;
        else
            qname = local;
        
        writer.endElement( uri, local, qname );

        // pop namespace bindings and
        // fire endPrefixMapping events
        nsContext.iterateDeclaredPrefixes(endPrefixCallback);
        
        popElement();
        
        // prepare to collect texts
        textBuf.setLength(0);
        
        nsContext.endElement();
    }
    
    
    /** Buffer for collecting characters. */
    private final StringBuffer textBuf = new StringBuffer();
    
    /**
     * Marshalls text.
     * 
     * <p>
     * This method can be called (i) after the startAttribute method
     * and (ii) before the endAttribute method, to marshal attribute values.
     * If the method is called more than once, those texts are considered
     * as separated by whitespaces. For example,
     * 
     * <pre>
     * c.startAttribute();
     * c.text("abc");
     * c.text("def");
     * c.endAttribute("","foo");
     * </pre>
     * 
     * will generate foo="abc def".
     * 
     * <p>
     * Similarly, this method can be called after the endAttributes
     * method to marshal texts inside elements. The same rule about
     * multiple invokations apply to this case, too. For example,
     * 
     * <pre>
     * c.startElement("","foo");
     * c.endAttributes();
     * c.text("abc");
     * c.text("def");
     *   c.startElement("","bar");
     *   c.endAttributes();
     *   c.endElement();
     * c.text("ghi");
     * c.endElement();
     * </pre>
     * 
     * will generate <code>&lt;foo>abc def&lt;bar/>ghi&lt;/foo></code>.
     */
    public void text( String text, String fieldName ) throws SAXException {
        // If the assertion fails, it must be a bug of xjc.
        // right now, we are not expecting the text method to be called.
        if(text==null) {
            reportError(Util.createMissingObjectError(currentTarget,fieldName));
            return;
        }
    
        if(textBuf.length()!=0)
            textBuf.append(' ');
        textBuf.append(text);
    }
    
    /**
     * Writes pending text (characters inside elements) to the writer.
     * This method is called from startElement and endElement.
     */
    private void writePendingText() throws SAXException {
        // assert(textBuf!=null);
        int len = textBuf.length();
        
        if(len!=0)
            writer.characters( textBuf.toString().toCharArray(), 0, len );
    }
    
    /**
     * Starts marshalling of an attribute.
     * 
     * The marshalling of an attribute will be done by
     * <ol>
     *  <li>call the startAttribute method
     *  <li>call the text method (several times if necessary)
     *  <li>call the endAttribute method
     * </ol>
     * 
     * No two attributes can be marshalled at the same time.
     * Note that the whole attribute marshalling must be happened
     * after the startElement method and before the endAttributes method.
     */
    public void startAttribute( String uri, String local ) {
        // initialize the buffer to collect attribute value
        textBuf.setLength(0);
        
        // remember the attribute name. We'll use this value later.
        this.attNamespaceUri = uri;
        this.attLocalName = local;
    }
    
    // used to keep attribute names until the endAttribute method is called.
    private String attNamespaceUri;
    private String attLocalName;

    public void endAttribute() {
        // use CDATA as the attribute type. This preserves
        // successive processors to collapse whitespaces.
        // (we cannot prevent characters like #xD to be replaced to
        // #x20, though).
        //
        // strictly speaking, attribute value normalization should be
        // provessed by XML parser, so it's unclear whether XML writer
        // uses this type value.
        //
        // in any way, CDATA type is the safest choice here.
        
        String qname;
        if(attNamespaceUri.length()==0) {
            // default namespace. don't need prefix
            qname = attLocalName;
        } else {
            qname = nsContext.declareNamespace(attNamespaceUri,null,true)+':'+attLocalName;
        }

        attributes.addAttribute(attNamespaceUri,attLocalName,qname,"CDATA",textBuf.toString());
    }
    
    public String onID( IdentifiableObject owner, String value ) throws SAXException {
        objectsWithId.add(owner);
        return value;
    }
    public String onIDREF( IdentifiableObject obj ) throws SAXException {
        idReferencedObjects.add(obj);
        String id = obj.____jaxb____getId();
        if(id==null) {
            reportError( new NotIdentifiableEventImpl(
                ValidationEvent.ERROR,
                Messages.format(Messages.ERR_NOT_IDENTIFIABLE),
                new ValidationEventLocatorImpl(obj) ) );
        }
        return id;
    }
    
    void reconcileID() throws AbortSerializationException {
        // find objects that were not a part of the object graph 
        idReferencedObjects.removeAll(objectsWithId);
        
        for( Iterator itr=idReferencedObjects.iterator(); itr.hasNext(); ) {
            IdentifiableObject o = (IdentifiableObject)itr.next();
            reportError( new NotIdentifiableEventImpl(
                ValidationEvent.ERROR,
                Messages.format(Messages.ERR_DANGLING_IDREF,o.____jaxb____getId()),
                new ValidationEventLocatorImpl(o) ) );
        }
        
        // clear the garbage
        idReferencedObjects.clear();
        objectsWithId.clear();
    }



    public void childAsBody( JAXBObject o, String fieldName ) throws SAXException {
        if(o==null) {
            // if null is passed, it usually means that the content tree object
            // doesn't have some of its required property.
            reportMissingObjectError(fieldName);
            // as a marshaller, we should be generous, so we'll continue to marshal
            // this document by skipping this missing object.
            return;
        }
        
        JAXBObject oldTarget = currentTarget;
        currentTarget = o;
        
        owner.context.getGrammarInfo().castToXMLSerializable(o).serializeBody(this);
        
        currentTarget = oldTarget;
    }
    
    public void childAsAttributes( JAXBObject o, String fieldName ) throws SAXException {
        if(o==null) {
            reportMissingObjectError(fieldName);
            return;
        }

        JAXBObject oldTarget = currentTarget;
        currentTarget = o;
        
        owner.context.getGrammarInfo().castToXMLSerializable(o).serializeAttributes(this);
        
        currentTarget = oldTarget;
    }
    
    public void childAsURIs( JAXBObject o, String fieldName ) throws SAXException {
        if(o==null) {
            reportMissingObjectError(fieldName);
            return;
        }
        
        JAXBObject oldTarget = currentTarget;
        currentTarget = o;
        
        owner.context.getGrammarInfo().castToXMLSerializable(o).serializeURIs(this);
        
        currentTarget = oldTarget;
    }
    

    public void reportError( ValidationEvent ve ) throws AbortSerializationException {
        ValidationEventHandler handler;
        
        try {
            handler = owner.getEventHandler();
        } catch( JAXBException e ) {
            throw new AbortSerializationException(e);
        }
        
        if(!handler.handleEvent(ve)) {
            if(ve.getLinkedException() instanceof Exception)
                throw new AbortSerializationException((Exception)ve.getLinkedException());
            else
                throw new AbortSerializationException(ve.getMessage());
        }
    }
    
    
    public void reportMissingObjectError(String fieldName) throws SAXException {
        reportError(Util.createMissingObjectError(currentTarget,fieldName));
    }
    
    
    private static void _assert( boolean b ) {
        if(!b)
            throw new JAXBAssertionError();
    }
    
    /**
     * Default {@link NamespacePrefixMapper} implementation used when
     * it is not specified by the user.
     */
    private static NamespacePrefixMapper defaultNamespacePrefixMapper = new NamespacePrefixMapper() {
        public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
            if( namespaceUri.equals("http://www.w3.org/2001/XMLSchema-instance") )
                return "xsi";
            return suggestion;
        }
    };
}
