//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v1.0.6-01/24/2006 06:08 PM(kohsuke)-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: 2010.11.10 at 07:55:46 PM PST 
//

package org.infoml.jaxb.impl.runtime;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;

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.LocatorImpl;

import com.sun.xml.bind.JAXBAssertionError;
import com.sun.xml.bind.unmarshaller.Messages;
import com.sun.xml.bind.unmarshaller.Tracer;
import com.sun.xml.bind.util.AttributesImpl;

/**
 * Implementation of {@link UnmarshallerHandler}.
 * 
 * This object converts SAX events into unmarshaller events and
 * cooridnates the entire unmarshalling process.
 *
 * @author
 *  <a href="mailto:kohsuke.kawaguchi@sun.com">Kohsuke KAWAGUCHI</a>
 */
public class SAXUnmarshallerHandlerImpl
    implements SAXUnmarshallerHandler, UnmarshallingContext
{
    /**
     * This flag is set to true at the startDocument event
     * and false at the endDocument event.
     * 
     * Until the first document is unmarshalled, we don't
     * want to return an object. So this variable is initialized
     * to true.
     */
    private boolean isUnmarshalInProgress = true;
    
    
    
    public SAXUnmarshallerHandlerImpl( UnmarshallerImpl _parent, GrammarInfo _gi ) {
        this.parent = _parent;
        grammarInfo = _gi;
        startPrefixMapping("",""); // by default, the default ns is bound to "".
     }
    
    private final GrammarInfo grammarInfo;
    public GrammarInfo getGrammarInfo() { return grammarInfo; }
    
    /**
     * Returns true if we should be collecting characters in the current element.
     */
    private final boolean shouldCollectText() {
        return collectText[stackTop];
    }
    
    public void startDocument() throws SAXException {
        // reset the object
        result = null;
        handlerLen=0;
        patchers=null;
        patchersLen=0;
        aborted = false;
        isUnmarshalInProgress = true;
        
        stackTop=0;
        elementDepth=1;
    }
    
    public void endDocument() throws SAXException {
        runPatchers();
        isUnmarshalInProgress = false;
    }
    
    public void startElement( String uri, String local, String qname, Attributes atts )
            throws SAXException {
        
        // work gracefully with misconfigured parsers that don't support namespaces
        if( uri==null )
            uri="";
        if( local==null || local.length()==0 )
            local=qname;
        if( qname==null || qname.length()==0 )
            qname=local;
        
        if(result==null) {
            // this is the root element.
            // create a root object and start unmarshalling
            UnmarshallingEventHandler unmarshaller =
                grammarInfo.createUnmarshaller(uri,local,this);
            if(unmarshaller==null) {
                // the registry doesn't know about this element.
                //
                // the no.1 cause of this problem is that your application is configuring
                // an XML parser by your self and you forgot to call
                // the SAXParserFactory.setNamespaceAware(true). When this happens, you see
                // the namespace URI is reported as empty whereas you expect something else.
                throw new SAXParseException(
                    Messages.format( Messages.UNEXPECTED_ROOT_ELEMENT2,
                        uri, local, computeExpectedRootElements() ),
                    getLocator() );
            }
            result = unmarshaller.owner();

            pushContentHandler(unmarshaller,0);
        }
    
        processText(true);
    
        getCurrentHandler().enterElement(uri,local,qname,atts);
    }

    public final void endElement( String uri, String local, String qname )
            throws SAXException {
        
        // work gracefully with misconfigured parsers that don't support namespaces
        if( uri==null )
            uri="";
        if( local==null || local.length()==0 )
            local=qname;
        if( qname==null || qname.length()==0 )
            qname=local;
        
        processText(false);
        getCurrentHandler().leaveElement(uri,local,qname);
    }
    
    
    
    
    
    /** Root object that is being unmarshalled. */
    private Object result;
    public Object getResult() throws UnmarshalException {
        if(isUnmarshalInProgress)
            throw new IllegalStateException();
        
        if(!aborted)       return result;
        
        // there was an error.
        throw new UnmarshalException((String)null);
    }

    
    
//
//
// handler stack maintainance
//
//
    private UnmarshallingEventHandler[] handlers = new UnmarshallingEventHandler[16];
    private int[] mementos = new int[16];
    private int handlerLen=0;
    
    public void pushContentHandler( UnmarshallingEventHandler handler, int memento ) {
        if(handlerLen==handlers.length) {
            // expand buffer
            UnmarshallingEventHandler[] h = new UnmarshallingEventHandler[handlerLen*2];
            int[] m = new int[handlerLen*2];
            System.arraycopy(handlers,0,h,0,handlerLen);
            System.arraycopy(mementos,0,m,0,handlerLen);
            handlers = h;
            mementos = m;
        }
        handlers[handlerLen] = handler;
        mementos[handlerLen] = memento;
        handlerLen++;
    }
    
    public void popContentHandler() throws SAXException {
        handlerLen--;
        handlers[handlerLen]=null;  // this handler is removed
        getCurrentHandler().leaveChild(mementos[handlerLen]);
    }

    public UnmarshallingEventHandler getCurrentHandler() {
        return handlers[handlerLen-1];
    }


//
//
// text handling
//
//    
    private StringBuffer buffer = new StringBuffer();
    
    protected void consumeText( String str, boolean ignorable ) throws SAXException {
         if(ignorable && str.trim().length()==0)
            // if we are allowed to ignore text and
            // the text is ignorable, ignore.
            return;
        
        // otherwise perform a transition by this token.
        getCurrentHandler().text(str);
    }
    private void processText( boolean ignorable ) throws SAXException {
        if( shouldCollectText() )
            consumeText(buffer.toString(),ignorable);
        
        // avoid excessive object allocation, but also avoid
        // keeping a huge array inside StringBuffer.
        if(buffer.length()<1024)    buffer.setLength(0);
        else                        buffer = new StringBuffer();
    }
    
    public final void characters( char[] buf, int start, int len ) {
        if( shouldCollectText() )
            buffer.append(buf,start,len);
    }

    public final void ignorableWhitespace( char[] buf, int start, int len ) {
        characters(buf,start,len);
    }



    
//
//
// namespace binding maintainance
//
//
    private String[] nsBind = new String[16];
    private int nsLen=0;
    
    // in the current scope, nsBind[0] - nsBind[idxStack[idxStackTop]-1]
    // are active.
    // use {@link #elementDepth} and {@link stackTop} to access.
    private int[] idxStack = new int[16];
    
    public void startPrefixMapping( String prefix, String uri ) {
        if(nsBind.length==nsLen) {
            // expand the buffer
            String[] n = new String[nsLen*2];
            System.arraycopy(nsBind,0,n,0,nsLen);
            nsBind=n;
        }
        nsBind[nsLen++] = prefix;
        nsBind[nsLen++] = uri;
    }
    public void endPrefixMapping( String prefix ) {
        nsLen-=2;
    }
    public String resolveNamespacePrefix( String prefix ) {
        if(prefix.equals("xml"))
            return "http://www.w3.org/XML/1998/namespace";
        
        for( int i=idxStack[stackTop]-2; i>=0; i-=2 ) {
            if(prefix.equals(nsBind[i]))
                return nsBind[i+1];
        }
        return null;
    }
    public String[] getNewlyDeclaredPrefixes() {
        return getPrefixList( idxStack[stackTop-1] );
    }

    public String[] getAllDeclaredPrefixes() {
        return getPrefixList( 2 );  // skip the default ""->"" mapping
    }
    
    private String[] getPrefixList( int startIndex ) {
        int size = (idxStack[stackTop]-startIndex)/2;
        String[] r = new String[size];
        for( int i=0; i<r.length; i++ )
            r[i] = nsBind[startIndex+i*2];
        return r;
    }

    
    //
    //  NamespaceContext2 implementation 
    //
    public Iterator getPrefixes(String uri) {
        // wrap it into unmodifiable list so that the remove method
        // will throw UnsupportedOperationException.
        return Collections.unmodifiableList(
            getAllPrefixesInList(uri)).iterator();
    }
    
    private List getAllPrefixesInList(String uri) {
        List a = new ArrayList();
        
        if( uri.equals(XMLConstants.XML_NS_URI) ) {
            a.add(XMLConstants.XML_NS_PREFIX);
            return a;
        }
        if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) ) {
            a.add(XMLConstants.XMLNS_ATTRIBUTE);
            return a;
        }
        if( uri==null )
            throw new IllegalArgumentException();
          
        for( int i=nsLen-2; i>=0; i-=2 )
            if(uri.equals(nsBind[i+1]))
                if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) )
                    // make sure that this prefix is still effective.
                    a.add(nsBind[i]);
         
        return a;
    }

    public String getPrefix(String uri) {
        if( uri.equals(XMLConstants.XML_NS_URI) )
            return XMLConstants.XML_NS_PREFIX;
        if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) )
            return XMLConstants.XMLNS_ATTRIBUTE;
        if( uri==null )
            throw new IllegalArgumentException();
          
        for( int i=idxStack[stackTop]-2; i>=0; i-=2 )
            if(uri.equals(nsBind[i+1]))
                if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) )
                    // make sure that this prefix is still effective.
                    return nsBind[i];
         
        return null;
    }

     public String getNamespaceURI(String prefix) {
         if( prefix==null )
             throw new IllegalArgumentException();
         if( prefix.equals(XMLConstants.XMLNS_ATTRIBUTE) )
             return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
        
         return resolveNamespacePrefix(prefix);
     }

