/*
* Copyright (c) 1998-2006 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
* of NON-INFRINGEMENT.  See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
*   Free Software Foundation, Inc.
*   59 Temple Place, Suite 330
*   Boston, MA 02111-1307  USA
*
* @author Adam Megacz
*/

package com.caucho.jaxb;

import com.caucho.jaxb.adapters.BeanAdapter;
import com.caucho.jaxb.skeleton.ClassSkeleton;
import com.caucho.jaxb.property.Property;
import com.caucho.util.L10N;
import com.caucho.vfs.*;
import com.caucho.xml.stream.*;

import org.w3c.dom.*;
import org.xml.sax.*;

import javax.xml.bind.*;
import javax.xml.bind.annotation.adapters.*;
import javax.xml.bind.attachment.*;
import javax.xml.bind.helpers.*;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.sax.*;
import javax.xml.validation.*;

import java.net.URL;
import java.io.*;
import java.util.*;

// XXX extends AbstractUnmarshallerImpl
public class UnmarshallerImpl implements Unmarshaller
{
  private static final L10N L = new L10N(UnmarshallerImpl.class);

  private JAXBContextImpl _context;
  
  protected boolean validating;

  private AttachmentUnmarshaller _attachmentUnmarshaller = null;
  private ValidationEventHandler _validationEventHandler = null;
  private Listener _listener = null;
  private HashMap<String,Object> _properties = new HashMap<String,Object>();
  private Schema _schema = null;
  private XMLReader _xmlreader = null;
  private XmlAdapter _adapter = null;
  private HashMap<Class,XmlAdapter> _adapters
    = new HashMap<Class,XmlAdapter>();

  UnmarshallerImpl(JAXBContextImpl context)
    throws JAXBException
  {
    _context = context;
    setEventHandler(JAXBContextImpl.DEFAULT_VALIDATION_EVENT_HANDLER);
  }

  //
  // unmarshallers.
  //