//
//
// Attribute handling
//
//
    /**
     * Attributes stack.
     */
    private AttributesImpl[] attStack = new AttributesImpl[16];
    /**
     * Element nesting level.
     */
    private int elementDepth;
    /**
     * Always {@link #elementDepth}-1.
     */
    private int stackTop;
    
    /**
     * Stack of collectText flag.
     * False means text can be ignored for this element.
     * 
     * Use {@link #elementDepth} and {@link #stackTop} to access the array.
     */ 
    private boolean[] collectText = new boolean[16];
    
    public void pushAttributes( Attributes atts, boolean collectTextFlag ) {
        
        if( attStack.length==elementDepth ) {
            // reallocate the buffer
            AttributesImpl[] buf1 = new AttributesImpl[attStack.length*2];
            System.arraycopy(attStack,0,buf1,0,attStack.length);
            attStack = buf1;
            
            int[] buf2 = new int[idxStack.length*2];
            System.arraycopy(idxStack,0,buf2,0,idxStack.length);
            idxStack = buf2;
            
            boolean[] buf3 = new boolean[collectText.length*2];
            System.arraycopy(collectText,0,buf3,0,collectText.length);
            collectText = buf3;
        }
        
        elementDepth++;
        stackTop++;
        
        // push the stack
        AttributesImpl a = attStack[stackTop];
        if( a==null )
            attStack[stackTop] = a = new AttributesImpl();
        else
            a.clear();
        
        // since Attributes object is mutable, it is criticall important
        // to make a copy.
        // also symbolize attribute names
        for( int i=0; i<atts.getLength(); i++ ) {
            String auri = atts.getURI(i);
            String alocal = atts.getLocalName(i);
            String avalue = atts.getValue(i);
            String aqname = atts.getQName(i);
            
            // work gracefully with misconfigured parsers that don't support namespaces
            if( auri==null )
                auri="";
            if( alocal==null || alocal.length()==0 )
                alocal=aqname;
            if( aqname==null || aqname.length()==0 )
                aqname=alocal;

            // <foo xsi:nil="false">some value</foo> is a valid fragment, however
            // we need a look ahead to correctly handle this case.
            // (because when we process @xsi:nil, we don't know what the value is,
            // and by the time we read "false", we can't cancel this attribute anymore.)
            //
            // as a quick workaround, we remove @xsi:nil if the value is false.
            if( auri=="http://www.w3.org/2001/XMLSchema-instance" && alocal=="nil" ) {
                String v = avalue.trim();
                if(v.equals("false") || v.equals("0"))
                    continue;   // skip this attribute
            }
            
            // otherwise just add it.
            a.addAttribute(
                    auri,
                    alocal,
                    aqname,
                    atts.getType(i),
                    avalue );
        }
        
        
        // start a new namespace scope
        idxStack[stackTop] = nsLen;
        
        collectText[stackTop] = collectTextFlag;
    }
    public void popAttributes() {
        stackTop--;
        elementDepth--;
    }
    public Attributes getUnconsumedAttributes() {
        return attStack[stackTop];
    }
    /**
     * @param uri,local
     *      has to be interned.
     */
    public int getAttribute( String uri, String local ) {
        return attStack[stackTop].getIndexFast(uri,local);
    }
    public void consumeAttribute( int idx ) throws SAXException {
        AttributesImpl a = attStack[stackTop];
        
        String uri = a.getURI(idx);
        String local = a.getLocalName(idx);
        String qname = a.getQName(idx);
        String value = a.getValue(idx);

        // mark the attribute as consumed
        // we need to remove the attribute before we process it
        // because the event handler might access attributes.
        a.removeAttribute(idx);
        
        
        getCurrentHandler().enterAttribute(uri,local,qname);
        consumeText(value,false);
        getCurrentHandler().leaveAttribute(uri,local,qname);
    }
    public String eatAttribute( int idx ) throws SAXException {
        AttributesImpl a = attStack[stackTop];
        
        String value = a.getValue(idx);

        // mark the attribute as consumed
        a.removeAttribute(idx);
        
        return value;
    }

//
//
// ID/IDREF related code
//
//
    /**
     * Submitted patchers in the order they've submitted.
     * Many XML vocabulary doesn't use ID/IDREF at all, so we
     * initialize it with null.
     */
    private Runnable[] patchers = null;
    private int patchersLen = 0;
    
    public void addPatcher( Runnable job ) {
        // re-allocate buffer if necessary
        if( patchers==null )
            patchers = new Runnable[32];
        if( patchers.length == patchersLen ) {
            Runnable[] buf = new Runnable[patchersLen*2];
            System.arraycopy(patchers,0,buf,0,patchersLen);
            patchers = buf;
        }
        patchers[patchersLen++] = job;
    }
    
    /** Executes all the patchers. */
    private void runPatchers() {
        if( patchers!=null ) {
            for( int i=0; i<patchersLen; i++ )
                patchers[i].run();
        }
    }

    /** Records ID->Object map. */
    private Hashtable idmap = null;

    public String addToIdTable( String id ) {
        if(idmap==null)     idmap = new Hashtable();
        idmap.put( id, getCurrentHandler().owner() );
        return id;
    }
    
    public Object getObjectFromId( String id ) {
        if(idmap==null)     return null;
        return idmap.get(id);
    }
    


//
//
// Other SAX callbacks
//
//
    public void skippedEntity( String name ) {
    }
    public void processingInstruction( String target, String data ) {
        // just ignore
    }
    public void setDocumentLocator( Locator loc ) {
        locator = loc;
    }
    public Locator getLocator() { return locator; }
    
    private Locator locator = DUMMY_LOCATOR;

    private static final Locator DUMMY_LOCATOR = new LocatorImpl();