  /**
   * Unmarshall from a DOM node.
   */
  public Object unmarshal(Node node) throws JAXBException
  {
    try {
      XMLInputFactory factory = _context.getXMLInputFactory();
      
      return unmarshal(factory.createXMLStreamReader(new DOMSource(node)));
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
  }

  /**
   * Unmarshall from an input source.
   */
  protected Object unmarshal(XMLReader reader, InputSource source)
    throws JAXBException
  {
    throw new UnsupportedOperationException();
  }

  /**
   * Unmarshall from an event reader.
   */
  public Object unmarshal(XMLEventReader reader) throws JAXBException
  {
    throw new UnsupportedOperationException();

    /*
    try {
      XMLEvent event = null;

      while (reader.hasNext()) {
        event = reader.peek();

        if (event.isStartElement()) {
          StartElement start = (StartElement) event;

          ClassSkeleton skel = _context.getRootElement(start.getName());

          if (skel == null)
            throw new JAXBException(L.l("'{0}' is an unknown root element",
                  start.getName()));

          return skel.read(this, reader);
        }

        event = reader.nextEvent();
      }
      
      throw new JAXBException(L.l("Expected root element"));
    }
    catch (JAXBException e) {
      throw e;
    } 
    catch (Exception e) {
      throw new RuntimeException(e);
    }*/
  }

  public <T> JAXBElement<T> unmarshal(XMLEventReader xmlEventReader,
                                      Class<T> declaredType)
    throws JAXBException
  {
    throw new UnsupportedOperationException();
  }

  public Object unmarshal(XMLStreamReader reader)
    throws JAXBException
  {
    try {
      if (reader.nextTag() != XMLStreamReader.START_ELEMENT)
        throw new JAXBException(L.l("Expected root element"));

      ClassSkeleton skel = _context.getRootElement(reader.getName());

      if (skel == null)
        throw new JAXBException(L.l("'{0}' is an unknown root element",
              reader.getName()));

      return skel.read(this, reader);
    } 
    catch (JAXBException e) {
      throw e;
    } 
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Parses the XML based on an InputStream
   */
  public Object unmarshal(InputStream is)
    throws JAXBException
  {
    try {
      XMLStreamReader reader = new XMLStreamReaderImpl(is);

      try {
        return unmarshal(reader);
      } 
      finally {
        reader.close();
      }
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
  }

  public <T> JAXBElement<T> unmarshal(XMLStreamReader reader,
                                      Class<T> declaredType)
      throws JAXBException
  {
    try {
      while (reader.getEventType() != XMLStreamReader.START_ELEMENT)
        reader.next();

      QName name = reader.getName();

      Property property = _context.createProperty(declaredType);

      T val = (T) property.read(this, reader, null);

      return new JAXBElement<T>(name, declaredType, val);
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  //
  // From AbstractUnmarshallerImpl
  //

  public UnmarshallerHandler getUnmarshallerHandler()
  {
    // The idea here is that we return a SAX ContentHandler which the
    // user writes to and we use those writes to construct an object.
    // The object is retrieved using UnmarshallerHandler.getResult().
    // This is a "reusable" operation, so we should return a new handler
    // (or at least a reset one) for each call of this function.

    return new UnmarshallerHandlerImpl();
  }

  protected UnmarshalException createUnmarshalException(SAXException e)
  {
    return new UnmarshalException(e);
  }

  public <A extends XmlAdapter> A getAdapter(Class<A> type)
  {
    A a = (A)_adapters.get(type);

    if (a == null)
      return (A)new BeanAdapter();

    return a;
  }

  public AttachmentUnmarshaller getAttachmentUnmarshaller()
  {
    return _attachmentUnmarshaller;
  }

  public ValidationEventHandler getEventHandler() throws JAXBException
  {
    return _validationEventHandler;
  }

  public Listener getListener()
  {
    return _listener;
  }

  public Object getProperty(String name) throws PropertyException
  {
    return _properties.get(name);
  }

  public Schema getSchema()
  {
    return _schema;
  }

  protected XMLReader getXMLReader() throws JAXBException
  {
    return _xmlreader;
  }

  public boolean isValidating() throws JAXBException
  {
    return validating;
  }

  public <A extends XmlAdapter> void setAdapter(Class<A> type, A adapter)
  {
    _adapters.put(type, adapter);
  }

  public void setAdapter(XmlAdapter adapter)
  {
    setAdapter((Class)adapter.getClass(), adapter);
  }

  public void setAttachmentUnmarshaller(AttachmentUnmarshaller au)
  {
    _attachmentUnmarshaller = au;
  }

  public void setEventHandler(ValidationEventHandler handler)
    throws JAXBException
  {
    _validationEventHandler = handler;
  }

  public void setListener(Listener listener)
  {
    _listener = listener;
  }

  public void setProperty(String name, Object value) throws PropertyException
  {
    _properties.put(name, value);
  }

  public void setSchema(Schema schema)
  {
    _schema = schema;
  }

  public void setValidating(boolean validating) throws JAXBException
  {
    this.validating = validating;
  }

  public Object unmarshal(File f) throws JAXBException
  {
    FileInputStream fis = null;
    try {
      fis = new FileInputStream(f);
      XMLInputFactory factory = _context.getXMLInputFactory();
      return unmarshal(factory.createXMLStreamReader(f.getAbsolutePath(), fis));
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
    catch (IOException e) {
      throw new JAXBException(e);
    }
    finally {
      try {
        if (fis != null)
          fis.close();
      }
      catch (IOException e) {
        throw new JAXBException(e);
      }
    }
  }

  public Object unmarshal(InputSource inputSource) 
    throws JAXBException
  {
    try {
      XMLEventReader reader = 
        new SAXSourceXMLEventReaderImpl(new SAXSource(inputSource));

      return unmarshal(reader);
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
  }

  public Object unmarshal(Reader reader) throws JAXBException
  {
    try {
      XMLInputFactory factory = _context.getXMLInputFactory();
      
      return unmarshal(factory.createXMLStreamReader(reader));
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
  }

  public Object unmarshal(Source source) throws JAXBException
  {
    try {
      XMLInputFactory factory = _context.getXMLInputFactory();
      
      return unmarshal(factory.createXMLEventReader(source));
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
  }

  public <T> JAXBElement<T> unmarshal(Node node, Class<T> declaredType)
      throws JAXBException
  {
    throw new UnsupportedOperationException("subclasses must override this");
  }

  public <T> JAXBElement<T> unmarshal(Source node, Class<T> declaredType)
      throws JAXBException
  {
    try {
      XMLInputFactory factory = XMLInputFactory.newInstance();
      return unmarshal(factory.createXMLStreamReader(node), declaredType);
    }
    catch (XMLStreamException e) {
      throw new JAXBException(e);
    }
  }

  public Object unmarshal(URL url) throws JAXBException
  {
    try {
      InputStream is = url.openStream();

      try {
        return unmarshal(is);
      } 
      finally {
        is.close();
      }
    } catch (IOException e) {
      throw new JAXBException(e);
    }
  }

  private class UnmarshallerHandlerImpl implements UnmarshallerHandler {
    private ContentHandler _handler;
    private SAXSourceXMLEventReaderImpl _reader;
    private boolean _done = false;
    private Object _result = null;

    public UnmarshallerHandlerImpl()
    {
      _reader = new SAXSourceXMLEventReaderImpl();
      _handler = _reader.getContentHandler();
    }

    public Object getResult()
      throws JAXBException
    {
      if (! _done)
        throw new IllegalStateException();

      if (_result == null)
        _result = unmarshal(_reader);

      return _result;
    }

    public void characters(char[] ch, int start, int length)
      throws SAXException
    {
      _handler.characters(ch, start, length);
    }

    public void endDocument()
      throws SAXException
    {
      _handler.endDocument();
      _done = true;
    }

    public void endElement(String uri, String localName, String qName)
      throws SAXException
    {
      _handler.endElement(uri, localName, qName);
    }

    public void endPrefixMapping(String prefix)
      throws SAXException
    {
      _handler.endPrefixMapping(prefix);
    }

    public void ignorableWhitespace(char[] ch, int start, int length)
      throws SAXException
    {
      _handler.ignorableWhitespace(ch, start, length);
    }

    public void processingInstruction(String target, String data)
      throws SAXException
    {
      _handler.processingInstruction(target, data);
    }

    public void setDocumentLocator(Locator locator)
    {
      _handler.setDocumentLocator(locator);
    }

    public void skippedEntity(String name)
      throws SAXException
    {
      _handler.skippedEntity(name);
    }

    public void startDocument()
      throws SAXException
    {
      _handler.startDocument();
    }

    public void startElement(String uri, String localName, String qName, 
                             Attributes atts)
      throws SAXException
    {
      _handler.startElement(uri, localName, qName, atts);
    }

    public void startPrefixMapping(String prefix, String uri)
      throws SAXException
    {
      _handler.startPrefixMapping(prefix, uri);
    }
  }
}