//
//
// error handling
//
//
    private final UnmarshallerImpl parent;
    private boolean aborted = false;
    
    public void handleEvent(ValidationEvent event, boolean canRecover ) throws SAXException {
        ValidationEventHandler eventHandler;
        try {
            eventHandler = parent.getEventHandler();
        } catch( JAXBException e ) {
            // impossible.
            throw new JAXBAssertionError();
        }

        boolean recover = eventHandler.handleEvent(event);
        
        // if the handler says "abort", we will not return the object
        // from the unmarshaller.getResult()
        if(!recover)    aborted = true;
        
        if( !canRecover || !recover )
            throw new SAXException( new UnmarshalException(
                event.getMessage(),
                event.getLinkedException() ) );
    }
  
//
//
// ValidationContext implementation
//
//
    public String getBaseUri() { return null; }
    public boolean isUnparsedEntity(String s) { return true; }
    public boolean isNotation(String s) { return true; }


//
//
// debug trace methods
//
//
    private Tracer tracer;
    public void setTracer( Tracer t ) {
        this.tracer = t;
    }
    public Tracer getTracer() {
        if(tracer==null)
            tracer = new Tracer.Standard();
        return tracer;
    }
    
    /**
     * Computes the names of possible root elements for a better error diagnosis.
     */
    private String computeExpectedRootElements() {
        String r = "";
        
        String[] probePoints = grammarInfo.getProbePoints();
        for( int i=0; i<probePoints.length; i+=2 ) {
            if( grammarInfo.recognize(probePoints[i],probePoints[i+1]) ) {
                if(r.length()!=0)   r+=',';
                r += "<{"+probePoints[i]+"}"+probePoints[i+1]+">";
            }
        }
        
        return r;
    }
}
